/*
 * 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 <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <dlfcn.h>
#include <termios.h>
#include <pkcs11types.h>
#include <locale.h>
#include <limits.h>
#include <nl_types.h>
#include <memory.h>
#include <string.h>
#include <strings.h>
#include <pwd.h>
#include <grp.h>
#include <openssl/crypto.h>
#include "slotmgr.h"
#include "pkcsconf_msg.h"
#include "p11util.h"
#include "defs.h"

#define LEEDS_DEFAULT_PIN "87654321"
#define PIN_SIZE 80
#define BACK_SPACE 8
#define DELETE     127
#define LINE_FEED  10

#define CFG_SO_PIN         0x0001
#define CFG_USER_PIN       0x0002
#define CFG_SLOT           0x0004
#define CFG_PKCS_INFO      0x0008
#define CFG_TOKEN_INFO     0x0010
#define CFG_SLOT_INFO      0x0020
#define CFG_MECHANISM_INFO 0x0040
#define CFG_INITIALIZE     0x0080
#define CFG_INIT_USER      0x0100
#define CFG_SET_USER       0x0200
#define CFG_SET_SO         0x0400
#define CFG_NEW_PIN        0x0800
#define CFG_SHARED_MEM     0x1000
#define CFG_LIST_SLOT      0x2000

CK_RV init(void);
void usage(char *);
int echo(int);
int get_pin(CK_CHAR **);
int get_slot(char *);
CK_RV cleanup(void);
CK_RV display_pkcs11_info(void);
CK_RV get_slot_list(void);
CK_RV display_slot_info(int);
CK_RV display_token_info(int);
CK_RV display_mechanism_info(int);
CK_RV init_token(int, CK_CHAR_PTR);
CK_RV init_user_pin(int, CK_CHAR_PTR, CK_CHAR_PTR);
CK_RV list_slot(int);
CK_RV set_user_pin(int, CK_USER_TYPE, CK_CHAR_PTR, CK_CHAR_PTR);

void *dllPtr;
CK_FUNCTION_LIST_PTR FunctionPtr = NULL;
CK_SLOT_ID_PTR SlotList = NULL;
CK_ULONG SlotCount = 0;
Slot_Mgr_Shr_t *shmp = NULL;
int in_slot;

int main(int argc, char *argv[])
{
    CK_RV rv = CKR_OK;          // Return Code
    CK_FLAGS flags = 0;         // Bit mask for what options were passed in
    CK_CHAR_PTR sopin = NULL,   // The Security Office PIN
        pin = NULL,             // The User PIN
        newpin = NULL,          // To store PIN changes
        newpin2 = NULL;         // To store validation of PIN change

    int c,                      // To store passed in options
     newpinlen, newpin2len, errflag = 0;        // Error Flag

    /* Parse the command line parameters */
    while ((c = getopt(argc, argv, "itsmIc:S:U:upPn:lh")) != (-1)) {
        switch (c) {
        case 'c':              /* a specific card (slot) is specified */
            if (flags & CFG_SLOT) {
                printf("Must specify a single slot.\n");
                fflush(stdout);
                errflag++;
            } else {
                flags |= CFG_SLOT;
                in_slot = get_slot(optarg);
                if (in_slot < 0) {
                    printf("Must specify a decimal number as slot.\n");
                    errflag++;
                }
            }
            break;
        case 'S':              /* the SO pin */
            if (flags & CFG_SO_PIN) {
                printf("Must specify a single SO PIN.\n");
                fflush(stdout);
                errflag++;
            } else {
                flags |= CFG_SO_PIN;
                sopin = (CK_CHAR_PTR) malloc(strlen(optarg) + 1);
                memcpy(sopin, optarg, strlen(optarg) + 1);
            }
            break;
        case 'U':              /* the user pin */
            if (flags & CFG_USER_PIN) {
                printf("Must specify a single user PIN.\n");
                fflush(stdout);
                errflag++;
            } else {
                flags |= CFG_USER_PIN;
                pin = (CK_CHAR_PTR) malloc(strlen(optarg) + 1);
                memcpy(pin, optarg, strlen(optarg) + 1);
            }
            break;
        case 'n':              /* the new pin */
            if (flags & CFG_NEW_PIN) {
                printf("Must specify a single new PIN.\n");
                fflush(stdout);
                errflag++;
            } else {
                flags |= CFG_NEW_PIN;
                newpin = (CK_CHAR_PTR) malloc(strlen(optarg) + 1);
                memcpy(newpin, optarg, strlen(optarg) + 1);
            }
            break;
        case 'i':              /* display PKCS11 info */
            flags |= CFG_PKCS_INFO;
            break;
        case 't':              /* display token info */
            flags |= CFG_TOKEN_INFO;
            break;
        case 's':              /* display slot info */
            flags |= CFG_SLOT_INFO;
            break;
        case 'm':              /* display mechanism info */
            flags |= CFG_MECHANISM_INFO;
            break;
        case 'I':              /* initialize the token */
            flags |= CFG_INITIALIZE;
            break;
        case 'u':              /* initialize the user PIN */
            flags |= CFG_INIT_USER;
            break;
        case 'p':              /* set the user PIN */
            flags |= CFG_SET_USER;
            break;
        case 'P':              /* set the SO PIN */
            flags |= CFG_SET_SO;
            break;
        case 'l':              /* display slot description */
            flags |= CFG_LIST_SLOT;
            break;
        case 'h':              /* display command line options */
            usage(argv[0]);
            break;
        default:               /* if something else was passed in it's an error */
            errflag++;
            break;
        }
    }
    if (errflag != 0)           /* If there was an error print the usage statement */
        usage(argv[0]);

    if (!flags)                 /* If there was no options print the usage statement */
        usage(argv[0]);

    /* Eliminate the ability to specify -I -p -u -P without a slot number */
    if ((flags & (CFG_INITIALIZE | CFG_INIT_USER | CFG_SET_USER | CFG_SET_SO))
        && !(flags & CFG_SLOT)) {
        usage(argv[0]);
    }
    /* Load the PKCS11 library and start the slotmanager if it is not running */
    if (init() != CKR_OK) {
        rv = CKR_FUNCTION_FAILED;
	goto done;
    }

    /* Get the slot list and indicate if a slot number was passed in or not */
    if ((rv = get_slot_list()))
        goto done;

    /* If the user tries to set the user and SO pin at the same time print an
     * error massage and exit indicating the function failed */
    if ((flags & CFG_SET_USER) && (flags & CFG_SET_SO)) {
        printf("Setting the SO and user PINs are mutually exclusive.\n");
        fflush(stdout);
        rv = CKR_FUNCTION_FAILED;
	goto done;
    }

    /* If the user wants to display PKCS11 info call the function to do so */
    if (flags & CFG_PKCS_INFO)
        if ((rv = display_pkcs11_info()))
            goto done;

    /* If the user wants to display token info call the function to do so */
    if (flags & CFG_TOKEN_INFO)
        if ((rv = display_token_info((flags & CFG_SLOT) ? in_slot : -1)))
            goto done;

    /* If the user wants to display slot info call the function to do so */
    if (flags & CFG_SLOT_INFO)
        if ((rv = display_slot_info((flags & CFG_SLOT) ? in_slot : -1)))
            goto done;

    /* If the user wants to display slot info call the function to do so */
    if (flags & CFG_LIST_SLOT)
        if ((rv = list_slot((flags & CFG_SLOT) ? in_slot : -1)))
            goto done;

    /* If the user wants to display mechanism info call the function to do so */
    if (flags & CFG_MECHANISM_INFO)
        if ((rv = display_mechanism_info((flags & CFG_SLOT) ? in_slot : -1)))
            goto done;

    /* If the user wants to initialize the card check to see if they passed in
     * the SO pin, if not ask for the PIN */
    if (flags & CFG_INITIALIZE) {
        if (flags & CFG_SLOT) {
            if (~flags & CFG_SO_PIN) {
                int rc;
                do {
                    printf("Enter the SO PIN: ");
                    fflush(stdout);
                    rc = get_pin(&(sopin));
                } while (rc == -EINVAL);
            }
            rv = init_token(in_slot, sopin);
        } else {
            printf("Must specify one slot");
            fflush(stdout);
            rv = CKR_FUNCTION_FAILED;
        }
    }

    /* If the user wants to initialize the User PIN, check to see if they have
     * passed in the SO PIN, if not ask for it.  Then check to see if they
     * passed the New User PIN on the command line if not ask for the PIN and
     * verify it
     */
    if (flags & CFG_INIT_USER) {
        if (flags & CFG_SLOT) {
            if (~flags & CFG_SO_PIN) {
                int rc;

                do {
                    printf("Enter the SO PIN: ");
                    fflush(stdout);
                    rc = get_pin(&sopin);
                } while (rc == -EINVAL);
            }
            if (~flags & CFG_NEW_PIN) {
                int rc;

                do {
                    printf("Enter the new user PIN: ");
                    fflush(stdout);
                    rc = get_pin(&newpin);
                } while (rc == -EINVAL);
                newpinlen = strlen((char *) newpin);
                do {
                    printf("Re-enter the new user PIN: ");
                    fflush(stdout);
                    rc = get_pin(&newpin2);
                } while (rc == -EINVAL);
                newpin2len = strlen((char *) newpin2);
                if (newpinlen != newpin2len
                    || memcmp(newpin, newpin2, strlen((char *) newpin)) != 0) {
                    printf("New PINs do not match.\n");
                    fflush(stdout);
                    exit(CKR_PIN_INVALID);
                }
            }
            rv = init_user_pin(in_slot, newpin, sopin);
        } else {
            printf("Must specify one slot");
            fflush(stdout);
            rv = CKR_FUNCTION_FAILED;
        }
    }

    /* If the user wants to set the SO PIN, check to see if they have passed the
     * current SO PIN and the New PIN in.  If not prompt and validate them. */
    if (flags & CFG_SET_SO) {
        if (flags & CFG_SLOT) {
            if (~flags & CFG_SO_PIN) {
                int rc;

                do {
                    printf("Enter the SO PIN: ");
                    fflush(stdout);
                    rc = get_pin(&sopin);
                } while (rc == -EINVAL);
            }
            if (~flags & CFG_NEW_PIN) {
                int rc;

                do {
                    printf("Enter the new SO PIN: ");
                    fflush(stdout);
                    rc = get_pin(&newpin);
                } while (rc == -EINVAL);
                newpinlen = strlen((char *) newpin);
                do {
                    printf("Re-enter the new SO PIN: ");
                    fflush(stdout);
                    rc = get_pin(&newpin2);
                } while (rc == -EINVAL);
                newpin2len = strlen((char *) newpin2);
                if (newpinlen != newpin2len
                    || memcmp(newpin, newpin2, strlen((char *) newpin)) != 0) {
                    printf("New PINs do not match.\n");
                    fflush(stdout);
                    exit(CKR_PIN_INVALID);
                }
            }
            rv = set_user_pin(in_slot, CKU_SO, sopin, newpin);
        } else {
            printf("Must specify one slot");
            fflush(stdout);
            rv = CKR_FUNCTION_FAILED;
        }
    }

    /* If the user wants to set the User PIN, check to see if they have passed
     * the current User PIN and the New PIN in. If not prompt and validate them.
     */
    if (flags & CFG_SET_USER) {
        if (flags & CFG_SLOT) {
            if (~flags & CFG_USER_PIN) {
                int rc;

                do {
                    printf("Enter user PIN: ");
                    fflush(stdout);
                    rc = get_pin(&pin);
                } while (rc == -EINVAL);
            }
            if (~flags & CFG_NEW_PIN) {
		int rc;

                do {
                    printf("Enter the new user PIN: ");
                    fflush(stdout);
                    rc = get_pin(&newpin);
                } while (rc == -EINVAL);
                newpinlen = strlen((char *) newpin);
                do {
                    printf("Re-enter the new user PIN: ");
                    fflush(stdout);
                    rc = get_pin(&newpin2);
                } while (rc == -EINVAL);
                newpin2len = strlen((char *) newpin2);
                if (newpinlen != newpin2len
                    || memcmp(newpin, newpin2, strlen((char *) newpin)) != 0) {
                    printf("New PINs do not match.\n");
                    fflush(stdout);
                    exit(CKR_PIN_INVALID);
                }
            }
            rv = set_user_pin(in_slot, CKU_USER, pin, newpin);
        } else {
            printf("Must specify one slot");
            fflush(stdout);
            rv = CKR_FUNCTION_FAILED;
        }
    }

    /* We are done, detach from shared memory, and free the memory we may have
     * allocated.  In the case of PIN's we use cleanse to ensure that they are
     * not left around in system memory*/

done:
    if (sopin) {
        OPENSSL_cleanse(sopin, strlen((char *) sopin));
        free(sopin);
    }

    if (pin) {
        OPENSSL_cleanse(pin, strlen((char *) pin));
        free(pin);
    }

    if (newpin) {
        OPENSSL_cleanse(newpin, strlen((char *) newpin));
        free(newpin);
    }

    if (newpin2) {
        OPENSSL_cleanse(newpin2, strlen((char *) newpin2));
        free(newpin2);
    }

    return rv == CKR_OK ? 0 : -1;
}

int get_pin(CK_CHAR **pin)
{
    int count;
    char buff[PIN_SIZE] = { 0 }, c = 0;
    int rc = 0;

    *pin = NULL;
    /* Turn off echoing to the terminal when getting the password */
    echo(FALSE);
    /* Get each character and print out a '*' for each input */
    for (count = 0; (c != LINE_FEED) && (count < PIN_SIZE);) {
        buff[count] = getc(stdin);
        c = buff[count];
        if (c == BACK_SPACE || c == DELETE) {
            if (count)
                count--;
            continue;
        }
        fflush(stdout);
        count++;
    }
    echo(TRUE);
    /* After we get the password go to the next line */
    printf("\n");
    fflush(stdout);
    /* Allocate 80 bytes for the user PIN. This is large enough
     * for the tokens supported in AIX 5.0 and 5.1 */
    *pin = (unsigned char *) malloc(PIN_SIZE);
    if (!(*pin)) {
        rc = -ENOMEM;
        goto out;
    }
    /* Strip the carage return from the user input (it is not part
     * of the PIN) and put the PIN in the return buffer */
    buff[count - 1] = '\0';
    /* keep the trailing null for the strlen */
    strncpy((char *) *pin, buff, PIN_SIZE);
out:
    return rc;
}

int get_slot(char *optarg)
{
    char *endptr;
    long val;

    errno = 0;
    val = strtol(optarg, &endptr, 10);

    /* Check for various possible errors */
    if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
        || (errno != 0 && val == 0)) {
        perror("strtol");
        return -1;
    }

    /* No digits were found in optarg, so return error */
    if (endptr == optarg)
        return -1;

    /* Invalid slot id */
    if (val < INT_MIN || val >= NUMBER_SLOTS_MANAGED)
        return -1;

    return (int)val;
}

int echo(int bool)
{
    struct termios term;

    /* flush standard out to make sure everything that needs to be displayed has
     * been displayed */
    fflush(stdout);

    /* get the current terminal attributes */
    if (tcgetattr(STDIN_FILENO, &term) != 0)
        return -1;

    /* Since we are calling this function we must want to read in a char at a
     * time.  Therefore set the cc structure before setting the terminal attrs
     */
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;

    /* If we are turning off the display of input characters AND with the
     * inverse of the ECHO mask, if we are turning on the display OR with the
     * ECHO mask.
     * We also set if we are reading in canonical or noncanonical mode.  */
    if (bool)
        term.c_lflag |= (ECHO | ICANON);
    else
        term.c_lflag &= ~(ECHO | ICANON);

    /* Set the attributes, and flush the streams so that any input already
     * displayed on the terminal is invalid */
    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &term) != 0)
        return -1;

    return 0;
}

CK_RV check_user_and_group(void)
{
    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) {
        return CKR_FUNCTION_FAILED;
    }

    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;
    }

    return CKR_FUNCTION_FAILED;
}

CK_RV display_pkcs11_info(void)
{

    CK_RV rc;
    CK_INFO CryptokiInfo;

    /* Get the PKCS11 infomation structure and if fails print message */
    rc = FunctionPtr->C_GetInfo(&CryptokiInfo);
    if (rc != CKR_OK) {
        printf("Error getting PKCS#11 info: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        return rc;
    }

    /* display the header and information */
    printf("PKCS#11 Info\n");
    printf("\tVersion %d.%d \n", CryptokiInfo.cryptokiVersion.major,
           CryptokiInfo.cryptokiVersion.minor);
    printf("\tManufacturer: %.32s \n", CryptokiInfo.manufacturerID);
    printf("\tFlags: 0x%lX  \n", CryptokiInfo.flags);
    printf("\tLibrary Description: %.32s \n", CryptokiInfo.libraryDescription);
    printf("\tLibrary Version: %d.%d \n", CryptokiInfo.libraryVersion.major,
           CryptokiInfo.libraryVersion.minor);

    return rc;
}

CK_RV get_slot_list()
{
    CK_RV rc;                   // Return Code

    /* Find out how many tokens are present in slots */
    rc = FunctionPtr->C_GetSlotList(TRUE, NULL_PTR, &SlotCount);
    if (rc != CKR_OK) {
        printf("Error getting number of slots: 0x%lX (%s)\n", rc,
               p11_get_ckr(rc));
        return rc;
    }

    if (SlotCount == 0) {
        printf("C_GetSlotList returned 0 slots. Check that your tokens"
               " are installed correctly.\n");
        return -ENODEV;
    }

    /* Allocate enough space for the slots information */
    SlotList = (CK_SLOT_ID_PTR) malloc(SlotCount * sizeof(CK_SLOT_ID));

    rc = FunctionPtr->C_GetSlotList(TRUE, SlotList, &SlotCount);
    if (rc != CKR_OK) {
        printf("Error getting slot list: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        return rc;
    }

    return CKR_OK;
}

void display_mechanism_name(CK_MECHANISM_TYPE mech)
{
    CK_ULONG i;

    for (i = 0; pkcs11_mech_list[i].name; i++) {
        if (pkcs11_mech_list[i].mech == mech) {
            printf("(%s)", pkcs11_mech_list[i].name);
            return;
        }
    }
}

void display_mechanism_flags(CK_FLAGS flags)
{
    CK_ULONG i, firsties = 1;
    char *tok = "(";

    for (i = 0; pkcs11_mech_flags[i].name; i++) {
        if (pkcs11_mech_flags[i].flag & flags) {
            printf("%s%s", tok, pkcs11_mech_flags[i].name);

            if (firsties) {
                tok = "|";
                firsties = 0;
            }
        }
    }

    if (!firsties)
        printf(")");
}

CK_RV print_mech_info(int slot_id)
{
    CK_RV rc;                   // Return Code
    CK_MECHANISM_TYPE_PTR MechanismList = NULL; // Head to Mechanism list
    CK_MECHANISM_INFO MechanismInfo;    // Structure to hold Mechanism Info
    CK_ULONG MechanismCount = 0;        // Number of supported mechanisms
    unsigned int i;

    /* For each slot find out how many mechanisms are supported */
    rc = FunctionPtr->C_GetMechanismList(slot_id, NULL_PTR, &MechanismCount);
    if (rc != CKR_OK) {
        printf("Error getting number of mechanisms: 0x%lX (%s)\n",
               rc, p11_get_ckr(rc));
        return rc;
    }

    /* Allocate enough memory to store all the supported mechanisms */
    MechanismList = (CK_MECHANISM_TYPE_PTR) malloc(MechanismCount *
                                                   sizeof(CK_MECHANISM_TYPE));

    /* This time get the mechanism list */
    rc = FunctionPtr->C_GetMechanismList(slot_id, MechanismList,
                                         &MechanismCount);
    if (rc != CKR_OK) {
        printf("Error getting mechanisms list: 0x%lX (%s)\n", rc,
               p11_get_ckr(rc));
        return rc;
    }

    /* For each Mechanism in the List */
    for (i = 0; i < MechanismCount; i++) {

        /* Get the Mechanism Info and display it */
        rc = FunctionPtr->C_GetMechanismInfo(slot_id,
                                             MechanismList[i], &MechanismInfo);
        if (rc != CKR_OK) {
            printf("Error getting mechanisms info: 0x%lX (%s)\n", rc,
                   p11_get_ckr(rc));
            return rc;
        }
        printf("Mechanism #%d\n", i);
        printf("\tMechanism: 0x%lX ", MechanismList[i]);

        display_mechanism_name(MechanismList[i]);
        printf("\n");

        printf("\tKey Size: %lu-%lu\n", MechanismInfo.ulMinKeySize,
               MechanismInfo.ulMaxKeySize);
        printf("\tFlags: 0x%lX ", MechanismInfo.flags);

        display_mechanism_flags(MechanismInfo.flags);
        printf("\n");
    }

    /* Free the memory we allocated for the mechanism list */
    free(MechanismList);
    return CKR_OK;
}

CK_RV display_mechanism_info(int slot_id)
{
    CK_ULONG lcv;

    if (slot_id == -1) {
        for (lcv = 0; lcv < SlotCount; lcv++) {
            printf("Mechanism Info for Slot #%lu:\n", SlotList[lcv]);
            print_mech_info(SlotList[lcv]);
        }
    } else {
        return print_mech_info(slot_id);
    }

    return CKR_OK;
}

void print_slot_info(int slot_id, CK_SLOT_INFO *SlotInfo)
{
    /* Display the slot information */
    printf("Slot #%d Info\n", slot_id);
    printf("\tDescription: %.64s\n", SlotInfo->slotDescription);
    printf("\tManufacturer: %.32s\n", SlotInfo->manufacturerID);
    printf("\tFlags: 0x%lX (", SlotInfo->flags);

    if (SlotInfo->flags & CKF_TOKEN_PRESENT)
        printf("TOKEN_PRESENT|");
    if (SlotInfo->flags & CKF_REMOVABLE_DEVICE)
        printf("REMOVABLE_DEVICE|");
    if (SlotInfo->flags & CKF_HW_SLOT)
        printf("HW_SLOT|");
    printf(")\n");

    printf("\tHardware Version: %d.%d\n", SlotInfo->hardwareVersion.major,
           SlotInfo->hardwareVersion.minor);
    printf("\tFirmware Version: %d.%d\n", SlotInfo->firmwareVersion.major,
           SlotInfo->firmwareVersion.minor);
}

CK_RV display_slot_info(int slot_id)
{
    CK_RV rc;                   // Return Code
    CK_SLOT_INFO SlotInfo;      // Structure to hold slot information
    unsigned int lcv;           // Loop control Variable

    if (slot_id != -1) {
        rc = FunctionPtr->C_GetSlotInfo(slot_id, &SlotInfo);
        if (rc != CKR_OK) {
            printf("Error getting slot info: 0x%lX (%s) \n", rc,
                   p11_get_ckr(rc));
            return rc;
        }

        print_slot_info(slot_id, &SlotInfo);
        return CKR_OK;
    }

    for (lcv = 0; lcv < SlotCount; lcv++) {
        /* Get the info for the slot we are examining and store in SlotInfo */
        rc = FunctionPtr->C_GetSlotInfo(SlotList[lcv], &SlotInfo);
        if (rc != CKR_OK) {
            printf("Error getting slot info: 0x%lX (%s) \n", rc,
                   p11_get_ckr(rc));
            return rc;
        }

        print_slot_info(SlotList[lcv], &SlotInfo);

    }
    return CKR_OK;
}

CK_RV list_slot(int slot_id)
{
    CK_RV rc;                   // Return code
    CK_SLOT_INFO SlotInfo;      // Structure to hold slot information
    unsigned int lcv;           // Loop control variable

    if (slot_id != -1) {
        rc = FunctionPtr->C_GetSlotInfo(slot_id, &SlotInfo);
        if (rc != CKR_OK) {
            printf("Error getting slot info: 0x%lX (%s)\n", rc,
                   p11_get_ckr(rc));
            return rc;
        }

        /* Display the slot description */
        printf("%d:", slot_id);
        printf("\tDescription: %.64s\n", SlotInfo.slotDescription);

        return CKR_OK;
    }


    for (lcv = 0; lcv < SlotCount; lcv++) {
        /* Get the info for the slot we are examining and store in SlotInfo */
        rc = FunctionPtr->C_GetSlotInfo(SlotList[lcv], &SlotInfo);
        if (rc != CKR_OK) {
            printf("Error getting slot info: 0x%lX (%s)\n", rc,
                   p11_get_ckr(rc));
            return rc;
        }

        /* Display the slot description */
        printf("%ld:", SlotList[lcv]);
        printf("\tDescription: %.64s\n", SlotInfo.slotDescription);
    }
    return CKR_OK;
}

static void print_value(CK_ULONG value, char *buf, CK_ULONG buf_len,
                        CK_BBOOL check_infinite, char *fmt)
{
    if (value == CK_UNAVAILABLE_INFORMATION)
        strncpy(buf, "[information unavailable]", buf_len - 1);
    else if (check_infinite && value == CK_EFFECTIVELY_INFINITE)
        strncpy(buf, "[effectively infinite]", buf_len - 1);
    else
        snprintf(buf, buf_len, fmt, value);
    buf[buf_len - 1] = '\0';
}

void print_token_info(int slot_id, CK_TOKEN_INFO *TokenInfo)
{
    char temp1[256];
    char temp2[256];

    /* Display the token information */
    printf("Token #%d Info:\n", slot_id);
    printf("\tLabel: %.32s\n", TokenInfo->label);
    printf("\tManufacturer: %.32s\n", TokenInfo->manufacturerID);
    printf("\tModel: %.16s\n", TokenInfo->model);
    printf("\tSerial Number: %.16s\n", TokenInfo->serialNumber);
    printf("\tFlags: 0x%lX (", TokenInfo->flags);

    /* print more informative flag message */
    if (TokenInfo->flags & CKF_RNG)
        printf("RNG|");
    if (TokenInfo->flags & CKF_WRITE_PROTECTED)
        printf("WRITE_PROTECTED|");
    if (TokenInfo->flags & CKF_LOGIN_REQUIRED)
        printf("LOGIN_REQUIRED|");
    if (TokenInfo->flags & CKF_USER_PIN_INITIALIZED)
        printf("USER_PIN_INITIALIZED|");
    if (TokenInfo->flags & CKF_RESTORE_KEY_NOT_NEEDED)
        printf("RESTORE_KEY_NOT_NEEDED|");
    if (TokenInfo->flags & CKF_CLOCK_ON_TOKEN)
        printf("CLOCK_ON_TOKEN|");
    if (TokenInfo->flags & CKF_PROTECTED_AUTHENTICATION_PATH)
        printf("PROTECTED_AUTHENTICATION_PATH|");
    if (TokenInfo->flags & CKF_DUAL_CRYPTO_OPERATIONS)
        printf("DUAL_CRYPTO_OPERATIONS|");
    if (TokenInfo->flags & CKF_TOKEN_INITIALIZED)
        printf("TOKEN_INITIALIZED|");
    if (TokenInfo->flags & CKF_SECONDARY_AUTHENTICATION)
        printf("SECONDARY_AUTHENTICATION|");
    if (TokenInfo->flags & CKF_USER_PIN_COUNT_LOW)
        printf("USER_PIN_COUNT_LOW|");
    if (TokenInfo->flags & CKF_USER_PIN_FINAL_TRY)
        printf("USER_PIN_FINAL_TRY|");
    if (TokenInfo->flags & CKF_USER_PIN_LOCKED)
        printf("USER_PIN_LOCKED|");
    if (TokenInfo->flags & CKF_USER_PIN_TO_BE_CHANGED)
        printf("USER_PIN_TO_BE_CHANGED|");
    if (TokenInfo->flags & CKF_SO_PIN_COUNT_LOW)
        printf("SO_PIN_COUNT_LOW|");
    if (TokenInfo->flags & CKF_SO_PIN_FINAL_TRY)
        printf("SO_PIN_FINAL_TRY|");
    if (TokenInfo->flags & CKF_SO_PIN_LOCKED)
        printf("SO_PIN_LOCKED|");
    if (TokenInfo->flags & CKF_SO_PIN_TO_BE_CHANGED)
        printf("SO_PIN_TO_BE_CHANGED|");
    printf(")\n");

    print_value(TokenInfo->ulSessionCount, temp1, sizeof(temp1), FALSE, "%lu");
    print_value(TokenInfo->ulMaxSessionCount, temp2, sizeof(temp2), TRUE,
                "%lu");
    printf("\tSessions: %s/%s\n", temp1, temp2);
    print_value(TokenInfo->ulRwSessionCount, temp1, sizeof(temp1), FALSE,
                "%lu");
    print_value(TokenInfo->ulMaxRwSessionCount, temp2, sizeof(temp2), TRUE,
                "%lu");
    printf("\tR/W Sessions: %s/%s\n", temp1, temp2);
    printf("\tPIN Length: %lu-%lu\n", TokenInfo->ulMinPinLen,
           TokenInfo->ulMaxPinLen);
    print_value(TokenInfo->ulFreePublicMemory, temp1, sizeof(temp1), FALSE,
                "0x%lX");
    print_value(TokenInfo->ulTotalPublicMemory, temp2, sizeof(temp2), FALSE,
                "0x%lX");
    printf("\tPublic Memory: %s/%s\n", temp1, temp2);
    print_value(TokenInfo->ulFreePrivateMemory, temp1, sizeof(temp1), FALSE,
                "0x%lX");
    print_value(TokenInfo->ulTotalPrivateMemory, temp2, sizeof(temp2), FALSE,
                "0x%lX");
    printf("\tPrivate Memory: %s/%s\n", temp1, temp2);
    printf("\tHardware Version: %d.%d\n", TokenInfo->hardwareVersion.major,
           TokenInfo->hardwareVersion.minor);
    printf("\tFirmware Version: %d.%d\n", TokenInfo->firmwareVersion.major,
           TokenInfo->firmwareVersion.minor);
    printf("\tTime: %.16s\n", TokenInfo->utcTime);
}

CK_RV display_token_info(int slot_id)
{
    CK_RV rc;                   // Return Code
    CK_TOKEN_INFO TokenInfo;    // Variable to hold Token Information
    unsigned int lcv;           // Loop control variable

    if (slot_id != -1) {
        rc = FunctionPtr->C_GetTokenInfo(slot_id, &TokenInfo);
        if (rc != CKR_OK) {
            printf("Error getting token info: 0x%lX (%s)\n", rc,
                   p11_get_ckr(rc));
            return rc;
        }

        print_token_info(slot_id, &TokenInfo);
        return CKR_OK;
    }

    for (lcv = 0; lcv < SlotCount; lcv++) {
        /* Get the Token info for each slot in the system */
        rc = FunctionPtr->C_GetTokenInfo(SlotList[lcv], &TokenInfo);
        if (rc != CKR_OK) {
            printf("Error getting token info: 0x%lX (%s)\n", rc,
                   p11_get_ckr(rc));
            return rc;
        }

        print_token_info(SlotList[lcv], &TokenInfo);
    }
    return CKR_OK;
}

CK_RV init_token(int slot_id, CK_CHAR_PTR pin)
{
    /* Note this function reinitializes a token to the state it was
     * in just after the initial install
     * It does the following actions (if SO pin is correct):
     *   (1) Purges all Token Objects
     *   (2) Resets SO PIN back to the default
     *   (3) Purges the USER PIN
     *   (4) Sets the Token Label
     */

    CK_RV rc;                   // Return Code
    CK_ULONG pinlen;            // Length of the PIN
    CK_CHAR label[32],          // What we want to set the Label of the card to
             enteredlabel[33];  // Max size of 32 + carriage return;

    /* Find out the size of the entered PIN */
    pinlen = strlen((char *) pin);

    /* Get the token label from the user, NOTE it states to give a unique label
     * but it is never verified as unique.  This is becuase Netscape requires a
     * unique token label; however the PKCS11 spec does not.
     */
    printf("Enter a unique token label: ");
    fflush(stdout);
    memset(enteredlabel, 0, sizeof(enteredlabel));

    if (fgets((char *) enteredlabel, sizeof(enteredlabel), stdin) == NULL)
        printf("\n");
    else
        enteredlabel[strcspn((const char *) enteredlabel, "\n")] = '\0';

    /* First clear the label array. Per PKCS#11 spec, We must PAD this field to
     * 32 bytes, and it should NOT be null-terminated */
    memset(label, ' ', sizeof(label));
    memcpy((char *) label, (char *) enteredlabel,
           strlen((char *) enteredlabel));

    rc = FunctionPtr->C_InitToken(slot_id, pin, pinlen, label);
    if (rc != CKR_OK) {
        if (rc == CKR_PIN_INCORRECT) {
            printf("Incorrect PIN Entered.\n");
            fflush(stdout);
        } else {
            printf("Error initializing token: 0x%lX (%s)\n", rc,
                   p11_get_ckr(rc));
            fflush(stdout);
        }
        return rc;
    }

    return CKR_OK;
}

CK_RV init_user_pin(int slot_id, CK_CHAR_PTR pin, CK_CHAR_PTR sopin)
{
    CK_RV rc;                   // Return Value
    CK_FLAGS flags = 0;         // Mask that we will use when opening the session
    CK_SESSION_HANDLE session_handle;   // The session handle we get
    CK_ULONG pinlen, sopinlen;  // Length of the user and SO PINs

    /* get the length of the PINs */
    pinlen = strlen((char *) pin);
    sopinlen = strlen((char *) sopin);

    /* set the mask we will use for Open Session */
    flags |= CKF_SERIAL_SESSION;
    flags |= CKF_RW_SESSION;

    /* We need to open a read/write session to the adapter to initialize the
     * user PIN. Attempt to do so */
    rc = FunctionPtr->C_OpenSession(slot_id, flags, NULL, NULL,
                                    &session_handle);
    if (rc != CKR_OK) {
        printf("Error opening session: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        fflush(stdout);
        return rc;
    }

    /* After the session is open, we must login as the SO to initialize
     * the PIN */
    rc = FunctionPtr->C_Login(session_handle, CKU_SO, sopin, sopinlen);
    if (rc != CKR_OK) {
        if (rc == CKR_PIN_INCORRECT) {
            printf("Incorrect PIN Entered.\n");
            fflush(stdout);
        } else {
            printf("Error logging in: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
            fflush(stdout);
        }
        return rc;
    }

    /* Call the function to Init the PIN */
    rc = FunctionPtr->C_InitPIN(session_handle, pin, pinlen);
    if (rc != CKR_OK) {
        printf("Error setting PIN: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        fflush(stdout);
    }

    /* Logout so that others can use the PIN */
    rc = FunctionPtr->C_Logout(session_handle);
    if (rc != CKR_OK) {
        printf("Error logging out: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        fflush(stdout);
    }

    /* Close the session */
    rc = FunctionPtr->C_CloseSession(session_handle);
    if (rc != CKR_OK) {
        printf("Error closing session: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        fflush(stdout);
        return rc;
    }
    return CKR_OK;
}

CK_RV set_user_pin(int slot_id, CK_USER_TYPE user, CK_CHAR_PTR oldpin,
                   CK_CHAR_PTR newpin)
{
    CK_RV rc;                   // Return Value
    CK_FLAGS flags = 0;         // Mash ot open the session with
    CK_SESSION_HANDLE session_handle;   // The handle of the session we will open
    CK_ULONG oldpinlen, newpinlen;      // The size of the new and ole PINS

    /* NOTE: This function is used for both the setting of the SO and USER pins,
     *       the CK_USER_TYPE specifes which we are changing. */

    /* Get the size of the PINs */
    oldpinlen = strlen((char *) oldpin);
    newpinlen = strlen((char *) newpin);

    /* set the flags we will open the session with */
    flags |= CKF_SERIAL_SESSION;
    flags |= CKF_RW_SESSION;

    /* Open the Session */
    rc = FunctionPtr->C_OpenSession(slot_id, flags, NULL, NULL,
                                    &session_handle);
    if (rc != CKR_OK) {
        printf("Error opening session: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        fflush(stdout);
        return rc;
    }

    /* Login to the session we just created as the pkcs11 passed in USER type */
    rc = FunctionPtr->C_Login(session_handle, user, oldpin, oldpinlen);
    if (rc != CKR_OK) {
        if (rc == CKR_PIN_INCORRECT) {
            printf("Incorrect PIN Entered.\n");
            fflush(stdout);
        } else {
            printf("Error logging in: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
            fflush(stdout);
        }
        return rc;
    }

    /* set the new PIN */
    rc = FunctionPtr->C_SetPIN(session_handle, oldpin, oldpinlen,
                               newpin, newpinlen);
    if (rc != CKR_OK) {
        printf("Error setting PIN: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        fflush(stdout);
    }

    /* and of course clean up after ourselves */
    rc = FunctionPtr->C_CloseSession(session_handle);
    if (rc != CKR_OK) {
        printf("Error closing session: 0x%lX (%s)\n", rc, p11_get_ckr(rc));
        fflush(stdout);
        return rc;
    }

    return CKR_OK;
}

CK_RV init(void)
{
    CK_RV rc = CKR_OK;          // Return Code
    void (*symPtr) ();          // Pointer for the Dll

    /* Open the PKCS11 API shared library, and inform the user is there is an
     * error */
    /* The host machine should have the right library in the
     * LD_LIBRARY_PATH */
    dllPtr = dlopen("libopencryptoki.so", RTLD_NOW);
    if (!dllPtr) {
        printf("Error loading PKCS#11 library\n");
        printf("dlopen error: %s\n", dlerror());
        fflush(stdout);
        return -1;
    }

    /* Get the list of the PKCS11 functions this token support */
    *(void **)(&symPtr) = dlsym(dllPtr, "C_GetFunctionList");
    if (!symPtr) {
        printf("Error getting function list, symbol not found, error: %s\n",
               strerror(errno));
        fflush(stdout);
        return rc;
    }

    symPtr(&FunctionPtr);

    /* If we get here we know the slot manager is running and we can use PKCS11
     * calls, so we will execute the PKCS11 Initilize command. */
    rc = FunctionPtr->C_Initialize(NULL);
    if (rc != CKR_OK) {
        printf("Error initializing the PKCS11 library: 0x%lX (%s)\n", rc,
               p11_get_ckr(rc));

        if (check_user_and_group() != CKR_OK) {
            printf("Note: all non-root users that require access to PKCS#11 "
                   "tokens using opencryptoki must be assigned to the pkcs11 "
                   "group to be able to communicate with the pkcsslotd "
		   "daemon.\n");
        }

        fflush(stdout);
        cleanup();
    }

    return rc;
}

CK_RV cleanup(void)
{
    CK_RV rc;                   // Return Code

    /* To clean up we will free the slot list we create, call the Finalize
     * routine for PKCS11 and close the dynamically linked library */
    free(SlotList);
    rc = FunctionPtr->C_Finalize(NULL);
    if (dllPtr)
        dlclose(dllPtr);

    exit(rc);
}

void usage(char *progname)
{
    /* If we get here the user needs help, so give it to them */
    printf("usage:\t%s [-itsmIupPh] [-c slotnumber -U userPIN -S SOPin "
           "-n newpin]\n", progname);
    printf("\t-i display PKCS11 info\n");
    printf("\t-t display token info\n");
    printf("\t-s display slot info\n");
    printf("\t-m display mechanism list\n");
    printf("\t-l display slot description\n");
    printf("\t-I initialize token \n");
    printf("\t-u initialize user PIN\n");
    printf("\t-p set the user PIN\n");
    printf("\t-P set the SO PIN\n");
    printf("\t-h show this help\n");

    exit(-1);
}
