#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_WINTYPES_H
#  include <wintypes.h>
#endif
#ifdef HAVE_PCSCLITE_H
#  include <pcsclite.h>
#endif
#ifdef HAVE_WINSCARD_H
#  include <winscard.h>
#endif
#ifdef HAVE_STDINT_H
#  include <stdint.h>
#endif
#ifdef HAVE_INTTYPES_H
#  include <inttypes.h>
#endif
#ifdef HAVE_STDLIB_H
#  include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif
#ifdef HAVE_STRING_H
#  include <string.h>
#endif
#ifdef HAVE_PTHREAD_H
#  include <pthread.h>
#endif
#ifdef HAVE_LIMITS_H
#  include <limits.h>
#endif
#ifdef HAVE_STDIO_H
#  include <stdio.h>
#endif
#define HAVE_ERRNO_H 1
#ifdef HAVE_ERRNO_H
#  include <errno.h>
#endif
#ifdef HAVE_ZLIB_H
#  ifdef HAVE_LIBZ
#    include <zlib.h>
#  endif
#else
#  ifdef HAVE_LIBZ
#    undef HAVE_LIBZ
#  endif
#endif
#ifdef CACKEY_DEBUG_SEARCH_SPEEDTEST
#  include <sys/time.h>
#endif

#define CK_PTR *
#define CK_DEFINE_FUNCTION(returnType, name) returnType name
#define CK_DECLARE_FUNCTION(returnType, name) returnType name
#define CK_DECLARE_FUNCTION_POINTER(returnType, name) returnType (* name)
#define CK_CALLBACK_FUNCTION(returnType, name) returnType (* name)
#ifndef NULL_PTR
#  define NULL_PTR 0
#endif

#include "pkcs11.h"
#include "pkcs11n.h"
#include "asn1-x509.h"
#include "sha.h"
#include "md5.h"

#ifndef CACKEY_CRYPTOKI_VERSION_CODE
#  define CACKEY_CRYPTOKI_VERSION_CODE 0x021e00
#endif

/* GSC-IS v2.1 Definitions */
/** Classes **/
#define GSCIS_CLASS_ISO7816           0x00
#define GSCIS_CLASS_GLOBAL_PLATFORM   0x80

/** Instructions **/
#define GSCIS_INSTR_GET_RESPONSE      0xC0
#define GSCIS_INSTR_READ_BINARY       0xB0
#define GSCIS_INSTR_UPDATE_BINARY     0xD6
#define GSCIS_INSTR_SELECT            0xA4
#define GSCIS_INSTR_EXTERNAL_AUTH     0x82
#define GSCIS_INSTR_GET_CHALLENGE     0x84
#define GSCIS_INSTR_INTERNAL_AUTH     0x88
#define GSCIS_INSTR_VERIFY            0x20
#define GSCIS_INSTR_CHANGE_REFERENCE  0x24
#define GSCIS_INSTR_SIGN              0x2A
#define GSCIS_INSTR_GET_PROP          0x56
#define GSCIS_INSTR_GET_ACR           0x4C
#define GSCIS_INSTR_READ_BUFFER       0x52
#define GSCIS_INSTR_SIGNDECRYPT       0x42

#define GSCIS_PARAM_SELECT_APPLET     0x04

/** Tags **/
/*** CCC Tags ***/
#define GSCIS_TAG_CARDID              0xF0
#define GSCIS_TAG_CCC_VER             0xF1
#define GSCIS_TAG_CCG_VER             0xF2
#define GSCIS_TAG_CARDURL             0xF3
#define GSCIS_TAG_PKCS15              0xF4
#define GSCIS_TAG_REG_DATA_MODEL      0xF5
#define GSCIS_TAG_ACR_TABLE           0xF6
#define GSCIS_TAG_CARD_APDU           0xF7
#define GSCIS_TAG_REDIRECTION         0xFA
#define GSCIS_TAG_CT                  0xFB
#define GSCIS_TAG_ST                  0xFC
#define GSCIS_TAG_NEXTCCC             0xFD

/*** General - EF 2200 ***/
#define GSCIS_TAG_FNAME               0x01
#define GSCIS_TAG_MNAME               0x02
#define GSCIS_TAG_LNAME               0x03
#define GSCIS_TAG_SUFFIX              0x04
#define GSCIS_TAG_GOVT_AGENCY         0x05
#define GSCIS_TAG_BUREAU              0x06
#define GSCIS_TAG_BUREAU_CODE         0x07
#define GSCIS_TAG_DEPT_CODE           0x08
#define GSCIS_TAG_TITLE               0x09
#define GSCIS_TAG_BUILDING            0x10
#define GSCIS_TAG_OFFICE_ADDR1        0x11
#define GSCIS_TAG_OFFICE_ADDR2        0x12
#define GSCIS_TAG_OFFICE_CITY         0x13
#define GSCIS_TAG_OFFICE_STATE        0x14
#define GSCIS_TAG_OFFICE_ZIP          0x15
#define GSCIS_TAG_OFFICE_COUNTRY      0x16
#define GSCIS_TAG_OFFICE_PHONE        0x17
#define GSCIS_TAG_OFFICE_PHONE_EXT    0x18
#define GSCIS_TAG_OFFICE_FAX          0x19
#define GSCIS_TAG_OFFICE_EMAIL        0x1A
#define GSCIS_TAG_OFFICE_ROOM         0x1B
#define GSCIS_TAG_NONGOV_AGENCY       0x1C
#define GSCIS_TAG_SSN_DESIGNATOR      0x1D

/*** PII - EF 2100 ***/
#define GSCIS_TAG_SSN                 0x20
#define GSCIS_TAG_DOB                 0x21
#define GSCIS_TAG_GENDER              0x22

/*** Login Information - EF 4000 ***/
#define GSCIS_TAG_USERID              0x40
#define GSCIS_TAG_DOMAIN              0x41
#define GSCIS_TAG_PASSWORD            0x42

/*** Card Information - EF 5000 ***/
#define GSCIS_TAG_ISSUERID            0x50
#define GSCIS_TAG_SERNO               0x51
#define GSCIS_TAG_ISSUE_DATE          0x52
#define GSCIS_TAG_EXPIRE_DATE         0x53
#define GSCIS_TAG_CARD_TYPE           0x54
#define GSCIS_TAG_SECURITY_CODE       0x57
#define GSCIS_TAG_CARDID_AID          0x58

/*** PIV Codes ***/
#define NISTSP800_73_3_INSTR_GET_DATA 0xCB
#define NISTSP800_73_3_INSTR_GENAUTH  0x87

/*** PKI Information - EF 7000 ***/
#define GSCIS_TAG_CERTIFICATE         0x70
#define GSCIS_TAG_CERT_ISSUE_DATE     0x71
#define GSCIS_TAG_CERT_EXPIRE_DATE    0x72

/** Applet IDs **/
#define GSCIS_AID_CCC                 0xA0, 0x00, 0x00, 0x01, 0x16, 0xDB, 0x00
#define GSCIS_AID_ID0                 0xA0, 0x00, 0x00, 0x00, 0x79, 0x01, 0x00
#define NISTSP800_73_3_PIV_AID        0xA0, 0x00, 0x00, 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00

/* PIV IDs */
/** Key Identifiers (NIST SP 800-78-3, Table 6-1 **/
#define NISTSP800_78_3_KEY_PIVAUTH   0x9A
#define NISTSP800_78_3_KEY_SIGNATURE 0x9C
#define NISTSP800_78_3_KEY_KEYMGT    0x9D
#define NISTSP800_78_3_KEY_CARDAUTH  0x9E

/** Algorithm Identifiers (NIST SP 800-78-3, Table 6-2 **/
#define NISTSP800_78_3_ALGO_RSA1024  0x06
#define NISTSP800_78_3_ALGO_RSA2048  0x07

/** Object Identifiers (NIST SP 800-73-3 Part 1, Table 2) **/
#define NISTSP800_73_3_OID_PIVAUTH   0x5F, 0xC1, 0x05
#define NISTSP800_73_3_OID_SIGNATURE 0x5F, 0xC1, 0x0A
#define NISTSP800_73_3_OID_KEYMGT    0x5F, 0xC1, 0x0B
#define NISTSP800_73_3_OID_CARDAUTH  0x5F, 0xC1, 0x01

/* Maximum size of data portion of APDUs */
/** Do not set this above 250 **/
#define CACKEY_APDU_MTU               250

/* ATR If not available */
#ifndef MAX_ATR_SIZE
#define MAX_ATR_SIZE 1024
#endif

#ifdef CACKEY_DEBUG
#  define CACKEY_DEBUG_MIN(a, b) ((a) < (b) ? (a) : (b))
#  ifdef HAVE_TIME_H
#    include <time.h>
static time_t cackey_debug_start_time = 0;
static unsigned long CACKEY_DEBUG_GETTIME(void) {
	if (cackey_debug_start_time == 0) {
		cackey_debug_start_time = time(NULL);
	}

	return(time(NULL) - cackey_debug_start_time);
}
#  else
static unsigned long CACKEY_DEBUG_GETTIME(void) {
	return(0);
}
#  endif

#  define CACKEY_DEBUG_PRINTF(x...) { \
	static char buf_user[4096] = {0}; \
	snprintf(buf_user, sizeof(buf_user), x); \
	buf_user[sizeof(buf_user) - 1] = '\0'; \
	fprintf(cackey_debug_fd(), "[%lu]: %s():%i: %s\n", CACKEY_DEBUG_GETTIME(), __func__, __LINE__, buf_user); \
	fflush(cackey_debug_fd()); \
}
#  define CACKEY_DEBUG_PRINTBUF(f, x, y) { \
	static char buf_user[8192] = {0}, *buf_user_p, *buf_user_print; \
	unsigned long buf_user_size; \
	unsigned char *TMPBUF; \
	unsigned long idx; \
	int snprintf_ret; \
	TMPBUF = (unsigned char *) (x); \
	buf_user[0] = 0; \
	buf_user[2] = 0; \
	buf_user_p = buf_user; \
	buf_user_size = sizeof(buf_user); \
	for (idx = 0; idx < CACKEY_DEBUG_MIN((y), sizeof(buf_user)); idx++) { \
		if (buf_user_size <= 0) { \
			break; \
		}; \
		snprintf_ret = snprintf(buf_user_p, buf_user_size, ", %02x", TMPBUF[idx]); \
		if (snprintf_ret <= 0) { \
			break; \
		}; \
		buf_user_p += snprintf_ret; \
		buf_user_size -= snprintf_ret; \
	}; \
	buf_user[sizeof(buf_user) - 1] = '\0'; \
	buf_user_print = buf_user + 2; \
	fprintf(cackey_debug_fd(), "[%lu]: %s():%i: %s  (%s/%lu = {%s})\n", CACKEY_DEBUG_GETTIME(), __func__, __LINE__, f, #x, (unsigned long) (y), buf_user_print); \
	fflush(cackey_debug_fd()); \
}
#  define free(x) { CACKEY_DEBUG_PRINTF("FREE(%p) (%s)", (void *) x, #x); free(x); }

static FILE *cackey_debug_fd(void) {
	static FILE *fd = NULL;
	char *logfile;

	if (fd != NULL) {
		return(fd);
	}

	/*
	 * Log to stderr initially so we can use debugging within
	 * this function without getting into an infinite loop
	 */
	fd = stderr;

	logfile = getenv("CACKEY_DEBUG_LOGFILE");
	if (logfile != NULL) {
		CACKEY_DEBUG_PRINTF("Found environment variable: %s", logfile);

		logfile = strchr(logfile, '=');
		if (logfile == NULL) {
			logfile = getenv("CACKEY_DEBUG_LOGFILE");
		} else {
			logfile++;
		}
	}

#ifdef CACKEY_DEBUG_LOGFILE
	if (logfile == NULL) {
		logfile = CACKEY_DEBUG_LOGFILE;
	}
#endif

	if (logfile != NULL) {
		CACKEY_DEBUG_PRINTF("Found log file: %s", logfile);

		fd = fopen(logfile, "a");
	}

	if (fd == NULL) {
		fd = stderr;
	}

	if (fd == stderr) {
		CACKEY_DEBUG_PRINTF("Returning stderr");
	} else {
		CACKEY_DEBUG_PRINTF("Returning %p", (void *) fd);
	}

	return(fd);
}

static void *CACKEY_DEBUG_FUNC_MALLOC(size_t size, const char *func, int line) {
	void *retval;

	retval = malloc(size);

	fprintf(cackey_debug_fd(), "[%lu]: %s():%i: MALLOC() = %p\n", CACKEY_DEBUG_GETTIME(), func, line, retval);
	fflush(cackey_debug_fd());

	return(retval);
}

static void *CACKEY_DEBUG_FUNC_REALLOC(void *ptr, size_t size, const char *func, int line) {
	void *retval;

	retval = realloc(ptr, size);

	if (retval != ptr) {
		fprintf(cackey_debug_fd(), "[%lu]: %s():%i: REALLOC(%p) = %p\n", CACKEY_DEBUG_GETTIME(), func, line, ptr, retval);
		fflush(cackey_debug_fd());
	}

	if (retval == NULL) {
		CACKEY_DEBUG_PRINTF(" *** ERROR *** realloc returned NULL (size = %lu)", (unsigned long) size);
	}

	return(retval);
}

static char *CACKEY_DEBUG_FUNC_STRDUP(const char *ptr, const char *func, int line) {
	char *retval;

	retval = strdup(ptr);

	fprintf(cackey_debug_fd(), "[%lu]: %s():%i: STRDUP_MALLOC() = %p\n", CACKEY_DEBUG_GETTIME(), func, line, retval);
	fflush(cackey_debug_fd());

	return(retval);
}

static const char *CACKEY_DEBUG_FUNC_TAG_TO_STR(unsigned char tag) {
	switch (tag) {
		case GSCIS_TAG_CARDID:
			return("GSCIS_TAG_CARDID");
		case GSCIS_TAG_CCC_VER:
			return("GSCIS_TAG_CCC_VER");
		case GSCIS_TAG_CCG_VER:
			return("GSCIS_TAG_CCG_VER");
		case GSCIS_TAG_CARDURL:
			return("GSCIS_TAG_CARDURL");
		case GSCIS_TAG_PKCS15:
			return("GSCIS_TAG_PKCS15");
		case GSCIS_TAG_REG_DATA_MODEL:
			return("GSCIS_TAG_REG_DATA_MODEL");
		case GSCIS_TAG_ACR_TABLE:
			return("GSCIS_TAG_ACR_TABLE");
		case GSCIS_TAG_CARD_APDU:
			return("GSCIS_TAG_CARD_APDU");
		case GSCIS_TAG_REDIRECTION:
			return("GSCIS_TAG_REDIRECTION");
		case GSCIS_TAG_CT:
			return("GSCIS_TAG_CT");
		case GSCIS_TAG_ST:
			return("GSCIS_TAG_ST");
		case GSCIS_TAG_NEXTCCC:
			return("GSCIS_TAG_NEXTCCC");
		case GSCIS_TAG_FNAME:
			return("GSCIS_TAG_FNAME");
		case GSCIS_TAG_MNAME:
			return("GSCIS_TAG_MNAME");
		case GSCIS_TAG_LNAME:
			return("GSCIS_TAG_LNAME");
		case GSCIS_TAG_SUFFIX:
			return("GSCIS_TAG_SUFFIX");
		case GSCIS_TAG_GOVT_AGENCY:
			return("GSCIS_TAG_GOVT_AGENCY");
		case GSCIS_TAG_BUREAU:
			return("GSCIS_TAG_BUREAU");
		case GSCIS_TAG_BUREAU_CODE:
			return("GSCIS_TAG_BUREAU_CODE");
		case GSCIS_TAG_DEPT_CODE:
			return("GSCIS_TAG_DEPT_CODE");
		case GSCIS_TAG_TITLE:
			return("GSCIS_TAG_TITLE");
		case GSCIS_TAG_BUILDING:
			return("GSCIS_TAG_BUILDING");
		case GSCIS_TAG_OFFICE_ADDR1:
			return("GSCIS_TAG_OFFICE_ADDR1");
		case GSCIS_TAG_OFFICE_ADDR2:
			return("GSCIS_TAG_OFFICE_ADDR2");
		case GSCIS_TAG_OFFICE_CITY:
			return("GSCIS_TAG_OFFICE_CITY");
		case GSCIS_TAG_OFFICE_STATE:
			return("GSCIS_TAG_OFFICE_STATE");
		case GSCIS_TAG_OFFICE_ZIP:
			return("GSCIS_TAG_OFFICE_ZIP");
		case GSCIS_TAG_OFFICE_COUNTRY:
			return("GSCIS_TAG_OFFICE_COUNTRY");
		case GSCIS_TAG_OFFICE_PHONE:
			return("GSCIS_TAG_OFFICE_PHONE");
		case GSCIS_TAG_OFFICE_PHONE_EXT:
			return("GSCIS_TAG_OFFICE_PHONE_EXT");
		case GSCIS_TAG_OFFICE_FAX:
			return("GSCIS_TAG_OFFICE_FAX");
		case GSCIS_TAG_OFFICE_EMAIL:
			return("GSCIS_TAG_OFFICE_EMAIL");
		case GSCIS_TAG_OFFICE_ROOM:
			return("GSCIS_TAG_OFFICE_ROOM");
		case GSCIS_TAG_NONGOV_AGENCY:
			return("GSCIS_TAG_NONGOV_AGENCY");
		case GSCIS_TAG_SSN_DESIGNATOR:
			return("GSCIS_TAG_SSN_DESIGNATOR");
		case GSCIS_TAG_SSN:
			return("GSCIS_TAG_SSN");
		case GSCIS_TAG_DOB:
			return("GSCIS_TAG_DOB");
		case GSCIS_TAG_GENDER:
			return("GSCIS_TAG_GENDER");
		case GSCIS_TAG_USERID:
			return("GSCIS_TAG_USERID");
		case GSCIS_TAG_DOMAIN:
			return("GSCIS_TAG_DOMAIN");
		case GSCIS_TAG_PASSWORD:
			return("GSCIS_TAG_PASSWORD");
		case GSCIS_TAG_ISSUERID:
			return("GSCIS_TAG_ISSUERID");
		case GSCIS_TAG_SERNO:
			return("GSCIS_TAG_SERNO");
		case GSCIS_TAG_ISSUE_DATE:
			return("GSCIS_TAG_ISSUE_DATE");
		case GSCIS_TAG_EXPIRE_DATE:
			return("GSCIS_TAG_EXPIRE_DATE");
		case GSCIS_TAG_CARD_TYPE:
			return("GSCIS_TAG_CARD_TYPE");
		case GSCIS_TAG_SECURITY_CODE:
			return("GSCIS_TAG_SECURITY_CODE");
		case GSCIS_TAG_CARDID_AID:
			return("GSCIS_TAG_CARDID_AID");
		case GSCIS_TAG_CERTIFICATE:
			return("GSCIS_TAG_CERTIFICATE");
		case GSCIS_TAG_CERT_ISSUE_DATE:
			return("GSCIS_TAG_CERT_ISSUE_DATE");
		case GSCIS_TAG_CERT_EXPIRE_DATE:
			return("GSCIS_TAG_CERT_EXPIRE_DATE");
	}

	return("UNKNOWN");
}

static const char *CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(LONG retcode) {
	switch (retcode) {
		case SCARD_S_SUCCESS:
			return("SCARD_S_SUCCESS");
		case SCARD_E_CANCELLED:
			return("SCARD_E_CANCELLED");
		case SCARD_E_CANT_DISPOSE:
			return("SCARD_E_CANT_DISPOSE");
		case SCARD_E_INSUFFICIENT_BUFFER:
			return("SCARD_E_INSUFFICIENT_BUFFER");
		case SCARD_E_INVALID_ATR:
			return("SCARD_E_INVALID_ATR");
		case SCARD_E_INVALID_HANDLE:
			return("SCARD_E_INVALID_HANDLE");
		case SCARD_E_INVALID_PARAMETER:
			return("SCARD_E_INVALID_PARAMETER");
		case SCARD_E_INVALID_TARGET:
			return("SCARD_E_INVALID_TARGET");
		case SCARD_E_INVALID_VALUE:
			return("SCARD_E_INVALID_VALUE");
		case SCARD_E_NO_MEMORY:
			return("SCARD_E_NO_MEMORY");
		case SCARD_E_UNKNOWN_READER:
			return("SCARD_E_UNKNOWN_READER");
		case SCARD_E_TIMEOUT:
			return("SCARD_E_TIMEOUT");
		case SCARD_E_SHARING_VIOLATION:
			return("SCARD_E_SHARING_VIOLATION");
		case SCARD_E_NO_SMARTCARD:
			return("SCARD_E_NO_SMARTCARD");
		case SCARD_E_UNKNOWN_CARD:
			return("SCARD_E_UNKNOWN_CARD");
		case SCARD_E_PROTO_MISMATCH:
			return("SCARD_E_PROTO_MISMATCH");
		case SCARD_E_NOT_READY:
			return("SCARD_E_NOT_READY");
		case SCARD_E_SYSTEM_CANCELLED:
			return("SCARD_E_SYSTEM_CANCELLED");
		case SCARD_E_NOT_TRANSACTED:
			return("SCARD_E_NOT_TRANSACTED");
		case SCARD_E_READER_UNAVAILABLE:
			return("SCARD_E_READER_UNAVAILABLE");
		case SCARD_W_UNSUPPORTED_CARD:
			return("SCARD_W_UNSUPPORTED_CARD");
		case SCARD_W_UNRESPONSIVE_CARD:
			return("SCARD_W_UNRESPONSIVE_CARD");
		case SCARD_W_UNPOWERED_CARD:
			return("SCARD_W_UNPOWERED_CARD");
		case SCARD_W_RESET_CARD:
			return("SCARD_W_RESET_CARD");
		case SCARD_W_REMOVED_CARD:
			return("SCARD_W_REMOVED_CARD");
		case SCARD_E_PCI_TOO_SMALL:
			return("SCARD_E_PCI_TOO_SMALL");
		case SCARD_E_READER_UNSUPPORTED:
			return("SCARD_E_READER_UNSUPPORTED");
		case SCARD_E_DUPLICATE_READER:
			return("SCARD_E_DUPLICATE_READER");
		case SCARD_E_CARD_UNSUPPORTED:
			return("SCARD_E_CARD_UNSUPPORTED");
		case SCARD_E_NO_SERVICE:
			return("SCARD_E_NO_SERVICE");
		case SCARD_E_SERVICE_STOPPED:
			return("SCARD_E_SERVICE_STOPPED");
		case SCARD_E_UNSUPPORTED_FEATURE:
			return("SCARD_E_UNSUPPORTED_FEATURE");
#ifdef SCARD_W_INSERTED_CARD
		case SCARD_W_INSERTED_CARD:
			return("SCARD_W_INSERTED_CARD");
#endif
#ifdef SCARD_E_NO_READERS_AVAILABLE
		case SCARD_E_NO_READERS_AVAILABLE:
			return("SCARD_E_NO_READERS_AVAILABLE");
#endif
	}

	return("UNKNOWN");
}

static const char *CACKEY_DEBUG_FUNC_OBJID_TO_STR(uint16_t objid) {
	switch (objid) {
		case 0x2000:
			return("CACKEY_TLV_OBJID_GENERALINFO");
		case 0x2100:
			return("CACKEY_TLV_OBJID_PROPERSONALINFO");
		case 0x3000:
			return("CACKEY_TLV_OBJID_ACCESSCONTROL");
		case 0x4000:
			return("CACKEY_TLV_OBJID_LOGIN");
		case 0x5000:
			return("CACKEY_TLV_OBJID_CARDINFO");
		case 0x6000:
			return("CACKEY_TLV_OBJID_BIOMETRICS");
		case 0x7000:
			return("CACKEY_TLV_OBJID_DIGITALSIGCERT");
		case 0x0200:
			return("CACKEY_TLV_OBJID_CAC_PERSON");
		case 0x0202:
			return("CACKEY_TLV_OBJID_CAC_BENEFITS");
		case 0x0203:
			return("CACKEY_TLV_OBJID_CAC_OTHERBENEFITS");
		case 0x0201:
			return("CACKEY_TLV_OBJID_CAC_PERSONNEL");
		case 0x02FE:
			return("CACKEY_TLV_OBJID_CAC_PKICERT");
	}
	
	return("UNKNOWN");
}

static const char *CACKEY_DEBUG_FUNC_APPTYPE_TO_STR(uint8_t apptype) {
	switch (apptype) {
		case 0x00:
			return("NONE");
		case 0x01:
			return("CACKEY_TLV_APP_GENERIC");
		case 0x02:
			return("CACKEY_TLV_APP_SKI");
		case 0x03:
			return("CACKEY_TLV_APP_GENERIC | CACKEY_TLV_APP_SKI");
		case 0x04:
			return("CACKEY_TLV_APP_PKI");
		case 0x05:
			return("CACKEY_TLV_APP_GENERIC | CACKEY_TLV_APP_PKI");
		case 0x06:
			return("CACKEY_TLV_APP_SKI | CACKEY_TLV_APP_PKI");
		case 0x07:
			return("CACKEY_TLV_APP_GENERIC | CACKEY_TLV_APP_SKI | CACKEY_TLV_APP_PKI");
	}

	return("INVALID");
}

static const char *CACKEY_DEBUG_FUNC_ATTRIBUTE_TO_STR(CK_ATTRIBUTE_TYPE attr) {
	switch (attr) {
		case CKA_CLASS:
			return("CKA_CLASS");
		case CKA_TOKEN:
			return("CKA_TOKEN");
		case CKA_PRIVATE:
			return("CKA_PRIVATE");
		case CKA_LABEL:
			return("CKA_LABEL");
		case CKA_APPLICATION:
			return("CKA_APPLICATION");
		case CKA_VALUE:
			return("CKA_VALUE");
		case CKA_OBJECT_ID:
			return("CKA_OBJECT_ID");
		case CKA_CERTIFICATE_TYPE:
			return("CKA_CERTIFICATE_TYPE");
		case CKA_ISSUER:
			return("CKA_ISSUER");
		case CKA_SERIAL_NUMBER:
			return("CKA_SERIAL_NUMBER");
		case CKA_AC_ISSUER:
			return("CKA_AC_ISSUER");
		case CKA_OWNER:
			return("CKA_OWNER");
		case CKA_ATTR_TYPES:
			return("CKA_ATTR_TYPES");
		case CKA_TRUSTED:
			return("CKA_TRUSTED");
		case CKA_KEY_TYPE:
			return("CKA_KEY_TYPE");
		case CKA_SUBJECT:
			return("CKA_SUBJECT");
		case CKA_ID:
			return("CKA_ID");
		case CKA_SENSITIVE:
			return("CKA_SENSITIVE");
		case CKA_ENCRYPT:
			return("CKA_ENCRYPT");
		case CKA_DECRYPT:
			return("CKA_DECRYPT");
		case CKA_WRAP:
			return("CKA_WRAP");
		case CKA_UNWRAP:
			return("CKA_UNWRAP");
		case CKA_SIGN:
			return("CKA_SIGN");
		case CKA_SIGN_RECOVER:
			return("CKA_SIGN_RECOVER");
		case CKA_VERIFY:
			return("CKA_VERIFY");
		case CKA_VERIFY_RECOVER:
			return("CKA_VERIFY_RECOVER");
		case CKA_DERIVE:
			return("CKA_DERIVE");
		case CKA_START_DATE:
			return("CKA_START_DATE");
		case CKA_END_DATE:
			return("CKA_END_DATE");
		case CKA_MODULUS:
			return("CKA_MODULUS");
		case CKA_MODULUS_BITS:
			return("CKA_MODULUS_BITS");
		case CKA_PUBLIC_EXPONENT:
			return("CKA_PUBLIC_EXPONENT");
		case CKA_PRIVATE_EXPONENT:
			return("CKA_PRIVATE_EXPONENT");
		case CKA_PRIME_1:
			return("CKA_PRIME_1");
		case CKA_PRIME_2:
			return("CKA_PRIME_2");
		case CKA_EXPONENT_1:
			return("CKA_EXPONENT_1");
		case CKA_EXPONENT_2:
			return("CKA_EXPONENT_2");
		case CKA_COEFFICIENT:
			return("CKA_COEFFICIENT");
		case CKA_PRIME:
			return("CKA_PRIME");
		case CKA_SUBPRIME:
			return("CKA_SUBPRIME");
		case CKA_BASE:
			return("CKA_BASE");
		case CKA_PRIME_BITS:
			return("CKA_PRIME_BITS");
		case CKA_SUB_PRIME_BITS:
			return("CKA_SUB_PRIME_BITS");
		case CKA_VALUE_BITS:
			return("CKA_VALUE_BITS");
		case CKA_VALUE_LEN:
			return("CKA_VALUE_LEN");
		case CKA_EXTRACTABLE:
			return("CKA_EXTRACTABLE");
		case CKA_LOCAL:
			return("CKA_LOCAL");
		case CKA_NEVER_EXTRACTABLE:
			return("CKA_NEVER_EXTRACTABLE");
		case CKA_ALWAYS_SENSITIVE:
			return("CKA_ALWAYS_SENSITIVE");
		case CKA_KEY_GEN_MECHANISM:
			return("CKA_KEY_GEN_MECHANISM");
		case CKA_MODIFIABLE:
			return("CKA_MODIFIABLE");
		case CKA_ECDSA_PARAMS:
			return("CKA_ECDSA_PARAMS");
		case CKA_EC_POINT:
			return("CKA_EC_POINT");
		case CKA_SECONDARY_AUTH:
			return("CKA_SECONDARY_AUTH");
		case CKA_AUTH_PIN_FLAGS:
			return("CKA_AUTH_PIN_FLAGS");
		case CKA_HW_FEATURE_TYPE:
			return("CKA_HW_FEATURE_TYPE");
		case CKA_RESET_ON_INIT:
			return("CKA_RESET_ON_INIT");
		case CKA_HAS_RESET:
			return("CKA_HAS_RESET");
		case CKA_VENDOR_DEFINED:
			return("CKA_VENDOR_DEFINED");
	}

	return("UNKNOWN");
}

#  define malloc(x) CACKEY_DEBUG_FUNC_MALLOC(x, __func__, __LINE__)
#  define realloc(x, y) CACKEY_DEBUG_FUNC_REALLOC(x, y, __func__, __LINE__)
#  ifdef strdup
#    undef strdup
#  endif
#  define strdup(x) CACKEY_DEBUG_FUNC_STRDUP(x, __func__, __LINE__)
#else
#  define CACKEY_DEBUG_PRINTF(x...) /**/
#  define CACKEY_DEBUG_PRINTBUF(f, x, y) /**/
#  define CACKEY_DEBUG_FUNC_TAG_TO_STR(x) "DEBUG_DISABLED"
#  define CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(x) "DEBUG_DISABLED"
#  define CACKEY_DEBUG_FUNC_OBJID_TO_STR(x) "DEBUG_DISABLED"
#  define CACKEY_DEBUG_FUNC_APPTYPE_TO_STR(x) "DEBUG_DISABLED"
#  define CACKEY_DEBUG_FUNC_ATTRIBUTE_TO_STR(x) "DEBUG_DISABLED"
#endif

/*
 * Include these source files in this translation unit so that we can bind to
 * functions and not include any symbols in the output shared object.
 */
#include "asn1-x509.c"
#include "sha1.c"
#include "md5.c"

typedef enum {
	CACKEY_ID_TYPE_ERROR,
	CACKEY_ID_TYPE_UNKNOWN,
	CACKEY_ID_TYPE_CAC,
	CACKEY_ID_TYPE_PIV,
	CACKEY_ID_TYPE_CERT_ONLY
} cackey_pcsc_id_type;

struct cackey_pcsc_identity {
	cackey_pcsc_id_type id_type;

	size_t certificate_len;
	unsigned char *certificate;

	ssize_t keysize;

	union {
		struct {
			unsigned char applet[7];
			uint16_t file;
		} cac;

		struct {
			unsigned char key_id;
			char label[32];
		} piv;
	} card;
};

struct cackey_identity {
	struct cackey_pcsc_identity *pcsc_identity;

	CK_ATTRIBUTE *attributes;
	CK_ULONG attributes_count;
};

struct cackey_session {
	int active;

	CK_SLOT_ID slotID;

	CK_STATE state;
	CK_FLAGS flags;
	CK_ULONG ulDeviceError;
	CK_VOID_PTR pApplication;
	CK_NOTIFY Notify;

	struct cackey_identity *identities;
	unsigned long identities_count;

	int search_active;
	CK_ATTRIBUTE_PTR search_query;
	CK_ULONG search_query_count;
	unsigned long search_curr_id;

	int sign_active;
	CK_MECHANISM_TYPE sign_mechanism;
	CK_BYTE_PTR sign_buf;
	unsigned long sign_buflen;
	unsigned long sign_bufused;
	struct cackey_identity *sign_identity;

	int decrypt_active;
	CK_MECHANISM_TYPE decrypt_mechanism;
	CK_VOID_PTR decrypt_mech_parm;
	CK_ULONG decrypt_mech_parmlen;
	struct cackey_identity *decrypt_identity;
};

struct cackey_slot {
	int active;
	int internal;

	char *pcsc_reader;

	int pcsc_card_connected;
	SCARDHANDLE pcsc_card;

	int transaction_depth;
	int transaction_need_hw_lock;

	int slot_reset;

	CK_FLAGS token_flags;

	unsigned char *label;

	DWORD protocol;

	unsigned int cached_certs_count;
	struct cackey_pcsc_identity *cached_certs;

	cackey_pcsc_id_type id_type_hint;
};

typedef enum {
	CACKEY_TLV_APP_GENERIC = 0x01,
	CACKEY_TLV_APP_SKI     = 0x02,
	CACKEY_TLV_APP_PKI     = 0x04
} cackey_tlv_apptype;

typedef enum {
	CACKEY_TLV_OBJID_GENERALINFO       = 0x2000,
	CACKEY_TLV_OBJID_PROPERSONALINFO   = 0x2100,
	CACKEY_TLV_OBJID_ACCESSCONTROL     = 0x3000,
	CACKEY_TLV_OBJID_LOGIN             = 0x4000,
	CACKEY_TLV_OBJID_CARDINFO          = 0x5000,
	CACKEY_TLV_OBJID_BIOMETRICS        = 0x6000,
	CACKEY_TLV_OBJID_DIGITALSIGCERT    = 0x7000,
	CACKEY_TLV_OBJID_CAC_PERSON        = 0x0200,
	CACKEY_TLV_OBJID_CAC_BENEFITS      = 0x0202,
	CACKEY_TLV_OBJID_CAC_OTHERBENEFITS = 0x0203,
	CACKEY_TLV_OBJID_CAC_PERSONNEL     = 0x0201,
	CACKEY_TLV_OBJID_CAC_PKICERT       = 0x02FE
} cackey_tlv_objectid;

typedef enum {
	CACKEY_PCSC_S_TOKENPRESENT    = 1,
	CACKEY_PCSC_S_OK              = 0,
	CACKEY_PCSC_E_GENERIC         = -1,
	CACKEY_PCSC_E_BADPIN          = -2,
	CACKEY_PCSC_E_LOCKED          = -3,
	CACKEY_PCSC_E_NEEDLOGIN       = -4,
	CACKEY_PCSC_E_TOKENABSENT     = -6,
	CACKEY_PCSC_E_RETRY           = -7,
	CACKEY_PCSC_E_NODATA          = -8
} cackey_ret;

struct cackey_tlv_cardurl {
	unsigned char        rid[5];
	cackey_tlv_apptype   apptype;
	cackey_tlv_objectid  objectid;
	cackey_tlv_objectid  appid;
	unsigned char        pinid;
};

struct cackey_tlv_entity;
struct cackey_tlv_entity {
	uint8_t tag;
	size_t length;

	union {
		void *value;
		struct cackey_tlv_cardurl *value_cardurl;
		uint8_t value_byte;
	};

	struct cackey_tlv_entity *_next;
};

/* CACKEY Global Handles */
static void *cackey_biglock = NULL;
static struct cackey_session cackey_sessions[128];
static struct cackey_slot cackey_slots[128];
static int cackey_initialized = 0;
static int cackey_biglock_init = 0;
CK_C_INITIALIZE_ARGS cackey_args;

/** Extra certificates to include in token **/
struct cackey_pcsc_identity extra_certs[] = {
#include "cackey_builtin_certs.h"
};

#define CACKEY_MACRO_DEFAULT_XSTR(str) CACKEY_MACRO_DEFAULT_STR(str)
#define CACKEY_MACRO_DEFAULT_STR(str) #str

/* Protected Authentication Path command */
static char *cackey_pin_command = NULL;

/* Reader Exclusion or Include-only */
static char *cackey_readers_include_only = NULL;
static char *cackey_readers_exclude = NULL;

/* PCSC Global Handles */
static LPSCARDCONTEXT cackey_pcsc_handle = NULL;

static unsigned long cackey_getversion(void) {
	static unsigned long retval = 255;
	unsigned long major = 0;
	unsigned long minor = 0;
	char *major_str = NULL;
	char *minor_str = NULL;

	CACKEY_DEBUG_PRINTF("Called.");

	if (retval != 255) {
		CACKEY_DEBUG_PRINTF("Returning 0x%lx (cached).", retval);

		return(retval);
	}

	retval = 0;

#ifdef PACKAGE_VERSION
        major_str = PACKAGE_VERSION;
	if (major_str) {
	        major = strtoul(major_str, &minor_str, 10);

		if (minor_str) {
			minor = strtoul(minor_str + 1, NULL, 10);
		}
	}

	retval = (major << 16) | (minor << 8);
#endif

	CACKEY_DEBUG_PRINTF("Returning 0x%lx", retval);

	return(retval);
}

/* PC/SC Related Functions */
/*
 * SYNPOSIS
 *     void cackey_slots_disconnect_all(int unitialize_all_readers);
 *
 * ARGUMENTS
 *     int unitialize_all_readers      Free the "pcsc_reader" object associated with
 *                           each slot (boolean)
 *
 * RETURN VALUE
 *     None
 *
 * NOTES
 *     This function disconnects from all cards.
 *
 */
static void cackey_slots_disconnect_all(int unitialize_all_readers) {
	uint32_t idx;

	CACKEY_DEBUG_PRINTF("Called.");

	for (idx = 0; idx < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); idx++) {
		if (cackey_slots[idx].internal) {
			/* Skip internal slots */
			continue;
		}

		if (cackey_slots[idx].pcsc_card_connected) {
			CACKEY_DEBUG_PRINTF("SCardDisconnect(%lu) called", (unsigned long) idx);

			SCardDisconnect(cackey_slots[idx].pcsc_card, SCARD_LEAVE_CARD);
		}

		if (cackey_slots[idx].label) {
			free(cackey_slots[idx].label);

			cackey_slots[idx].label = NULL;
		}

		if (unitialize_all_readers || !cackey_slots[idx].active) {
			if (cackey_slots[idx].pcsc_reader) {
				free(cackey_slots[idx].pcsc_reader);

				cackey_slots[idx].pcsc_reader = NULL;
			}

			cackey_slots[idx].transaction_need_hw_lock = 0;
			cackey_slots[idx].transaction_depth = 0;
			cackey_slots[idx].id_type_hint = CACKEY_ID_TYPE_UNKNOWN;
		} else {
			if (cackey_slots[idx].transaction_depth > 0) {
				cackey_slots[idx].transaction_need_hw_lock = 1;
			}
		}

		cackey_slots[idx].pcsc_card_connected = 0;

		if (cackey_slots[idx].active) {
			CACKEY_DEBUG_PRINTF("Marking active slot %lu as being reset", (unsigned long) idx);

			cackey_slots[idx].slot_reset = 1;
		}
	}

	CACKEY_DEBUG_PRINTF("Returning");

	return;
}

/*
 * SYNPOSIS
 *     cackey_ret cackey_pcsc_connect(void);
 *
 * ARGUMENTS
 *     None
 *
 * RETURN VALUE
 *     CACKEY_PCSC_S_OK         On success
 *     CACKEY_PCSC_E_GENERIC    On error
 *
 * NOTES
 *     This function connects to the PC/SC Connection Manager and updates the
 *     global handle.
 *
 */
static cackey_ret cackey_pcsc_connect(void) {
	LONG scard_est_context_ret;
#ifdef HAVE_SCARDISVALIDCONTEXT
	LONG scard_isvalid_ret;
#endif

	CACKEY_DEBUG_PRINTF("Called.");

	if (cackey_pcsc_handle == NULL) {
		cackey_pcsc_handle = malloc(sizeof(*cackey_pcsc_handle));
		if (cackey_pcsc_handle == NULL) {
			CACKEY_DEBUG_PRINTF("Call to malloc() failed, returning in failure");

			cackey_slots_disconnect_all(0);

			return(CACKEY_PCSC_E_GENERIC);
		}

		CACKEY_DEBUG_PRINTF("SCardEstablishContext() called");
		scard_est_context_ret = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, cackey_pcsc_handle);
		if (scard_est_context_ret != SCARD_S_SUCCESS) {
			CACKEY_DEBUG_PRINTF("Call to SCardEstablishContext failed (returned %s/%li), returning in failure", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_est_context_ret), (long) scard_est_context_ret);

			free(cackey_pcsc_handle);
			cackey_pcsc_handle = NULL;

			cackey_slots_disconnect_all(0);

			return(CACKEY_PCSC_E_GENERIC);
		}
	}

#ifdef HAVE_SCARDISVALIDCONTEXT
	CACKEY_DEBUG_PRINTF("SCardIsValidContext() called");
	scard_isvalid_ret = SCardIsValidContext(*cackey_pcsc_handle);
	if (scard_isvalid_ret != SCARD_S_SUCCESS) {
		CACKEY_DEBUG_PRINTF("Handle has become invalid (SCardIsValidContext = %s/%li), trying to re-establish...", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_isvalid_ret), (long) scard_isvalid_ret);

		CACKEY_DEBUG_PRINTF("SCardEstablishContext() called");
		scard_est_context_ret = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, cackey_pcsc_handle);
		if (scard_est_context_ret != SCARD_S_SUCCESS) {
			CACKEY_DEBUG_PRINTF("Call to SCardEstablishContext failed (returned %s/%li), returning in failure", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_est_context_ret), (long) scard_est_context_ret);

			free(cackey_pcsc_handle);
			cackey_pcsc_handle = NULL;

			cackey_slots_disconnect_all(0);

			return(CACKEY_PCSC_E_GENERIC);
		}

		CACKEY_DEBUG_PRINTF("Handle has been re-established");
	}
#endif

	CACKEY_DEBUG_PRINTF("Sucessfully connected to PC/SC, returning in success");

	return(CACKEY_PCSC_S_OK);
}

/*
 * SYNPOSIS
 *     cackey_ret cackey_pcsc_disconnect(void);
 *
 * ARGUMENTS
 *     None
 *
 * RETURN VALUE
 *     CACKEY_PCSC_S_OK         On success
 *     CACKEY_PCSC_E_GENERIC    On error
 *
 * NOTES
 *     This function disconnects from the PC/SC Connection manager and updates
 *     the global handle.
 *
 */
static cackey_ret cackey_pcsc_disconnect(void) {
	LONG scard_rel_context_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	if (cackey_pcsc_handle == NULL) {
		return(CACKEY_PCSC_S_OK);
	}

	scard_rel_context_ret = SCardReleaseContext(*cackey_pcsc_handle);

	if (cackey_pcsc_handle) {
		free(cackey_pcsc_handle);
	
		cackey_pcsc_handle = NULL;
	}

	if (scard_rel_context_ret != SCARD_S_SUCCESS) {
		return(CACKEY_PCSC_E_GENERIC);
	}

	cackey_slots_disconnect_all(0);

	return(CACKEY_PCSC_S_OK);
}

/*
 * SYNPOSIS
 *     void cackey_mark_slot_reset(struct cackey_slot *slot);
 *
 * ARGUMENTS
 *     None
 *
 * RETURN VALUE
 *     None
 *
 * NOTES
 *     This function marks a slot has having been reset, to later be cleaned up.
 *     Cleanup only happens when a PKCS#11 client calls C_FindObjectsInit.
 *
 */
static void cackey_mark_slot_reset(struct cackey_slot *slot) {
	struct cackey_pcsc_identity *pcsc_identities;
	unsigned long num_certs;

	if (slot == NULL) {
		return;
	}

	CACKEY_DEBUG_PRINTF("Called.");

	if (slot->pcsc_card_connected) {
		SCardDisconnect(slot->pcsc_card, SCARD_LEAVE_CARD);
	}

	slot->slot_reset = 1;
	slot->pcsc_card_connected = 0;
	if (cackey_pin_command == NULL) {
		slot->token_flags = CKF_LOGIN_REQUIRED;
	} else {
		slot->token_flags = 0;
	}

	return;
}

/*
 * SYNPOSIS
 *     LONG cackey_reconnect_card(struct cackey_slot *slot, DWORD default_protocol);
 *
 * ARGUMENTS
 *     cackey_slot *slot
 *         Slot to send commands to
 *
 *     DWORD default_protocol
 *         Protocol to attempt first
 *
 * RETURN VALUE
 *     The return value from SCardReconnect()
 *
 * NOTES
 *     This function is a wrapper around SCardReconnect()
 *
 *     The SCardReconnect() function call will be called first with the
 *     dwPreferredProtocols of "default_protocol".  If that call returns
 *     SCARD_E_PROTO_MISMATCH try again with a protocol of T=0, and failing
 *     that T=1.
 *
 */
static LONG cackey_reconnect_card(struct cackey_slot *slot, DWORD default_protocol) {
	DWORD selected_protocol;
	LONG scard_conn_ret;

	selected_protocol = 0;

	scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, default_protocol, SCARD_RESET_CARD, &selected_protocol);

	if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) {
		CACKEY_DEBUG_PRINTF("SCardReconnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=0")
		scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, SCARD_RESET_CARD, &selected_protocol);

		if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) {
			CACKEY_DEBUG_PRINTF("SCardReconnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=1")
			scard_conn_ret = SCardReconnect(slot->pcsc_card, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, SCARD_RESET_CARD, &selected_protocol);
		}
	}

	if (scard_conn_ret == SCARD_S_SUCCESS) {
		slot->protocol = selected_protocol;
	}

	return(scard_conn_ret);
}

/*
 * SYNPOSIS
 *     cackey_ret cackey_connect_card(struct cackey_slot *slot);
 *
 * ARGUMENTS
 *     cackey_slot *slot
 *         Slot to send commands to
 *
 * RETURN VALUE
 *     CACKEY_PCSC_S_OK         On success
 *     CACKEY_PCSC_E_GENERIC    On error
 *
 * NOTES
 *     None
 *
 */
static cackey_ret cackey_connect_card(struct cackey_slot *slot) {
	cackey_ret pcsc_connect_ret;
	DWORD protocol;
	LONG scard_conn_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!slot) {
		CACKEY_DEBUG_PRINTF("Invalid slot specified, returning in failure");

		return(CACKEY_PCSC_E_GENERIC);
	}

	pcsc_connect_ret = cackey_pcsc_connect();
	if (pcsc_connect_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("Connection to PC/SC failed, returning in failure");

		return(CACKEY_PCSC_E_GENERIC);
	}

	/* Connect to reader, if needed */
	if (!slot->pcsc_card_connected) {
		CACKEY_DEBUG_PRINTF("SCardConnect(%s) called for slot %p", slot->pcsc_reader, slot);
		scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &slot->pcsc_card, &protocol);

		if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) {
			CACKEY_DEBUG_PRINTF("SCardConnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=0")
			scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, &slot->pcsc_card, &protocol);

			if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) {
				CACKEY_DEBUG_PRINTF("SCardConnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=1")
				scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, &slot->pcsc_card, &protocol);
			}
		}

		if (scard_conn_ret == SCARD_W_UNPOWERED_CARD) {
			CACKEY_DEBUG_PRINTF("SCardConnect() returned SCARD_W_UNPOWERED_CARD, trying to re-connect...");

			scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_DIRECT, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &slot->pcsc_card, &protocol);

			if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) {
				CACKEY_DEBUG_PRINTF("SCardConnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=0")
				scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, &slot->pcsc_card, &protocol);

				if (scard_conn_ret == SCARD_E_PROTO_MISMATCH) {
					CACKEY_DEBUG_PRINTF("SCardConnect() returned SCARD_E_PROTO_MISMATCH, trying with just T=1")
					scard_conn_ret = SCardConnect(*cackey_pcsc_handle, slot->pcsc_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, &slot->pcsc_card, &protocol);
				}
			}

			scard_conn_ret = cackey_reconnect_card(slot, protocol);
		}

		if (scard_conn_ret == SCARD_E_NO_SERVICE) {
			CACKEY_DEBUG_PRINTF("SCardConnect() returned SCARD_E_NO_SERVICE -- which could mean our handle is invalid, will try to reconnect to PC/SC service");

			cackey_pcsc_disconnect();

			cackey_pcsc_connect();

			cackey_mark_slot_reset(slot);

			return(cackey_connect_card(slot));
		}

		if (scard_conn_ret != SCARD_S_SUCCESS) {
			CACKEY_DEBUG_PRINTF("Connection to card failed, returning in failure (SCardConnect() = %s/%li)", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_conn_ret), (long) scard_conn_ret);

			return(CACKEY_PCSC_E_GENERIC);
		}

		slot->pcsc_card_connected = 1;
		slot->transaction_depth = 0;
		slot->transaction_need_hw_lock = 0;
		slot->protocol = protocol;
	}

	CACKEY_DEBUG_PRINTF("Returning in success");

	return(CACKEY_PCSC_S_OK);
}

/*
 * SYNPOSIS
 *     cackey_ret cackey_begin_transaction(struct cackey_slot *slot);
 *
 * ARGUMENTS
 *     cackey_slot *slot
 *         Slot to send commands to
 *
 * RETURN VALUE
 *     CACKEY_PCSC_S_OK         On success
 *     CACKEY_PCSC_E_GENERIC    On error
 *
 * NOTES
 *     The transaction should be terminated using "cackey_end_transaction"
 *
 */
static cackey_ret cackey_begin_transaction(struct cackey_slot *slot) {
	cackey_ret cackey_conn_ret;
	LONG scard_trans_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	cackey_conn_ret = cackey_connect_card(slot);
	if (cackey_conn_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("Unable to connect to card, returning in error");

		return(CACKEY_PCSC_E_GENERIC);
	}

	slot->transaction_depth++;

	if (slot->transaction_depth > 1 && !slot->transaction_need_hw_lock) {
		CACKEY_DEBUG_PRINTF("Already in a transaction, performing no action (new depth = %i)", slot->transaction_depth);

		return(CACKEY_PCSC_S_OK);
	}

	slot->transaction_need_hw_lock = 0;

	scard_trans_ret = SCardBeginTransaction(slot->pcsc_card);
	if (scard_trans_ret != SCARD_S_SUCCESS) {
		CACKEY_DEBUG_PRINTF("Unable to begin transaction, returning in error");

		return(CACKEY_PCSC_E_GENERIC);
	}

	CACKEY_DEBUG_PRINTF("Sucessfully began transaction on slot (%s)", slot->pcsc_reader);

	return(CACKEY_PCSC_S_OK);
}

/*
 * SYNPOSIS
 *     cackey_ret cackey_end_transaction(struct cackey_slot *slot);
 *
 * ARGUMENTS
 *     cackey_slot *slot
 *         Slot to send commands to
 *
 * RETURN VALUE
 *     CACKEY_PCSC_S_OK         On success
 *     CACKEY_PCSC_E_GENERIC    On error
 *
 * NOTES
 *     This function requires "cackey_begin_transaction" to be called first
 *
 */
static cackey_ret cackey_end_transaction(struct cackey_slot *slot) {
	LONG scard_trans_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!slot->pcsc_card_connected) {
		CACKEY_DEBUG_PRINTF("Card is not connected, unable to end transaction on card");

		if (slot->transaction_depth > 0) {
			CACKEY_DEBUG_PRINTF("Decreasing transaction depth and asking for a hardware lock on the next begin transaction (current depth = %i)", slot->transaction_depth);

			slot->transaction_depth--;

			if (slot->transaction_depth > 0) {
				slot->transaction_need_hw_lock = 1;
			}
		}

		return(CACKEY_PCSC_E_GENERIC);
	}

	if (slot->transaction_depth == 0) {
		CACKEY_DEBUG_PRINTF("Terminating a transaction that has not begun!");

		return(CACKEY_PCSC_E_GENERIC);
	}

	slot->transaction_depth--;

	if (slot->transaction_depth > 0) {
		CACKEY_DEBUG_PRINTF("Transactions still in progress, not terminating on-card Transaction (current depth = %i)", slot->transaction_depth);

		return(CACKEY_PCSC_S_OK);
	}

	scard_trans_ret = SCardEndTransaction(slot->pcsc_card, SCARD_LEAVE_CARD);
	if (scard_trans_ret != SCARD_S_SUCCESS) {
		CACKEY_DEBUG_PRINTF("Unable to end transaction, returning in error");

		return(CACKEY_PCSC_E_GENERIC);
	}

	CACKEY_DEBUG_PRINTF("Sucessfully terminated transaction on slot (%s)", slot->pcsc_reader);

	return(CACKEY_PCSC_S_OK);
}

/* APDU Related Functions */
/*
 * SYNPOSIS
 *     cackey_ret cackey_send_apdu(struct cackey_slot *slot, unsigned char class, unsigned char instruction, unsigned char p1, unsigned char p2, unsigned char lc, unsigned char *data, unsigned char le, uint16_t *respcode, unsigned char *respdata, size_t *respdata_len);
 *
 * ARGUMENTS
 *     cackey_slot *slot
 *         Slot to send commands to
 *
 *     unsigned char class
 *         APDU Class (GSCIS_CLASS_ISO7816 or GSCIS_CLASS_GLOBAL_PLATFORM
 *         usually), (CLA)
 *
 *     unsigned char instruction
 *         APDU Instruction (INS)
 *
 *     unsigned char p1
 *         APDU Parameter 1 (P1)
 *
 *     unsigned char p2
 *         APDU Parameter 2 (P2)
 *
 *     unsigned char lc
 *         APDU Length of Content (Lc) -- this is the length of "data"
 *         parameter.  If "data" is specified as NULL, this parameter will
 *         be ignored.
 *
 *     unsigned char *data
 *         Pointer to buffer to send.  It should be "Lc" bytes long.  If
 *         specified as NULL, "Lc" will not be sent, and this buffer will be
 *         ignored.
 *
 *     unsigned char le
 *         APDU Length of Expectation (Le) -- this is the length of the
 *         expected reply.  If this is specified as 0 then it will not
 *         be sent.
 *
 *     uint16_t *respcode
 *         [OUT] Pointer to storage of APDU response code.  If this is
 *         specified as NULL, the response code will be discarded.
 *
 *     unsigned char *respdata
 *         [OUT] Pointer to storage of APDU response data.  If this is
 *         specified as NULL, the response data will be discarded.  If
 *         the "respdata_len" parameter is specified as NULL, this buffer
 *         will not be updated.
 *
 *     size_t *respdata_len
 *         [IN, OUT] Pointer initialing containing the size of the "respdata"
 *         buffer.  Before returning, the pointed to value is updated to the
 *         number of bytes written to the buffer.  If this is specified as
 *         NULL, it will not be updated, and "respdata" will be ignored causing
 *         the response data to be discarded.
 *
 * RETURN VALUE
 *     CACKEY_PCSC_S_OK           On success
 *     CACKEY_PCSC_E_GENERIC      On error
 *     CACKEY_PCSC_E_TOKENABSENT  If the sending failed because the token is
 *                                absent
 *     CACKEY_PCSC_E_RETRY        If something that looks retry'able went
 *                                wrong -- try the whole transaction over
 *                                again
 *
 * NOTES
 *     This function will connect to the PC/SC Connection Manager via
 *     cackey_pcsc_connect() if needed.
 *
 *     It will connect to the card in the reader attached to the slot
 *     specified.  It will reconnect to the card if the connection
 *     goes away.
 *
 */
static cackey_ret cackey_send_apdu(struct cackey_slot *slot, unsigned char class, unsigned char instruction, unsigned char p1, unsigned char p2, unsigned int lc, unsigned char *data, unsigned int le, uint16_t *respcode, unsigned char *respdata, size_t *respdata_len) {
	uint8_t major_rc, minor_rc;
	size_t bytes_to_copy, tmp_respdata_len;
	LPCSCARD_IO_REQUEST pioSendPci;
	SCARD_IO_REQUEST pioRecvPci;
	DWORD xmit_len, recv_len;
	LONG scard_xmit_ret, scard_reconn_ret;
	BYTE xmit_buf[1024], recv_buf[1024];
	int pcsc_connect_ret, pcsc_getresp_ret;
	int idx;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!slot) {
		CACKEY_DEBUG_PRINTF("Invalid slot specified.");

		return(CACKEY_PCSC_E_GENERIC);
	}

	if (respcode) {
		*respcode = 0xffff;
	}

	pcsc_connect_ret = cackey_connect_card(slot);
	if (pcsc_connect_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("Unable to connect to card, returning in failure");

		return(CACKEY_PCSC_E_GENERIC);
	}

	/* Determine which protocol to send using */
	switch (slot->protocol) {
		case SCARD_PROTOCOL_T0:
			CACKEY_DEBUG_PRINTF("Protocol to send datagram is T=0");

			pioSendPci = SCARD_PCI_T0;

			break;
		case SCARD_PROTOCOL_T1:
			CACKEY_DEBUG_PRINTF("Protocol to send datagram is T=1");

			pioSendPci = SCARD_PCI_T1;

			break;
		default:
			CACKEY_DEBUG_PRINTF("Invalid protocol found, aborting.");

			return(CACKEY_PCSC_E_GENERIC);
	}

	/* Transmit */
	xmit_len = 0;
	xmit_buf[xmit_len++] = class;
	xmit_buf[xmit_len++] = instruction;
	xmit_buf[xmit_len++] = p1;
	xmit_buf[xmit_len++] = p2;
	if (data) {
		if (lc > 255) {
			CACKEY_DEBUG_PRINTF("CAUTION!  Using an Lc greater than 255 is untested.  Lc = %u", lc);

			xmit_buf[xmit_len++] = 0x82; /* XXX UNTESTED */
			xmit_buf[xmit_len++] = (lc & 0xff00) >> 8;
			xmit_buf[xmit_len++] = lc & 0xff;
		} else {
			xmit_buf[xmit_len++] = lc;
		}
		for (idx = 0; idx < lc; idx++) {
			xmit_buf[xmit_len++] = data[idx];
		}
	}

	if (le != 0x00) {
		if (le > 256) {
			CACKEY_DEBUG_PRINTF("CAUTION!  Using an Le greater than 256 is untested.  Le = %u", le);

			xmit_buf[xmit_len++] = 0x82; /* XXX UNTESTED */
			xmit_buf[xmit_len++] = (le & 0xff00) >> 8;
			xmit_buf[xmit_len++] = le & 0xff;
		} else if (le == 256) {
			xmit_buf[xmit_len++] = 0x00;
		} else {
			xmit_buf[xmit_len++] = le;
		}
	}

	/* Begin Smartcard Transaction */
	cackey_begin_transaction(slot);

	if (class == GSCIS_CLASS_ISO7816 && (instruction == GSCIS_INSTR_VERIFY || instruction == GSCIS_INSTR_CHANGE_REFERENCE) && p1 == 0x00) {
		CACKEY_DEBUG_PRINTF("Sending APDU: <<censored>>");
	} else {
		CACKEY_DEBUG_PRINTBUF("Sending APDU:", xmit_buf, xmit_len);
	}

	recv_len = sizeof(recv_buf);
	memcpy(&pioRecvPci, pioSendPci, sizeof(pioRecvPci));
	scard_xmit_ret = SCardTransmit(slot->pcsc_card, pioSendPci, xmit_buf, xmit_len, &pioRecvPci, recv_buf, &recv_len);

	CACKEY_DEBUG_PRINTF("SCardTransmit() completed with value: %s/%lx", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_xmit_ret), (unsigned long) scard_xmit_ret);

	if (scard_xmit_ret == SCARD_E_NOT_TRANSACTED) {
		CACKEY_DEBUG_PRINTF("Failed to send APDU to card (SCardTransmit() = %s/%lx), will ask calling function to retry (not resetting card)...", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_xmit_ret), (unsigned long) scard_xmit_ret);

		/* End Smartcard Transaction */
		cackey_end_transaction(slot);

		cackey_reconnect_card(slot, slot->protocol);

		return(CACKEY_PCSC_E_RETRY);
	}

	if (scard_xmit_ret == SCARD_E_NO_SERVICE) {
		CACKEY_DEBUG_PRINTF("Failed to send APDU to card, possibly due to PC/SC handle being invalid (SCardTransmit() = %s/%lx), will ask calling function to retry...", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_xmit_ret), (unsigned long) scard_xmit_ret);

		cackey_mark_slot_reset(slot);

		return(CACKEY_PCSC_E_RETRY);
	}

	if (scard_xmit_ret != SCARD_S_SUCCESS) {
		CACKEY_DEBUG_PRINTF("Failed to send APDU to card (SCardTransmit() = %s/%lx)", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_xmit_ret), (unsigned long) scard_xmit_ret);

		CACKEY_DEBUG_PRINTF("Marking slot as having been reset");
		cackey_mark_slot_reset(slot);

		if (scard_xmit_ret == SCARD_W_RESET_CARD) {
			CACKEY_DEBUG_PRINTF("Reset required, please hold...");

			scard_reconn_ret = cackey_reconnect_card(slot, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1);

			if (scard_reconn_ret == SCARD_S_SUCCESS) {
				switch (slot->protocol) {
					case SCARD_PROTOCOL_T0:
						pioSendPci = SCARD_PCI_T0;

						break;
					case SCARD_PROTOCOL_T1:
						pioSendPci = SCARD_PCI_T1;

						break;
					default:
						CACKEY_DEBUG_PRINTF("Invalid protocol found, but too late to do anything about it now -- trying anyway.");

						break;
				}

				/* Re-establish transaction, if it was present */
				if (slot->transaction_depth > 0) {
					slot->transaction_depth--;
					slot->transaction_need_hw_lock = 1;
					cackey_begin_transaction(slot);
				}

				CACKEY_DEBUG_PRINTF("Reset successful, retransmitting");

				recv_len = sizeof(recv_buf);
				memcpy(&pioRecvPci, pioSendPci, sizeof(pioRecvPci));
				scard_xmit_ret = SCardTransmit(slot->pcsc_card, pioSendPci, xmit_buf, xmit_len, &pioRecvPci, recv_buf, &recv_len);

				if (scard_xmit_ret != SCARD_S_SUCCESS) {
					CACKEY_DEBUG_PRINTF("Retransmit failed, returning in failure after disconnecting the card (SCardTransmit = %s/%li)", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_xmit_ret), (long) scard_xmit_ret);

					SCardDisconnect(slot->pcsc_card, SCARD_LEAVE_CARD);
					slot->pcsc_card_connected = 0;

					/* End Smartcard Transaction */
					slot->transaction_depth = 1;
					cackey_end_transaction(slot);

					return(CACKEY_PCSC_E_TOKENABSENT);
				}
			} else {
				CACKEY_DEBUG_PRINTF("Disconnecting card");

				SCardDisconnect(slot->pcsc_card, SCARD_LEAVE_CARD);
				slot->pcsc_card_connected = 0;

				/* End Smartcard Transaction */
				slot->transaction_depth = 1;
				cackey_end_transaction(slot);

				CACKEY_DEBUG_PRINTF("Returning in failure");
				return(CACKEY_PCSC_E_TOKENABSENT);
			}
		} else {
			CACKEY_DEBUG_PRINTF("Disconnecting card");

			SCardDisconnect(slot->pcsc_card, SCARD_LEAVE_CARD);
			slot->pcsc_card_connected = 0;

			/* End Smartcard Transaction */
			slot->transaction_depth = 1;
			cackey_end_transaction(slot);

			CACKEY_DEBUG_PRINTF("Returning in failure");
			return(CACKEY_PCSC_E_TOKENABSENT);
		}
	}

	CACKEY_DEBUG_PRINTBUF("Returned Value:", recv_buf, recv_len);

	if (recv_len < 2) {
		/* Minimal response length is 2 bytes, returning in failure */
		CACKEY_DEBUG_PRINTF("Response too small, returning in failure (recv_len = %lu)", (unsigned long) recv_len);

		/* End Smartcard Transaction */
		cackey_end_transaction(slot);

		/* Supply an invalid response code */
		if (respcode) {
			*respcode = 0;
		}

		return(CACKEY_PCSC_E_NODATA);
	}

	/* Determine result code */
	major_rc = recv_buf[recv_len - 2];
	minor_rc = recv_buf[recv_len - 1];
	if (respcode) {
		*respcode = (major_rc << 8) | minor_rc;
	}

	/* Adjust message buffer */
	recv_len -= 2;

	/* Add bytes to return value */
	tmp_respdata_len = 0;
	if (respdata && respdata_len) {
		tmp_respdata_len = *respdata_len;

		bytes_to_copy = *respdata_len;

		if (recv_len < bytes_to_copy) {
			bytes_to_copy = recv_len;
		}

		CACKEY_DEBUG_PRINTF("Copying %lu bytes to the buffer (recv'd %lu bytes, but only %lu bytes left in our buffer)", (unsigned long) bytes_to_copy, (unsigned long) recv_len, (unsigned long) *respdata_len);

		memcpy(respdata, recv_buf, bytes_to_copy);
		respdata += bytes_to_copy;

		*respdata_len = bytes_to_copy;
		tmp_respdata_len -= bytes_to_copy;
	} else {
		if (recv_len != 0) {
			CACKEY_DEBUG_PRINTF("Throwing away %lu bytes, nowhere to put them!", (unsigned long) recv_len);
		}
	}

	if (major_rc == 0x61) {
		/* We need to READ */
		CACKEY_DEBUG_PRINTF("Buffer read required");

		if (minor_rc == 0x00) {
			minor_rc = CACKEY_APDU_MTU;
		}

		pcsc_getresp_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, GSCIS_INSTR_GET_RESPONSE, 0x00, 0x00, 0, NULL, minor_rc, respcode, respdata, &tmp_respdata_len);

		if (pcsc_getresp_ret != CACKEY_PCSC_S_OK) {
			CACKEY_DEBUG_PRINTF("Buffer read failed!  Returning in failure");

			/* End Smartcard Transaction */
			cackey_end_transaction(slot);

			if (pcsc_getresp_ret == CACKEY_PCSC_E_RETRY) {
				return(CACKEY_PCSC_E_RETRY);
			}

			return(CACKEY_PCSC_E_GENERIC);
		}

		if (respdata_len) {
			*respdata_len += tmp_respdata_len;
		}

		/* End Smartcard Transaction */
		cackey_end_transaction(slot);

		CACKEY_DEBUG_PRINTF("Returning in success (buffer read complete)");
		return(CACKEY_PCSC_S_OK);
	}

	/* End Smartcard Transaction */
	cackey_end_transaction(slot);

	if (major_rc == 0x90) {
		/* Success */
		CACKEY_DEBUG_PRINTF("Returning in success (major_rc = 0x90)");

		return(CACKEY_PCSC_S_OK);
	}


	CACKEY_DEBUG_PRINTF("APDU Returned an error, returning in failure");

	return(CACKEY_PCSC_E_GENERIC);
}

static unsigned char *cackey_read_bertlv_tag(unsigned char *buffer, size_t *buffer_len_p, unsigned char tag, unsigned char *outbuffer, size_t *outbuffer_len_p) {
	unsigned char *buffer_p;
	size_t outbuffer_len, buffer_len;
	size_t size;
	int idx;

	CACKEY_DEBUG_PRINTF("Called.");

	if (buffer_len_p == NULL) {
		CACKEY_DEBUG_PRINTF("buffer_len_p is NULL.  Returning in failure.");

		return(NULL);
	}

	if (outbuffer_len_p == NULL) {
		CACKEY_DEBUG_PRINTF("outbuffer_len_p is NULL.  Returning in failure.");

		return(NULL);
	}

	buffer_len = *outbuffer_len_p;
	outbuffer_len = *outbuffer_len_p;

	if (buffer_len < 2) {
		CACKEY_DEBUG_PRINTF("buffer_len is less than 2, so we can't read any tag.  Returning in failure.");

		return(NULL);
	}

	buffer_p = buffer;
	if (buffer_p[0] != tag) {
		CACKEY_DEBUG_PRINTF("Tag found was not tag expected.  Tag = %02x, Expected = %02x.  Returning in failure.", (unsigned int) buffer_p[0], tag);

		return(NULL);
	}

	buffer_p++;
	buffer_len--;

	if ((buffer_p[0] & 0x80) == 0x80) {
		size = 0;
		idx = (buffer_p[0] & 0x7f);

		if (idx > buffer_len) {
			CACKEY_DEBUG_PRINTF("Malformed BER value -- not enough bytes available to read length (idx = %i, buffer_len = %lu)", idx, (unsigned long) buffer_len);

			return(NULL);
		}

		for (; idx > 0; idx--) {
			buffer_p++;
			buffer_len--;

			size <<= 8;
			size |= buffer_p[0];
		}
	} else {
		size = buffer_p[0];
	}

	buffer_p++;
	buffer_len--;

	if (size > outbuffer_len) {
		CACKEY_DEBUG_PRINTF("Unable to copy value buffer to outbuffer, not enough room.  Value buffer length = %lu, out buffer length = %lu", (unsigned long) size, (unsigned long) outbuffer_len);

		return(NULL);
	}

	*outbuffer_len_p = size;
	if (outbuffer) {
		memcpy(outbuffer, buffer_p, size);
		buffer_p += size;
		buffer_len -= size;

		*buffer_len_p = buffer_len;

		CACKEY_DEBUG_PRINTBUF("BER-TLV results:", outbuffer, size);
	} else {
		memmove(buffer, buffer_p, size);
		buffer_p = buffer;

		CACKEY_DEBUG_PRINTBUF("BER-TLV results:", buffer, size);
	}

	CACKEY_DEBUG_PRINTF("Returning in success.  Size of contents for tag %02x is %lu", (unsigned int) tag, (unsigned long) size);

	return(buffer_p);
}

/*
 * SYNPOSIS
 *     ssize_t cackey_get_data(struct cackey_slot *slot, unsigned char *buffer, size_t buffer_len, unsigned char oid[3]);
 *
 * ARGUMENTS
 *     struct cackey_slot *slot
 *         Slot to send commands to
 *
 *     unsigned char *buffer
 *         [OUT] Buffer
 *
 *     size_t buffer_len
 *         Number of bytes to attempt to read
 *
 *     unsigned char oid[3]
 *         3-byte OID to read
 *
 *
 * RETURN VALUE
 *     This function returns the number of bytes actually read, or -1 on error.
 *
 * NOTES
 *     None
 *
 */
static ssize_t cackey_get_data(struct cackey_slot *slot, unsigned char *buffer, size_t buffer_len, unsigned char oid[3]) {
	unsigned char cmd[] = {0x5C, 0x03, 0x00, 0x00, 0x00};
	unsigned char *buffer_p;
	size_t init_buffer_len, size;
	uint16_t respcode;
	int send_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	init_buffer_len = buffer_len;

	cmd[2] = oid[0];
	cmd[3] = oid[1];
	cmd[4] = oid[2];

	/* 256 to indicate the largest message size -- not clear if this will work with all messages */
	send_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, NISTSP800_73_3_INSTR_GET_DATA, 0x3F, 0xFF, sizeof(cmd), cmd, 256, &respcode, buffer, &buffer_len);

	if (send_ret == CACKEY_PCSC_E_RETRY) {
		CACKEY_DEBUG_PRINTF("ADPU Sending failed, retrying read buffer");

		return(cackey_get_data(slot, buffer, init_buffer_len, oid));
	}

	if (send_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("cackey_send_apdu() failed, returning in failure");

		return(-1);
	}

#ifdef CACKEY_PARANOID
#  ifdef _POSIX_SSIZE_MAX
	if (buffer_len > _POSIX_SSIZE_MAX) {
		CACKEY_DEBUG_PRINTF("Read bytes (buffer_len) exceeds maximum value, returning in failure. (max = %li, buffer_len = %lu)", (long) _POSIX_SSIZE_MAX, (unsigned long) buffer_len);

		return(-1);
	}
#  endif
#endif

	if (buffer_len < 2) {
		CACKEY_DEBUG_PRINTF("APDU GET DATA returned %lu bytes, which is too short for a BER-TLV response", (unsigned long) buffer_len);

		return(-1);
	}

	size = buffer_len;
	buffer_p = cackey_read_bertlv_tag(buffer, &buffer_len, 0x53, NULL, &size);

	if (buffer_p == NULL) {
		CACKEY_DEBUG_PRINTF("Tag decoding failed, returning in error.");

		return(-1);
	}

	CACKEY_DEBUG_PRINTBUF("GET DATA result", buffer, size);

	CACKEY_DEBUG_PRINTF("Returning in success, read %lu bytes", (unsigned long) size);

	return(size);
}

/*
 * SYNPOSIS
 *     ssize_t cackey_read_buffer(struct cackey_slot *slot, unsigned char *buffer, size_t count, unsigned char t_or_v, size_t initial_offset);
 *
 * ARGUMENTS
 *     struct cackey_slot *slot
 *         Slot to send commands to
 *
 *     unsigned char *buffer
 *         [OUT] Buffer
 *
 *     size_t count
 *         Number of bytes to attempt to read
 *
 *     unsigned char t_or_v
 *         Select the T-buffer (01) or V-buffer (02) to read from.  
 *
 *     size_t initial_offset
 *         Specify the offset to begin the read from
 *
 *
 * RETURN VALUE
 *     This function returns the number of bytes actually read, or -1 on error.
 *
 * NOTES
 *     None
 *
 */
static ssize_t cackey_read_buffer(struct cackey_slot *slot, unsigned char *buffer, size_t count, unsigned char t_or_v, size_t initial_offset) {
	unsigned char *init_buffer;
	size_t init_count;
	size_t init_initial_offset;

	size_t offset = 0, max_offset, max_count;
	unsigned char cmd[2];
	uint16_t respcode;
	int send_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	init_buffer = buffer;
	init_count = count;
	init_initial_offset = initial_offset;

	max_offset = count;
	max_count = CACKEY_APDU_MTU;

	if (t_or_v != 1 && t_or_v != 2) {
		CACKEY_DEBUG_PRINTF("Invalid T or V parameter specified, returning in failure");

		return(-1);
	}

	cmd[0] = t_or_v;

	while (1) {
		if (offset >= max_offset) {
			CACKEY_DEBUG_PRINTF("Buffer too small, returning what we got...");

			break;
		}

		count = max_offset - offset;
		if (count > max_count) {
			count = max_count;
		}

		cmd[1] = count;

		send_ret = cackey_send_apdu(slot, GSCIS_CLASS_GLOBAL_PLATFORM, GSCIS_INSTR_READ_BUFFER, ((initial_offset + offset) >> 8) & 0xff, (initial_offset + offset) & 0xff, sizeof(cmd), cmd, 0x00, &respcode, buffer + offset, &count);

		if (send_ret == CACKEY_PCSC_E_RETRY) {
			CACKEY_DEBUG_PRINTF("ADPU Sending failed, retrying read buffer");

			return(cackey_read_buffer(slot, init_buffer, init_count, t_or_v, init_initial_offset));
		}

		if (send_ret != CACKEY_PCSC_S_OK) {
			if (respcode == 0x6A86) {
				if (max_count == 1) {
					break;
				}

				max_count = max_count / 2;

				continue;
			}

			CACKEY_DEBUG_PRINTF("cackey_send_apdu() failed, returning in failure");

			return(-1);
		}

		offset += count;

		if (count < max_count) {
			CACKEY_DEBUG_PRINTF("Short read -- count = %i, cmd[1] = %i", (int) count, (int) cmd[1]);

			break;
		}
	}

#ifdef CACKEY_PARANOID
#  ifdef _POSIX_SSIZE_MAX
	if (offset > _POSIX_SSIZE_MAX) {
		CACKEY_DEBUG_PRINTF("Offset exceeds maximum value, returning in failure. (max = %li, offset = %lu)", (long) _POSIX_SSIZE_MAX, (unsigned long) offset);

		return(-1);
	}
#  endif
#endif

	CACKEY_DEBUG_PRINTF("Returning in success, read %lu bytes", (unsigned long) offset);

	return(offset);
}

/*
 * SYNPOSIS
 *     cackey_ret cackey_select_applet(struct cackey_slot *slot, unsigned char *aid, size_t aid_len);
 *
 * ARGUMENTS
 *     struct cackey_slot *slot
 *         Slot to send commands to
 *
 *     unsigned char *aid
 *         Buffer containing Applet ID to select
 *
 *     size_t aid_len
 *         Number of bytes in the "aid" (Applet ID) parameter
 *
 * RETURN VALUE
 *     CACKEY_PCSC_S_OK         On success
 *     CACKEY_PCSC_E_GENERIC    On error
 *
 * NOTES
 *     None
 *
 */
static cackey_ret cackey_select_applet(struct cackey_slot *slot, unsigned char *aid, size_t aid_len) {
	int send_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	CACKEY_DEBUG_PRINTBUF("Selecting applet:", aid, aid_len);

	send_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, GSCIS_INSTR_SELECT, GSCIS_PARAM_SELECT_APPLET, 0x00, aid_len, aid, 0x00, NULL, NULL, NULL);

	if (send_ret == CACKEY_PCSC_E_RETRY) {
		CACKEY_DEBUG_PRINTF("ADPU Sending failed, retrying select applet");

		return(cackey_select_applet(slot, aid, aid_len));
	}

	if (send_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("Failed to open applet, returning in failure");

		return(CACKEY_PCSC_E_GENERIC);
	}

	CACKEY_DEBUG_PRINTF("Successfully selected file");

	return(CACKEY_PCSC_S_OK);
}

/*
 * SYNPOSIS
 *     cackey_pcsc_id_type cackey_detect_and_select_root_applet(struct cackey_slot *slot, cackey_pcsc_id_type type_hint);
 *
 * ARGUMENTS
 *     struct cackey_slot *slot
 *         Slot to send commands to
 *
 *     cackey_pcsc_id_type type_hint
 *         A hint as to which type of card might be in this slot (CAC or PIV)
 *
 * RETURN VALUE
 *     CACKEY_ID_TYPE_PIV       If the card connected is a PIV
 *     CACKEY_ID_TYPE_CAC       If the card connected is a CAC with the CCC
 *                              applet
 *     CACKEY_ID_TYPE_ERROR     If we are unable to determine what type of card
 *                              is connected
 *
 * NOTES
 *     This function reselects the "root" applet, after this function is called
 *     the user may be required to login again
 *
 */
static cackey_pcsc_id_type cackey_detect_and_select_root_applet(struct cackey_slot *slot, cackey_pcsc_id_type type_hint) {
	unsigned char cac_ccc_aid[] = {GSCIS_AID_CCC};
	unsigned char cac_id0_aid[] = {GSCIS_AID_ID0};
	unsigned char piv_aid[] = {NISTSP800_73_3_PIV_AID};
	cackey_pcsc_id_type try_types[2], try_type;
	int send_ret;
	int idx;

	CACKEY_DEBUG_PRINTF("Reselecting the root applet");

	if (type_hint == CACKEY_ID_TYPE_UNKNOWN) {
		type_hint = slot->id_type_hint;
	}

	slot->id_type_hint = CACKEY_ID_TYPE_UNKNOWN;

	switch (type_hint) {
		case CACKEY_ID_TYPE_PIV:
			CACKEY_DEBUG_PRINTF("Trying to reselect the PIV root applet first");

			try_types[0] = CACKEY_ID_TYPE_PIV;
			try_types[1] = CACKEY_ID_TYPE_CAC;

			break;
		default:
			CACKEY_DEBUG_PRINTF("Trying to reselect the CAC CCC applet first");

			try_types[0] = CACKEY_ID_TYPE_CAC;
			try_types[1] = CACKEY_ID_TYPE_PIV;

			break;
	}

	for (idx = 0; idx < (sizeof(try_types) / sizeof(try_types[0])); idx++) {
		try_type = try_types[idx];

		switch (try_type) {
			case CACKEY_ID_TYPE_CAC:
				CACKEY_DEBUG_PRINTF("Trying to select the CAC CCC applet");

				send_ret = cackey_select_applet(slot, cac_ccc_aid, sizeof(cac_ccc_aid));
				if (send_ret != CACKEY_PCSC_S_OK) {
					send_ret = cackey_select_applet(slot, cac_id0_aid, sizeof(cac_id0_aid));
				}

				break;
			case CACKEY_ID_TYPE_PIV:
				CACKEY_DEBUG_PRINTF("Trying to select the PIV root applet");

				send_ret = cackey_select_applet(slot, piv_aid, sizeof(piv_aid));

				break;
		}

		if (send_ret == CACKEY_PCSC_S_OK) {
			CACKEY_DEBUG_PRINTF("Successfully selected the %s applet -- setting the \"LOGIN REQUIRED\" flag on the token",
				try_type == CACKEY_ID_TYPE_CAC ? "CAC" : "PIV"
			);

			slot->token_flags = CKF_LOGIN_REQUIRED;

			slot->id_type_hint = try_type;

			return(try_type);
		}
	}

	CACKEY_DEBUG_PRINTF("Unable to select any applet, returning in failure");

	return(CACKEY_ID_TYPE_ERROR);
}

/*
 * SYNPOSIS
 *     cackey_ret cackey_select_file(struct cackey_slot *slot, uint16_t ef);
 *
 * ARGUMENTS
 *     struct cackey_slot *slot
 *         Slot to send commands to
 *
 *     uint16_t ef
 *         Elemental File to select
 *
 * RETURN VALUE
 *     CACKEY_PCSC_S_OK         On success
 *     CACKEY_PCSC_E_GENERIC    On error
 *
 * NOTES
 *     This selects an Elementary File (EF) under the currently selected
 *     Dedicated File (DF)
 *
 *     Typically this is called after selecting the correct Applet (using
 *     cackey_select_applet) for VM cards
 *
 */
static cackey_ret cackey_select_file(struct cackey_slot *slot, uint16_t ef) {
	unsigned char fid_buf[2];
	int send_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	/* Open the elementary file */
	fid_buf[0] = (ef >> 8) & 0xff;
	fid_buf[1] = ef & 0xff;

	CACKEY_DEBUG_PRINTF("Selecting file: %04lx", (unsigned long) ef);

	send_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, GSCIS_INSTR_SELECT, 0x02, 0x0C, sizeof(fid_buf), fid_buf, 0x00, NULL, NULL, NULL);
	if (send_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("Failed to open file, returning in failure");

		return(CACKEY_PCSC_E_GENERIC);
	}

	CACKEY_DEBUG_PRINTF("Successfully selected file");

	return(CACKEY_PCSC_S_OK);
}

/*
 * SYNPOSIS
 *     void cackey_free_tlv(struct cackey_tlv_entity *root);
 *
 * ARGUMENTS
 *     struct cackey_tlv_entity *root
 *         Root of the TLV list to start freeing
 *
 * RETURN VALUE
 *     None
 *
 * NOTES
 *     This function frees the TLV linked listed returned from
 *     "cackey_read_tlv"
 *
 */
static void cackey_free_tlv(struct cackey_tlv_entity *root) {
	struct cackey_tlv_entity *curr, *next;

	if (root == NULL) {
		return;
	}

	for (curr = root; curr; curr = next) {
		next = curr->_next;

		switch (curr->tag) {
			case GSCIS_TAG_ACR_TABLE:
			case GSCIS_TAG_CERTIFICATE:
				if (curr->value) {
					free(curr->value);
				}
				break;
			case GSCIS_TAG_CARDURL:
				if (curr->value_cardurl) {
					free(curr->value_cardurl);
				}
				break;
		}

		free(curr);
	}

	return;
}

/*
 * SYNPOSIS
 *     ...
 *
 * ARGUMENTS
 *     ...
 *
 * RETURN VALUE
 *     ...
 *
 * NOTES
 *     ...
 *
 */
static struct cackey_tlv_entity *cackey_read_tlv(struct cackey_slot *slot) {
	struct cackey_tlv_entity *curr_entity, *root = NULL, *last = NULL;
	unsigned char tlen_buf[2], tval_buf[1024], *tval;
	unsigned char vlen_buf[2], vval_buf[8192], *vval;
	unsigned char *tmpbuf;
	unsigned long tmpbuflen;
	ssize_t tlen, vlen;
	ssize_t read_ret;
	size_t offset_t = 0, offset_v = 0;
	unsigned char tag;
	size_t length;
#ifdef HAVE_LIBZ
	int uncompress_ret;
#endif

	CACKEY_DEBUG_PRINTF("Called.");

	read_ret = cackey_read_buffer(slot, tlen_buf, sizeof(tlen_buf), 1, offset_t);
	if (read_ret != sizeof(tlen_buf)) {
		CACKEY_DEBUG_PRINTF("Read failed, returning in failure");

		return(NULL);
	}

	tlen = (tlen_buf[1] << 8) | tlen_buf[0];

	read_ret = cackey_read_buffer(slot, vlen_buf, sizeof(vlen_buf), 2, offset_v);
	if (read_ret != sizeof(vlen_buf)) {
		CACKEY_DEBUG_PRINTF("Read failed, returning in failure");

		return(NULL);
	}

	vlen = (vlen_buf[1] << 8) | vlen_buf[0];

	CACKEY_DEBUG_PRINTF("Tag Length = %lu, Value Length = %lu", (unsigned long) tlen, (unsigned long) vlen);

	offset_t += 2;
	offset_v += 2;

	if (tlen > sizeof(tval_buf)) {
		CACKEY_DEBUG_PRINTF("Tag length is too large, returning in failure");

		return(NULL);
	}

	if (vlen > sizeof(vval_buf)) {
		CACKEY_DEBUG_PRINTF("Value length is too large, returning in failure");

		return(NULL);
	}

	read_ret = cackey_read_buffer(slot, tval_buf, tlen, 1, offset_t);
	if (read_ret != tlen) {
		CACKEY_DEBUG_PRINTF("Unable to read entire T-buffer, returning in failure");

		return(NULL);
	}

	read_ret = cackey_read_buffer(slot, vval_buf, vlen, 2, offset_v);
	if (read_ret != vlen) {
		CACKEY_DEBUG_PRINTF("Unable to read entire V-buffer, returning in failure");

		return(NULL);
	}

	tval = tval_buf;
	vval = vval_buf;
	while (tlen > 0 && vlen > 0) {
		tag = *tval;
		tval++;
		tlen--;

		if (*tval == 0xff) {
			length = (tval[2] << 8) | tval[1];
			tval += 3;
			tlen -= 3;
		} else {
			length = *tval;
			tval++;
			tlen--;
		}

		CACKEY_DEBUG_PRINTF("Tag: %s (%02x)", CACKEY_DEBUG_FUNC_TAG_TO_STR(tag), (unsigned int) tag);
		CACKEY_DEBUG_PRINTBUF("Value:", vval, length);

		curr_entity = NULL;
		switch (tag) {
			case GSCIS_TAG_CARDURL:
				curr_entity = malloc(sizeof(*curr_entity));
				curr_entity->value_cardurl = malloc(sizeof(*curr_entity->value_cardurl));

				memcpy(curr_entity->value_cardurl->rid, vval, 5);
				curr_entity->value_cardurl->apptype = vval[5];
				curr_entity->value_cardurl->objectid = (vval[6] << 8) | vval[7];
				curr_entity->value_cardurl->appid = (vval[8] << 8) | vval[9];

				curr_entity->tag = tag;
				curr_entity->_next = NULL;

				break;
			case GSCIS_TAG_ACR_TABLE:
				curr_entity = malloc(sizeof(*curr_entity));
				tmpbuf = malloc(length);

				memcpy(tmpbuf, vval, length);

				curr_entity->tag = tag;
				curr_entity->length = length;
				curr_entity->value = tmpbuf;
				curr_entity->_next = NULL;

				break;
			case GSCIS_TAG_CERTIFICATE:
				curr_entity = malloc(sizeof(*curr_entity));

#ifdef HAVE_LIBZ
				tmpbuflen = length * 2;
				tmpbuf = malloc(tmpbuflen);

				uncompress_ret = uncompress(tmpbuf, &tmpbuflen, vval, length);
				if (uncompress_ret != Z_OK) {
					CACKEY_DEBUG_PRINTF("Failed to decompress, uncompress() returned %i -- resorting to direct copy", uncompress_ret);

					tmpbuflen = length;
					memcpy(tmpbuf, vval, length);
				}

				CACKEY_DEBUG_PRINTBUF("Decompressed to:", tmpbuf, tmpbuflen);
#else
				CACKEY_DEBUG_PRINTF("Missing ZLIB Support, this certificate is likely useless...");

				tmpbuflen = length;
				memcpy(tmpbuf, vval, length);
#endif

				curr_entity->tag = tag;
				curr_entity->length = tmpbuflen;
				curr_entity->value = tmpbuf;
				curr_entity->_next = NULL;

				break;
			case GSCIS_TAG_PKCS15:
				curr_entity = malloc(sizeof(*curr_entity));

				curr_entity->tag = tag;
				curr_entity->value_byte = vval[0];
				curr_entity->_next = NULL;

				break;
		}

		vval += length;
		vlen -= length;

		if (curr_entity != NULL) {
			if (root == NULL) {
				root = curr_entity;
			}

			if (last != NULL) {
				last->_next = curr_entity;
			}

			last = curr_entity;
		}
	}

	return(root);
}

/*
 * SYNPOSIS
 *     ...
 *
 * ARGUMENTS
 *     ...
 *
 * RETURN VALUE
 *     ...
 *
 * NOTES
 *     ...
 *
 */
static void cackey_free_certs(struct cackey_pcsc_identity *start, size_t count, int free_start) {
	size_t idx;

	if (start == NULL) {
		return;
	}

	for (idx = 0; idx < count; idx++) {
		if (start[idx].certificate) {
			free(start[idx].certificate);
		}
	}

	if (free_start) {
		free(start);
	}

	return;
}

static struct cackey_pcsc_identity *cackey_copy_certs(struct cackey_pcsc_identity *dest, struct cackey_pcsc_identity *start, size_t count) {
	size_t idx;

	if (start == NULL) {
		return(NULL);
	}

	if (dest == NULL) {
		dest = malloc(sizeof(*dest) * count);
	}

	for (idx = 0; idx < count; idx++) {
		dest[idx].id_type = start[idx].id_type;

		switch (dest[idx].id_type) {
			case CACKEY_ID_TYPE_CAC:
				memcpy(dest[idx].card.cac.applet, start[idx].card.cac.applet, sizeof(dest[idx].card.cac.applet));
				dest[idx].card.cac.file = start[idx].card.cac.file;
				break;
			case CACKEY_ID_TYPE_PIV:
				dest[idx].card.piv.key_id = start[idx].card.piv.key_id;
				memcpy(dest[idx].card.piv.label, start[idx].card.piv.label, sizeof(dest[idx].card.piv.label));
				break;
			case CACKEY_ID_TYPE_CERT_ONLY:
				break;
		}
		dest[idx].certificate_len = start[idx].certificate_len;
		dest[idx].keysize = start[idx].keysize;

		dest[idx].certificate = malloc(dest[idx].certificate_len);
		memcpy(dest[idx].certificate, start[idx].certificate, dest[idx].certificate_len);
	}

	return(dest);
}

/*
 * SYNPOSIS
 *     ...
 *
 * ARGUMENTS
 *     ...
 *
 * RETURN VALUE
 *     ...
 *
 * NOTES
 *     ...
 *
 */
static struct cackey_pcsc_identity *cackey_read_certs(struct cackey_slot *slot, struct cackey_pcsc_identity *certs, unsigned long *count) {
	cackey_pcsc_id_type check_id_type;
	struct cackey_pcsc_identity *curr_id;
	struct cackey_tlv_entity *ccc_tlv, *ccc_curr, *app_tlv, *app_curr;
	unsigned char *piv_oid, piv_oid_pivauth[] = {NISTSP800_73_3_OID_PIVAUTH}, piv_oid_signature[] = {NISTSP800_73_3_OID_SIGNATURE}, piv_oid_keymgt[] = {NISTSP800_73_3_OID_KEYMGT};
	unsigned char curr_aid[7];
	unsigned char buffer[8192], *buffer_p, *tmpbuf;
	unsigned long outidx = 0;
	char *piv_label;
	cackey_ret transaction_ret;
	ssize_t read_ret;
	size_t buffer_len, tmpbuflen;
	int certs_resizable;
	int send_ret, select_ret;
	int piv_key, piv = 0;
	int cached_certs_valid;
	int idx;
	cackey_pcsc_id_type id_type;
#ifdef HAVE_LIBZ
	int uncompress_ret;
	z_stream gzip_stream;
#endif

	CACKEY_DEBUG_PRINTF("Called.");

	if (count == NULL) {
		CACKEY_DEBUG_PRINTF("count is NULL, returning in failure");

		return(NULL);
	}

	if (certs != NULL) {
		if (*count == 0) {
			CACKEY_DEBUG_PRINTF("Requested we return 0 objects, short-circuit");

			return(certs);
		}
	}

	cached_certs_valid = 0;
	if (!slot->slot_reset) {
		if (slot->cached_certs) {
			cached_certs_valid = 1;

			if (slot->cached_certs_count > 0) {
				cached_certs_valid = 1;
			}
		}
	}

	if (cached_certs_valid) {
		if (certs == NULL) {
			certs = malloc(sizeof(*certs) * slot->cached_certs_count);
			*count = slot->cached_certs_count;
		} else {
			if (*count > slot->cached_certs_count) {
				*count = slot->cached_certs_count;
			}
		}

		cackey_copy_certs(certs, slot->cached_certs, *count);

		CACKEY_DEBUG_PRINTF("Returning cached certificates for this slot (card has not been reset and there are cached certs available)");

		return(certs);
	}

	if (slot->cached_certs) {
		cackey_free_certs(slot->cached_certs, slot->cached_certs_count, 1);

		slot->cached_certs = NULL;
	}

	/* Begin a SmartCard transaction */
	transaction_ret = cackey_begin_transaction(slot);
	if (transaction_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("Unable begin transaction, returning in failure");

		return(NULL);
	}

	id_type = cackey_detect_and_select_root_applet(slot, CACKEY_ID_TYPE_UNKNOWN);

	switch (id_type) {
		case CACKEY_ID_TYPE_CAC:
			piv = 0;

			break;
		case CACKEY_ID_TYPE_PIV:
			piv = 1;

			break;
		case CACKEY_ID_TYPE_ERROR:
			/* Terminate SmartCard Transaction */
			cackey_end_transaction(slot);

			if (certs == NULL) {
				*count = 0;
			}

			return(NULL);

			break;
		case CACKEY_ID_TYPE_CERT_ONLY:
			CACKEY_DEBUG_PRINTF("Error.  Impossible condition reached.");

			break;
	}

	if (certs == NULL) {
		certs = malloc(sizeof(*certs) * 5);
		*count = 5;
		certs_resizable = 1;
	} else {
		certs_resizable = 0;
	}

	if (piv) {
		for (idx = 0; idx < 3; idx++) {
			switch (idx) {
				case 0:
					piv_oid = piv_oid_pivauth;
					piv_key = NISTSP800_78_3_KEY_PIVAUTH;
					piv_label = "Authentication";
					break;
				case 1:
					piv_oid = piv_oid_signature;
					piv_key = NISTSP800_78_3_KEY_SIGNATURE;
					piv_label = "Signature";
					break;
				case 2:
					piv_oid = piv_oid_keymgt;
					piv_key = NISTSP800_78_3_KEY_KEYMGT;
					piv_label = "Key Management";
					break;
			}

			read_ret = cackey_get_data(slot, buffer, sizeof(buffer), piv_oid);

			if (read_ret <= 0) {
				continue;
			}

			curr_id = &certs[outidx];
			outidx++;

			curr_id->keysize = -1;
			curr_id->id_type = CACKEY_ID_TYPE_PIV;
			curr_id->card.piv.key_id = piv_key;
			memcpy(curr_id->card.piv.label, piv_label, strlen(piv_label) + 1);

			curr_id->certificate_len = read_ret;
			curr_id->certificate = malloc(curr_id->certificate_len);

			buffer_len = sizeof(buffer);
			buffer_p = cackey_read_bertlv_tag(buffer, &buffer_len, 0x70, curr_id->certificate, &curr_id->certificate_len);

			if (buffer_p == NULL) {
				CACKEY_DEBUG_PRINTF("Reading certificate from BER-TLV response failed, skipping key %i", idx);

				free(curr_id->certificate);

				curr_id->certificate = NULL;

				outidx--;

				continue;
			}

			if (curr_id->certificate_len > 4) {
				if (memcmp(curr_id->certificate, "\x1f\x8b\x08\x00", 4) == 0) {
#ifdef HAVE_LIBZ
					tmpbuflen = curr_id->certificate_len * 2;
					tmpbuf = malloc(tmpbuflen);

					CACKEY_DEBUG_PRINTBUF("Attempting to decompress:", curr_id->certificate, curr_id->certificate_len);

					gzip_stream.zalloc = NULL;
					gzip_stream.zfree = NULL;
					gzip_stream.opaque = NULL;

					gzip_stream.next_in  = curr_id->certificate;
					gzip_stream.avail_in = curr_id->certificate_len;
					gzip_stream.next_out = tmpbuf;
					gzip_stream.avail_out = tmpbuflen;

					uncompress_ret = inflateInit(&gzip_stream);
					if (uncompress_ret == Z_OK) {
						/* Try again as a gzip buffer */
						uncompress_ret = inflateEnd(&gzip_stream);
						if (uncompress_ret == Z_OK) {
							gzip_stream.zalloc = NULL;
							gzip_stream.zfree = NULL;
							gzip_stream.opaque = NULL;

							gzip_stream.next_in  = curr_id->certificate;
							gzip_stream.avail_in = curr_id->certificate_len;
							gzip_stream.next_out = tmpbuf;
							gzip_stream.avail_out = tmpbuflen;
							uncompress_ret = inflateInit2(&gzip_stream, 31);
						}
					}
					if (uncompress_ret == Z_OK) {
						uncompress_ret = inflate(&gzip_stream, 0);
					}
					if (uncompress_ret == Z_STREAM_END) {
						uncompress_ret = inflateEnd(&gzip_stream);
					} else {
						uncompress_ret = Z_DATA_ERROR;
					}
					if (uncompress_ret == Z_OK) {
						tmpbuflen = gzip_stream.total_out;

						CACKEY_DEBUG_PRINTBUF("Decompressed to:", tmpbuf, tmpbuflen);

						free(curr_id->certificate);

						curr_id->certificate = tmpbuf;
						curr_id->certificate_len = tmpbuflen;
					} else {
						CACKEY_DEBUG_PRINTF("Decompressing failed! uncompress() returned %i", uncompress_ret);

						free(tmpbuf);
					}
#else
					CACKEY_DEBUG_PRINTF("Error.  We got a compressed certificate but we do not have zlib.  Hoping for the best.");
#endif
				}
			}
		}
	} else {
		/* Read all the applets from the CCC's TLV */
		ccc_tlv = cackey_read_tlv(slot);

		/* Look for CARDURLs that coorespond to PKI applets */
		for (ccc_curr = ccc_tlv; ccc_curr; ccc_curr = ccc_curr->_next) {
			CACKEY_DEBUG_PRINTF("Found tag: %s ... ", CACKEY_DEBUG_FUNC_TAG_TO_STR(ccc_curr->tag));

			if (ccc_curr->tag != GSCIS_TAG_CARDURL) {
				CACKEY_DEBUG_PRINTF("  ... skipping it (we only care about CARDURLs)");

				continue;
			}

			if ((ccc_curr->value_cardurl->apptype & CACKEY_TLV_APP_PKI) != CACKEY_TLV_APP_PKI) {
				CACKEY_DEBUG_PRINTF("  ... skipping it (we only care about PKI applets, this applet supports: %s/%02x)", CACKEY_DEBUG_FUNC_APPTYPE_TO_STR(ccc_curr->value_cardurl->apptype), (unsigned int) ccc_curr->value_cardurl->apptype);

				continue;
			}

			CACKEY_DEBUG_PRINTBUF("RID:", ccc_curr->value_cardurl->rid, sizeof(ccc_curr->value_cardurl->rid));
			CACKEY_DEBUG_PRINTF("AppID = %s/%04lx", CACKEY_DEBUG_FUNC_OBJID_TO_STR(ccc_curr->value_cardurl->appid), (unsigned long) ccc_curr->value_cardurl->appid);
			CACKEY_DEBUG_PRINTF("ObjectID = %s/%04lx", CACKEY_DEBUG_FUNC_OBJID_TO_STR(ccc_curr->value_cardurl->objectid), (unsigned long) ccc_curr->value_cardurl->objectid);

			memcpy(curr_aid, ccc_curr->value_cardurl->rid, sizeof(ccc_curr->value_cardurl->rid));
			curr_aid[sizeof(curr_aid) - 2] = (ccc_curr->value_cardurl->appid >> 8) & 0xff;
			curr_aid[sizeof(curr_aid) - 1] = ccc_curr->value_cardurl->appid & 0xff;

			/* Select found applet ... */
			select_ret = cackey_select_applet(slot, curr_aid, sizeof(curr_aid));
			if (select_ret != CACKEY_PCSC_S_OK) {
				CACKEY_DEBUG_PRINTF("Failed to select applet, skipping processing of this object");

				continue;
			}

			/* ... and object (file) */
			select_ret = cackey_select_file(slot, ccc_curr->value_cardurl->objectid);
			if (select_ret != CACKEY_PCSC_S_OK) {
				CACKEY_DEBUG_PRINTF("Failed to select file, skipping processing of this object");

				continue;
			}

			/* Process this file's TLV looking for certificates */
			app_tlv = cackey_read_tlv(slot);
	
			for (app_curr = app_tlv; app_curr; app_curr = app_curr->_next) {
				CACKEY_DEBUG_PRINTF("Found tag: %s", CACKEY_DEBUG_FUNC_TAG_TO_STR(app_curr->tag));
				if (app_curr->tag != GSCIS_TAG_CERTIFICATE) {
					CACKEY_DEBUG_PRINTF("  ... skipping it (we only care about CERTIFICATEs)");

					continue;
				}

				curr_id = &certs[outidx];
				outidx++;

				curr_id->id_type = CACKEY_ID_TYPE_CAC;
				memcpy(curr_id->card.cac.applet, curr_aid, sizeof(curr_id->card.cac.applet));
				curr_id->card.cac.file = ccc_curr->value_cardurl->objectid;
				curr_id->keysize = -1;

				CACKEY_DEBUG_PRINTF("Filling curr_id->card.cac.applet (%p) with %lu bytes:", curr_id->card.cac.applet, (unsigned long) sizeof(curr_id->card.cac.applet));
				CACKEY_DEBUG_PRINTBUF("VAL:", curr_id->card.cac.applet, sizeof(curr_id->card.cac.applet));

				curr_id->certificate_len = app_curr->length;

				curr_id->certificate = malloc(curr_id->certificate_len);
				memcpy(curr_id->certificate, app_curr->value, curr_id->certificate_len);

				if (outidx >= *count) {
					if (certs_resizable) {
						*count *= 2;
						if (*count != 0) {
							certs = realloc(certs, sizeof(*certs) * (*count));
						} else {
							certs = NULL;
						}
					} else {
						break;
					}
				}
			}

			cackey_free_tlv(app_tlv);

			if (outidx >= *count) {
				break;
			}
		}

		cackey_free_tlv(ccc_tlv);
	}

	*count = outidx;

	if (certs_resizable) {
		if (*count != 0) {
			certs = realloc(certs, sizeof(*certs) * (*count));
		} else {
			free(certs);

			certs = NULL;
		}
	}

	slot->cached_certs = cackey_copy_certs(NULL, certs, *count);
	slot->cached_certs_count = *count;

	/* Terminate SmartCard Transaction */
	cackey_end_transaction(slot);

	return(certs);
}

/*
 * SYNPOSIS
 *     ...
 *
 * ARGUMENTS
 *     ...
 *
 * RETURN VALUE
 *     ...
 *
 * NOTES
 *     ...
 *
 */
static ssize_t cackey_signdecrypt(struct cackey_slot *slot, struct cackey_identity *identity, unsigned char *buf, size_t buflen, unsigned char *outbuf, size_t outbuflen, int padInput, int unpadOutput) {
	cackey_pcsc_id_type id_type, check_id_type;
	unsigned char dyn_auth_template[10], *dyn_auth_tmpbuf;
	unsigned char *tmpbuf, *tmpbuf_s, *outbuf_s, *outbuf_p;
	unsigned char bytes_to_send, p1, class;
	unsigned char blocktype;
	cackey_ret send_ret;
	uint16_t respcode;
	ssize_t retval = 0, unpadoffset;
	size_t tmpbuflen, padlen, tmpoutbuflen, outbuf_len;
	int free_tmpbuf = 0;
	int le;

	CACKEY_DEBUG_PRINTF("Called.");

	if (slot == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  slot is NULL");

		return(-1);
	}

	if (buf == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  buf is NULL");

		return(-1);
	}

	if (outbuf == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  outbuf is NULL");

		return(-1);
	}

	if (identity == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  identity is NULL");

		return(-1);
	}

	if (identity->pcsc_identity == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  identity->pcsc_identity is NULL");

		return(-1);
	}

	id_type = identity->pcsc_identity->id_type;
	if (id_type == CACKEY_ID_TYPE_CERT_ONLY) {
		CACKEY_DEBUG_PRINTF("Error.  identity->pcsc_identity is CACKEY_ID_TYPE_CERT_ONLY, which cannot be used for sign/decrypt");

		return(-1);
	}

	switch (id_type) {
		case CACKEY_ID_TYPE_PIV:
		case CACKEY_ID_TYPE_CAC:
			break;
		default:
			CACKEY_DEBUG_PRINTF("Error.  identity->pcsc_identity is not a supported value. Type is: 0x%lx (PIV = 0x%lx, CAC = 0x%lx)", (unsigned long) id_type, (unsigned long) CACKEY_ID_TYPE_PIV, (unsigned long) CACKEY_ID_TYPE_CAC);

			return(-1);
	}

	/* Determine identity Key size */
	if (identity->pcsc_identity->keysize < 0) {
		identity->pcsc_identity->keysize = x509_to_keysize(identity->pcsc_identity->certificate, identity->pcsc_identity->certificate_len);
	}

	/* Pad message to key size */
	if (padInput) {
		if (identity->pcsc_identity->keysize > 0) {
			if (buflen != identity->pcsc_identity->keysize) {
				if (buflen > (identity->pcsc_identity->keysize - 3)) {
					CACKEY_DEBUG_PRINTF("Error.  Message is too large to sign/decrypt");

					return(-1);
				}

				tmpbuflen = identity->pcsc_identity->keysize;
				tmpbuf = malloc(tmpbuflen);
				free_tmpbuf = 1;

				padlen = tmpbuflen - buflen - 3;

				CACKEY_DEBUG_PRINTF("Need to pad the buffer with %llu bytes (tmpbuflen = %llu, buflen = %llu)", (unsigned long long) padlen, (unsigned long long) tmpbuflen, (unsigned long long) buflen);

				/* RSA PKCS#1 EMSA-PKCS1-v1_5 Padding */
				tmpbuf[0] = 0x00;
				tmpbuf[1] = 0x01;
				memset(&tmpbuf[2], 0xFF, padlen);
				tmpbuf[padlen + 2]= 0x00;
				memcpy(&tmpbuf[padlen + 3], buf, buflen);

				CACKEY_DEBUG_PRINTBUF("Unpadded:", buf, buflen);
				CACKEY_DEBUG_PRINTBUF("Padded:", tmpbuf, tmpbuflen);
			} else {
				tmpbuf = buf;
				tmpbuflen = buflen;
				free_tmpbuf = 0;
				padlen = 0;
			}
		} else {
			CACKEY_DEBUG_PRINTF("Unable to determine key size, hoping the message is properly padded!");

			tmpbuf = buf;
			tmpbuflen = buflen;
			free_tmpbuf = 0;
			padlen = 0;
		}
	} else {
		tmpbuf = buf;
		tmpbuflen = buflen;
		free_tmpbuf = 0;
		padlen = 0;
	}

	/* Begin transaction */
	cackey_begin_transaction(slot);

	/* Select correct applet */
	switch (id_type) {
		case CACKEY_ID_TYPE_CAC:
			CACKEY_DEBUG_PRINTF("Selecting applet found at %p ...", identity->pcsc_identity->card.cac.applet);
			cackey_select_applet(slot, identity->pcsc_identity->card.cac.applet, sizeof(identity->pcsc_identity->card.cac.applet));

			/* Select correct file */
			cackey_select_file(slot, identity->pcsc_identity->card.cac.file);
			break;
		case CACKEY_ID_TYPE_PIV:
			dyn_auth_template[0] = 0x7C;
			dyn_auth_template[1] = 0x82;
			dyn_auth_template[2] = ((tmpbuflen + 6) & 0xff00) >> 8;
			dyn_auth_template[3] = (tmpbuflen + 6) & 0x00ff;
			dyn_auth_template[4] = 0x82;
			dyn_auth_template[5] = 0x00;
			dyn_auth_template[6] = 0x81;
			dyn_auth_template[7] = 0x82;
			dyn_auth_template[8] = (tmpbuflen & 0xff00) >> 8;
			dyn_auth_template[9] = tmpbuflen & 0x00ff;

			dyn_auth_tmpbuf = malloc(tmpbuflen + sizeof(dyn_auth_template));
			memcpy(dyn_auth_tmpbuf, dyn_auth_template, sizeof(dyn_auth_template));
			memcpy(dyn_auth_tmpbuf + sizeof(dyn_auth_template), tmpbuf, tmpbuflen);

			if (free_tmpbuf) {
				free(tmpbuf);
			}

			tmpbuflen += sizeof(dyn_auth_template);
			tmpbuf = dyn_auth_tmpbuf;
			free_tmpbuf = 1;

			break;
		case CACKEY_ID_TYPE_CERT_ONLY:
			break;
	}

	tmpbuf_s = tmpbuf;
	outbuf_s = outbuf;
	while (tmpbuflen) {
		tmpoutbuflen = outbuflen;

		if (tmpbuflen > CACKEY_APDU_MTU) {
			bytes_to_send = CACKEY_APDU_MTU;
		} else {
			bytes_to_send = tmpbuflen;
		}

		send_ret = CACKEY_PCSC_E_GENERIC;
		switch (id_type) {
			case CACKEY_ID_TYPE_CAC:
				if (tmpbuflen > CACKEY_APDU_MTU) {
					p1 = 0x80;
					le = 0x00;
				} else {
					p1 = 0x00;
					le = 0x00;
				}

				send_ret = cackey_send_apdu(slot, GSCIS_CLASS_GLOBAL_PLATFORM, GSCIS_INSTR_SIGNDECRYPT, p1, 0x00, bytes_to_send, tmpbuf, le, &respcode, outbuf, &tmpoutbuflen);
				break;
			case CACKEY_ID_TYPE_PIV:
				if (tmpbuflen > CACKEY_APDU_MTU) {
					class = 0x10;
					le = 0x00;
				} else {
					class = GSCIS_CLASS_ISO7816;
					le = 256;
				}

				send_ret = cackey_send_apdu(slot, class, NISTSP800_73_3_INSTR_GENAUTH, NISTSP800_78_3_ALGO_RSA2048, identity->pcsc_identity->card.piv.key_id, bytes_to_send, tmpbuf, le, &respcode, outbuf, &tmpoutbuflen);
				break;
			case CACKEY_ID_TYPE_CERT_ONLY:
				break;
		}

		if (send_ret != CACKEY_PCSC_S_OK) {
			if (free_tmpbuf) {
				if (tmpbuf_s) {
					free(tmpbuf_s);
				}
			}

			/* End transaction */
			cackey_end_transaction(slot);

			if (send_ret == CACKEY_PCSC_E_RETRY) {
				CACKEY_DEBUG_PRINTF("ADPU Sending Failed -- retrying.");

				return(cackey_signdecrypt(slot, identity, buf, buflen, outbuf, outbuflen, padInput, unpadOutput));
			}

			CACKEY_DEBUG_PRINTF("ADPU Sending Failed -- returning in error.");

			if (respcode == 0x6982 || respcode == 0x6e00 || respcode == 0x6d00) {
				if (respcode == 0x6e00) {
					CACKEY_DEBUG_PRINTF("Got \"WRONG CLASS\", this means we are talking to the wrong object (likely because the card went away) -- resetting");
				} else if (respcode == 0x6d00) {
					CACKEY_DEBUG_PRINTF("Got \"INVALID INSTRUCTION\", this means we are talking to the wrong object (likely because the card went away) -- resetting");
				} else {
					CACKEY_DEBUG_PRINTF("Security status not satisified (respcode = 0x%04x).  Returning NEEDLOGIN", (int) respcode);
				}

				cackey_mark_slot_reset(slot);

				cackey_detect_and_select_root_applet(slot, CACKEY_ID_TYPE_UNKNOWN);

				slot->token_flags = CKF_LOGIN_REQUIRED;

				return(CACKEY_PCSC_E_NEEDLOGIN);
			}

			if (send_ret == CACKEY_PCSC_E_TOKENABSENT) {
				CACKEY_DEBUG_PRINTF("Token absent.  Returning TOKENABSENT");

				cackey_mark_slot_reset(slot);

				return(CACKEY_PCSC_E_TOKENABSENT);
			}

			CACKEY_DEBUG_PRINTF("Something went wrong during signing, resetting the slot and hoping for the best.");

			cackey_pcsc_disconnect();

			cackey_pcsc_connect();

			cackey_detect_and_select_root_applet(slot, CACKEY_ID_TYPE_UNKNOWN);

			return(CACKEY_PCSC_E_GENERIC);
		}

		tmpbuf += bytes_to_send;
		tmpbuflen -= bytes_to_send;

		outbuf += tmpoutbuflen;
		outbuflen -= tmpoutbuflen;
		retval += tmpoutbuflen;
	}

	if (free_tmpbuf) {
		if (tmpbuf_s) {
			free(tmpbuf_s);
		}
	}

	outbuf = outbuf_s;

	/* End transaction */
	cackey_end_transaction(slot);

#ifdef CACKEY_PARANOID
#  ifdef _POSIX_SSIZE_MAX
	if (outbuflen > _POSIX_SSIZE_MAX) {
		CACKEY_DEBUG_PRINTF("Outbuflen exceeds maximum value, returning in failure. (max = %li, outbuflen = %lu)", (long) _POSIX_SSIZE_MAX, (unsigned long) outbuflen);

		return(-1);
	}
#  endif
#endif

	/* We must remove the "7C" tag to get to the signature */
	switch (id_type) {
		case CACKEY_ID_TYPE_PIV:
			outbuf_len = retval;
			outbuf_p = cackey_read_bertlv_tag(outbuf, &outbuf_len, 0x7C, NULL,  &outbuf_len);
			if (outbuf_p == NULL) {
				CACKEY_DEBUG_PRINTF("Response from PIV for GENERATE AUTHENTICATION was not a 0x7C tag, returning in failure");

				return(-1);
			}

			retval = outbuf_len;

			outbuf_len = retval;
			outbuf_p = cackey_read_bertlv_tag(outbuf, &outbuf_len, 0x82, NULL,  &outbuf_len);
			if (outbuf_p == NULL) {
				CACKEY_DEBUG_PRINTF("Response from PIV for GENERATE AUTHENTICATION was not a 0x82 within a 0x7C tag, returning in failure");

				return(-1);
			}

			retval = outbuf_len;

			break;
		case CACKEY_ID_TYPE_CAC:
		case CACKEY_ID_TYPE_CERT_ONLY:
			break;
	}

	/* Unpad reply */
	if (unpadOutput) {
		if (retval < 3) {
			CACKEY_DEBUG_PRINTF("Reply is too small, we are not able to unpad -- passing back and hoping for the best!");

			CACKEY_DEBUG_PRINTF("Returning in success, retval = %li (bytes)", (long) retval);
			return(retval);
		}

		if (outbuf[0] != 0x00) {
			CACKEY_DEBUG_PRINTF("Unrecognized padding scheme -- passing back and hoping for the best!");

			CACKEY_DEBUG_PRINTF("Returning in success, retval = %li (bytes)", (long) retval);
			return(retval);
		}

		blocktype = outbuf[1];
		unpadoffset = 0;

		switch (blocktype) {
			case 0x00:
				/* Padding Scheme 1, the first non-zero byte is the start of data */
				for (unpadoffset = 2; unpadoffset < retval; unpadoffset++) {
					if (outbuf[unpadoffset] != 0x00) {
						break;
					}
				}
				break;
			case 0x01:
				/* Padding Scheme 2, pad bytes are 0xFF followed by 0x00 */
				for (unpadoffset = 2; unpadoffset < retval; unpadoffset++) {
					if (outbuf[unpadoffset] != 0xFF) {
						if (outbuf[unpadoffset] == 0x00) {
							unpadoffset++;

							break;
						} else {
							CACKEY_DEBUG_PRINTF("Invalid padding data found, returning in failure, should have been 0x00 found 0x%02x", (unsigned int) outbuf[unpadoffset]);

							return(-1);
						}
					} else {
						CACKEY_DEBUG_PRINTF("Invalid padding data found, returning in failure, should have been 0xFF found 0x%02x", (unsigned int) outbuf[unpadoffset]);

						return(-1);
					}
				}
				break;
			case 0x02:
				/* Padding Scheme 3, pad bytes are non-zero first zero byte found is the seperator byte */
				for (unpadoffset = 2; unpadoffset < retval; unpadoffset++) {
					if (outbuf[unpadoffset] == 0x00) {
						unpadoffset++;

						break;
					}
				}
				break;
		}

		if (unpadoffset > retval) {
			CACKEY_DEBUG_PRINTF("Offset greater than reply size, aborting.  (unpadoffset = %lu, retval = %lu)", (unsigned long) unpadoffset, (unsigned long) retval);

			return(-1);
		}

		CACKEY_DEBUG_PRINTBUF("Padded:", outbuf, retval);

		retval -= unpadoffset;
		memmove(outbuf, outbuf + unpadoffset, retval);

		CACKEY_DEBUG_PRINTBUF("Unpadded:", outbuf, retval);
	}


	CACKEY_DEBUG_PRINTF("Returning in success, retval = %li (bytes)", (long) retval);

	return(retval);
}

/*
 * SYNPOSIS
 *     ...
 *
 * ARGUMENTS
 *     ...
 *
 * RETURN VALUE
 *     ...
 *
 * NOTES
 *     ...
 *
 */
static cackey_ret cackey_token_present(struct cackey_slot *slot) {
	cackey_ret pcsc_connect_ret;
	DWORD reader_len = 0, state = 0, protocol = 0, atr_len;
	BYTE atr[MAX_ATR_SIZE];
	LONG status_ret, scard_reconn_ret;
	LPSTR reader_name;

	CACKEY_DEBUG_PRINTF("Called.");

	if (slot->internal) {
		CACKEY_DEBUG_PRINTF("Returning token present (internal token)");

		return(CACKEY_PCSC_S_TOKENPRESENT);
	}

	pcsc_connect_ret = cackey_connect_card(slot);
	if (pcsc_connect_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("Unable to connect to card, returning token absent");

		return(CACKEY_PCSC_E_TOKENABSENT);
	}

	CACKEY_DEBUG_PRINTF("Calling SCardStatus() to determine card status");

	atr_len = sizeof(atr);
	status_ret = SCardStatus(slot->pcsc_card, NULL, &reader_len, &state, &protocol, atr, &atr_len);
 
	if (status_ret == SCARD_E_INSUFFICIENT_BUFFER) {
		CACKEY_DEBUG_PRINTF("ScardStatus() returned SCARD_E_INSUFFICIENT_BUFFER, assuming this is a bug (e.g., Google PCSC) implementation and retrying");

		atr_len = sizeof(atr);

		reader_len = 32768;
		reader_name = malloc(reader_len);

		status_ret = SCardStatus(slot->pcsc_card, reader_name, &reader_len, &state, &protocol, atr, &atr_len);

		free(reader_name);
	}

	if (status_ret == SCARD_E_INVALID_HANDLE) {
		CACKEY_DEBUG_PRINTF("SCardStatus() returned SCARD_E_INVALID_HANDLE, marking as not already connected and trying again");
		cackey_mark_slot_reset(slot);

		pcsc_connect_ret = cackey_connect_card(slot);
		if (pcsc_connect_ret != CACKEY_PCSC_S_OK) {
			CACKEY_DEBUG_PRINTF("Unable to connect to card, returning token absent");

			return(CACKEY_PCSC_E_TOKENABSENT);
		}

		CACKEY_DEBUG_PRINTF("Calling SCardStatus() again");

		atr_len = sizeof(atr);
		status_ret = SCardStatus(slot->pcsc_card, NULL, &reader_len, &state, &protocol, atr, &atr_len);
	}

	if (status_ret != SCARD_S_SUCCESS) {
		cackey_mark_slot_reset(slot);

		if (status_ret == SCARD_W_RESET_CARD) {
			CACKEY_DEBUG_PRINTF("Reset required, please hold...");

			scard_reconn_ret = cackey_reconnect_card(slot, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1);
			if (scard_reconn_ret == SCARD_S_SUCCESS) {
				/* Re-establish transaction, if it was present */
				if (slot->transaction_depth > 0) {
					slot->transaction_depth--;
					slot->transaction_need_hw_lock = 1;
					cackey_begin_transaction(slot);
				}

				CACKEY_DEBUG_PRINTF("Reset successful, requerying");
				status_ret = SCardStatus(slot->pcsc_card, NULL, &reader_len, &state, &protocol, atr, &atr_len);
				if (status_ret != SCARD_S_SUCCESS) {
					CACKEY_DEBUG_PRINTF("Still unable to query card status, returning token absent.  SCardStatus() = %s", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(status_ret));

					return(CACKEY_PCSC_E_TOKENABSENT);
				}
			} else {
				CACKEY_DEBUG_PRINTF("Unable to reconnect to card, returning token absent.  SCardReconnect() = %s", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_reconn_ret));

				return(CACKEY_PCSC_E_TOKENABSENT);
			}
		} else {
			CACKEY_DEBUG_PRINTF("Unable to query card status, returning token absent.  SCardStatus() = %s", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(status_ret));

			return(CACKEY_PCSC_E_TOKENABSENT);
		}
	}

	if ((state & SCARD_ABSENT) == SCARD_ABSENT) {
		CACKEY_DEBUG_PRINTF("Card is absent, returning token absent");

		return(CACKEY_PCSC_E_TOKENABSENT);
	}

	CACKEY_DEBUG_PRINTF("Returning token present.");

	return(CACKEY_PCSC_S_TOKENPRESENT);
}

/*
 * SYNPOSIS
 *     ...
 *
 * ARGUMENTS
 *     ...
 *
 * RETURN VALUE
 *     ...
 *
 * NOTES
 *     ...
 *
 */
static cackey_ret cackey_set_pin(struct cackey_slot *slot, unsigned char *old_pin, unsigned long old_pin_len, unsigned char *pin, unsigned long pin_len) {
	struct cackey_pcsc_identity *pcsc_identities;
	unsigned char cac_pin[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
	unsigned char old_cac_pin[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
	unsigned char pin_update[sizeof(cac_pin) + sizeof(old_cac_pin)];
	unsigned long num_certs;
	uint16_t response_code;
	int tries_remaining;
	int send_ret;
	int key_reference = 0x00;

	/* Apparently, CAC PINs are *EXACTLY* 8 bytes long -- pad with 0xFF if too short */
	if (pin_len >= 8) {
		memcpy(cac_pin, pin, 8);
	} else {
		memcpy(cac_pin, pin, pin_len);
	}

	if (old_pin_len >= 8) {
		memcpy(old_cac_pin, old_pin, 8);
	} else {
		memcpy(old_cac_pin, old_pin, old_pin_len);
	}

	/* Concatenate both PINs together to send as a single instruction */
	memcpy(pin_update, old_cac_pin, sizeof(old_cac_pin));
	memcpy(pin_update + sizeof(old_cac_pin), cac_pin, sizeof(cac_pin));

	/* Reject PINs which are too short */
	if (pin_len < 5) {
		CACKEY_DEBUG_PRINTF("Rejecting New PIN which is too short (length = %lu, must be atleast 5)", pin_len);

		return(CACKEY_PCSC_E_BADPIN);
	}

	if (old_pin_len < 5) {
		CACKEY_DEBUG_PRINTF("Rejecting Old PIN which is too short (length = %lu, must be atleast 5)", old_pin_len);

		return(CACKEY_PCSC_E_BADPIN);
	}

	/* PIV authentication uses a "key_reference" of 0x80 */
	pcsc_identities = cackey_read_certs(slot, NULL, &num_certs);
	if (num_certs > 0 && pcsc_identities != NULL) {
		switch (pcsc_identities[0].id_type) {
			case CACKEY_ID_TYPE_PIV:
				CACKEY_DEBUG_PRINTF("We have PIV card, so we will attempt to authenticate using the PIV Application key reference");

				key_reference = 0x80;
				break;
			default:
				break;
		}

		cackey_free_certs(pcsc_identities, num_certs, 1);
	}

	/* Issue a Set PIN (CHANGE REFERENCE) */
	send_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, GSCIS_INSTR_CHANGE_REFERENCE, 0x00, key_reference, sizeof(pin_update), pin_update, 0x00, &response_code, NULL, NULL);

	if (send_ret != CACKEY_PCSC_S_OK) {
		if ((response_code & 0x63C0) == 0x63C0) {
			tries_remaining = (response_code & 0xF);

			CACKEY_DEBUG_PRINTF("PIN Verification failed, %i tries remaining", tries_remaining);

			return(CACKEY_PCSC_E_BADPIN);
		}

		if (response_code == 0x6983) {
			CACKEY_DEBUG_PRINTF("Unable to set PIN, device is locked or changing the PIN is disabled");

			return(CACKEY_PCSC_E_LOCKED);
		}

		return(CACKEY_PCSC_E_GENERIC);
	}

	CACKEY_DEBUG_PRINTF("PIN Change succeeded");

	return(CACKEY_PCSC_S_OK);

	/* Disable a warning, since this is only used in debug mode */
	tries_remaining = tries_remaining;
}

/*
 * SYNPOSIS
 *     ...
 *
 * ARGUMENTS
 *     ...
 *
 * RETURN VALUE
 *     ...
 *
 * NOTES
 *     ...
 *
 */
static cackey_ret cackey_login(struct cackey_slot *slot, unsigned char *pin, unsigned long pin_len, int *tries_remaining_p, int retries) {
	struct cackey_pcsc_identity *pcsc_identities;
	unsigned char cac_pin[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
	unsigned long num_certs;
	uint16_t response_code;
	int tries_remaining;
	int send_ret;
	int key_reference = 0x00, have_piv = 0;
	cackey_ret connect_ret, token_ret;

	/* Indicate that we do not know about how many tries are remaining */
	if (tries_remaining_p) {
		*tries_remaining_p = -1;
	}

	/* Apparently, CAC PINs are *EXACTLY* 8 bytes long -- pad with 0xFF if too short */
	if (pin_len >= 8) {
		memcpy(cac_pin, pin, 8);
	} else {
		memcpy(cac_pin, pin, pin_len);
	}

	/* Reject PINs which are too short */
	if (pin_len < 5) {
		CACKEY_DEBUG_PRINTF("Rejecting PIN which is too short (length = %lu, must be atleast 5)", pin_len);

		return(CACKEY_PCSC_E_BADPIN);
	}

	/* PIV authentication uses a "key_reference" of 0x80 */
	pcsc_identities = cackey_read_certs(slot, NULL, &num_certs);
	if (num_certs > 0 && pcsc_identities != NULL) {
		switch (pcsc_identities[0].id_type) {
			case CACKEY_ID_TYPE_PIV:
				CACKEY_DEBUG_PRINTF("We have PIV card, so we will attempt to authenticate using the PIV Application key reference");

				have_piv = 1;
				break;
			default:
				break;
		}

		cackey_free_certs(pcsc_identities, num_certs, 1);
	}

	if (have_piv == 1) {
		key_reference = 0x80;
	}

	/* Issue PIN Verify */
	send_ret = cackey_send_apdu(slot, GSCIS_CLASS_ISO7816, GSCIS_INSTR_VERIFY, 0x00, key_reference, sizeof(cac_pin), cac_pin, 0x00, &response_code, NULL, NULL);

	if (send_ret != CACKEY_PCSC_S_OK) {
		if ((response_code & 0x63C0) == 0x63C0) {
			tries_remaining = (response_code & 0xF);

			CACKEY_DEBUG_PRINTF("PIN Verification failed, %i tries remaining", tries_remaining);

			if (tries_remaining_p) {
				*tries_remaining_p = tries_remaining;
			}

			return(CACKEY_PCSC_E_BADPIN);
		}

		if (response_code == 0x6983) {
			CACKEY_DEBUG_PRINTF("PIN Verification failed, device is locked");

			return(CACKEY_PCSC_E_LOCKED);
		}

		if (response_code == 0x6d00) {
			if (retries > 0) {
				CACKEY_DEBUG_PRINTF("Got ISO 7816 Response \"6D 00\" in response to a VERIFY request.");
				CACKEY_DEBUG_PRINTF("We did not expect this because it is not mentioned in NIST SP 800-73-3 Part 2 Section 3.2.1 or GSC-IS v2.1");
				CACKEY_DEBUG_PRINTF("We are going to try to reset the card and select the applet again.");

				if (num_certs > 0 && pcsc_identities != NULL) {
					cackey_detect_and_select_root_applet(slot, pcsc_identities[0].id_type);
				}

				cackey_mark_slot_reset(slot);

				connect_ret = cackey_connect_card(slot);
				if (connect_ret != CACKEY_PCSC_S_OK) {
					CACKEY_DEBUG_PRINTF("Unable to reconnect after resetting the card, returning in error.");

					return(connect_ret);
				}

				CACKEY_DEBUG_PRINTF("Verifying we still have a token.");
				token_ret = cackey_token_present(slot);
				if (token_ret != CACKEY_PCSC_S_TOKENPRESENT) {
					CACKEY_DEBUG_PRINTF("Token not present, returning in error.");

					return(token_ret);
				}

				CACKEY_DEBUG_PRINTF("Trying to login again");
				return(cackey_login(slot, pin, pin_len, tries_remaining_p, retries - 1));
			}
		}

		return(CACKEY_PCSC_E_GENERIC);
	}

	CACKEY_DEBUG_PRINTF("PIN Verification succeeded");

	return(CACKEY_PCSC_S_OK);
}

/*
 * SYNPOSIS
 *     ...
 *
 * ARGUMENTS
 *     ...
 *
 * RETURN VALUE
 *     ...
 *
 * NOTES
 *     ...
 *
 */
static ssize_t cackey_pcsc_identity_to_label(struct cackey_pcsc_identity *identity, unsigned char *label_buf, unsigned long label_buf_len) {
	unsigned long certificate_len;
	void *label_asn1;
	void *certificate;
	int x509_read_ret;

	certificate = identity->certificate;
	certificate_len = identity->certificate_len;

	if (certificate_len < 0) {
		return(-1);
	}

	x509_read_ret = x509_to_subject(certificate, certificate_len, (void **) &label_asn1);
	if (x509_read_ret < 0) {
		return(-1);
	}

	x509_read_ret = x509_dn_to_string(label_asn1, x509_read_ret, (char *) label_buf, label_buf_len, "CN");
	if (x509_read_ret <= 0) {
		x509_read_ret = x509_dn_to_string(label_asn1, x509_read_ret, (char *) label_buf, label_buf_len, NULL);

		if (x509_read_ret <= 0) {
			return(-1);
		}
	}

#ifdef CACKEY_PARANOID
#  ifdef _POSIX_SSIZE_MAX
	if (x509_read_ret > _POSIX_SSIZE_MAX) {
		CACKEY_DEBUG_PRINTF("x509_read_ret exceeds maximum value, returning in failure. (max = %li, x509_read_ret = %lu)", (long) _POSIX_SSIZE_MAX, (unsigned long) x509_read_ret);

		return(-1);
	}
#  endif
#endif

	return(x509_read_ret);
}

/* Returns 0 on success */
static int cackey_mutex_create(void **mutex) {
	pthread_mutex_t *pthread_mutex;
	int pthread_retval;
	CK_RV custom_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if ((cackey_args.flags & CKF_OS_LOCKING_OK) == CKF_OS_LOCKING_OK) {
		pthread_mutex = malloc(sizeof(*pthread_mutex));
		if (!pthread_mutex) {
			CACKEY_DEBUG_PRINTF("Failed to allocate memory.");

			return(-1);
		}

		pthread_retval = pthread_mutex_init(pthread_mutex, NULL);
		if (pthread_retval != 0) {
			CACKEY_DEBUG_PRINTF("pthread_mutex_init() returned error (%i).", pthread_retval);

			return(-1);
		}

		*mutex = pthread_mutex;
	} else {
		if (cackey_args.CreateMutex) {
			custom_retval = cackey_args.CreateMutex(mutex);

			if (custom_retval != CKR_OK) {
				CACKEY_DEBUG_PRINTF("cackey_args.CreateMutex() returned error (%li).", (long) custom_retval);

				return(-1);
			}
		}
	}

	CACKEY_DEBUG_PRINTF("Returning sucessfully (0)");

	return(0);
}

/* Returns 0 on success */
static int cackey_mutex_lock(void *mutex) {
	pthread_mutex_t *pthread_mutex;
	int pthread_retval;
	CK_RV custom_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if ((cackey_args.flags & CKF_OS_LOCKING_OK) == CKF_OS_LOCKING_OK) {
		pthread_mutex = mutex;

		pthread_retval = pthread_mutex_lock(pthread_mutex);
		if (pthread_retval != 0) {
			CACKEY_DEBUG_PRINTF("pthread_mutex_lock() returned error (%i).", pthread_retval);

			return(-1);
		}
	} else {
		if (cackey_args.LockMutex) {
			custom_retval = cackey_args.LockMutex(mutex);

			if (custom_retval != CKR_OK) {
				CACKEY_DEBUG_PRINTF("cackey_args.LockMutex() returned error (%li).", (long) custom_retval);

				return(-1);
			}
		}
	}

	CACKEY_DEBUG_PRINTF("Returning sucessfully (0)");

	return(0);
}

/* Returns 0 on success */
static int cackey_mutex_unlock(void *mutex) {
	pthread_mutex_t *pthread_mutex;
	int pthread_retval;
	CK_RV custom_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if ((cackey_args.flags & CKF_OS_LOCKING_OK) == CKF_OS_LOCKING_OK) {
		pthread_mutex = mutex;

		pthread_retval = pthread_mutex_unlock(pthread_mutex);
		if (pthread_retval != 0) {
			CACKEY_DEBUG_PRINTF("pthread_mutex_unlock() returned error (%i).", pthread_retval);

			return(-1);
		}
	} else {
		if (cackey_args.UnlockMutex) {
			custom_retval = cackey_args.UnlockMutex(mutex);

			if (custom_retval != CKR_OK) {
				CACKEY_DEBUG_PRINTF("cackey_args.UnlockMutex() returned error (%li).", (long) custom_retval);

				return(-1);
			}
		}
	}

	CACKEY_DEBUG_PRINTF("Returning sucessfully (0)");

	return(0);
}

static CK_ATTRIBUTE_PTR cackey_get_attributes(CK_OBJECT_CLASS objectclass, struct cackey_pcsc_identity *identity, unsigned long identity_num, CK_ULONG_PTR pulCount) {
	static CK_BBOOL ck_true = 1;
	static CK_BBOOL ck_false = 0;
	static CK_TRUST ck_trusted = CK_TRUSTED_DELEGATOR;
	CK_ULONG numattrs = 0, retval_count;
	CK_ATTRIBUTE_TYPE curr_attr_type;
	CK_ATTRIBUTE curr_attr, *retval;
	CK_VOID_PTR pValue;
	CK_ULONG ulValueLen;
	CK_OBJECT_CLASS ck_object_class;
	CK_CERTIFICATE_TYPE ck_certificate_type;
	CK_KEY_TYPE ck_key_type;
	CK_UTF8CHAR ucTmpBuf[1024];
	SHA1Context sha1_ctx;
	MD5_CTX md5_ctx;
	uint8_t sha1_hash[SHA1HashSize];
	uint8_t md5_hash[MD5HashSize];
	unsigned char *certificate;
	ssize_t certificate_len = -1, x509_read_ret;
	int pValue_free;

	CACKEY_DEBUG_PRINTF("Called (objectClass = %lu, identity_num = %lu).", (unsigned long) objectclass, identity_num);

	*pulCount = 0;

	if (objectclass != CKO_CERTIFICATE && objectclass != CKO_PUBLIC_KEY && objectclass != CKO_PRIVATE_KEY && objectclass != CKO_NETSCAPE_TRUST) {
		CACKEY_DEBUG_PRINTF("Returning 0 objects (NULL), invalid object class");

		return(NULL);
	}

	/* Get Cert */
	if (identity == NULL) {
		CACKEY_DEBUG_PRINTF("Returning 0 objects (NULL), invalid identiy provided");

		return(NULL);
	}

	certificate = identity->certificate;
	certificate_len = identity->certificate_len;

	if (certificate_len == -1 || certificate == NULL) {
		CACKEY_DEBUG_PRINTF("Returning 0 objects (NULL), this identity does not have an X.509 certificate associated with it and will not work");

		return(NULL);
	}

	/* Verify that certificate is ASN.1 encoded X.509 certificate */
	if (x509_to_serial(certificate, certificate_len, NULL) < 0) {
		CACKEY_DEBUG_PRINTF("Returning 0 objects (NULL), the X.509 certificate associated with this identity is not valid");

		return(NULL);
	}

	retval_count = 64;
	retval = malloc(retval_count * sizeof(*retval));

	for (curr_attr_type = 0; curr_attr_type < 0xce5363bf; curr_attr_type++) {
		if (curr_attr_type == 0x800) {
			curr_attr_type = 0xce536300;
		}

		pValue_free = 0;
		pValue = NULL;
		ulValueLen = (CK_LONG) -1;

		switch (curr_attr_type) {
			case CKA_CLASS:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_CLASS (0x%08lx) ...", (unsigned long) curr_attr_type);

				ck_object_class = objectclass;

				pValue = &ck_object_class;
				ulValueLen = sizeof(ck_object_class);

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_OBJECT_CLASS *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_TOKEN:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_TOKEN (0x%08lx) ...", (unsigned long) curr_attr_type);

				pValue = &ck_true;
				ulValueLen = sizeof(ck_true);

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_PRIVATE:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_PRIVATE (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass != CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are not a Netscape trust object");

					break;
				}

				pValue = &ck_false;
				ulValueLen = sizeof(ck_false);

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_TRUSTED:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_TRUSTED (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				pValue = &ck_true;
				ulValueLen = sizeof(ck_true);

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_MODIFIABLE:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_MODIFIABLE (0x%08lx) ...", (unsigned long) curr_attr_type);

				pValue = &ck_false;
				ulValueLen = sizeof(ck_false);

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_LABEL:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_LABEL (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (identity->id_type == CACKEY_ID_TYPE_PIV) {
					pValue = identity->card.piv.label;
					ulValueLen = strlen(pValue);
				} else {
					ulValueLen = snprintf((char *) ucTmpBuf, sizeof(ucTmpBuf), "Identity #%lu", (unsigned long) identity_num);
					pValue = ucTmpBuf;

					if (ulValueLen >= sizeof(ucTmpBuf)) {
						ulValueLen = 0;
						pValue = NULL;
					}
				}

				CACKEY_DEBUG_PRINTF(" ... returning (%p/%lu)", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_VALUE:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_VALUE (0x%08lx) ...", (unsigned long) curr_attr_type);

				switch (objectclass) {
					case CKO_PRIVATE_KEY:
						CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a private key.");

						break;
					case CKO_NETSCAPE_TRUST:
						CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

						break;
					case CKO_PUBLIC_KEY:
						if (certificate_len >= 0) {
							x509_read_ret = x509_to_pubkey(certificate, certificate_len, &pValue);
							if (x509_read_ret < 0) { 
								pValue = NULL;
							} else {
								ulValueLen = x509_read_ret;
							}
						}

						break;
					case CKO_CERTIFICATE:
						pValue = certificate;
						ulValueLen = certificate_len;

						break;
				}

				CACKEY_DEBUG_PRINTF(" ... returning %p/%lu", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_ISSUER:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_ISSUER (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass != CKO_CERTIFICATE && objectclass != CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are not a certificate or Netscape trust object");

					break;
				}

				if (certificate_len >= 0) {
					x509_read_ret = x509_to_issuer(certificate, certificate_len, &pValue);
					if (x509_read_ret < 0) {
						pValue = NULL;
					} else {
						ulValueLen = x509_read_ret;
					}
				}

				CACKEY_DEBUG_PRINTF(" ... returning %p/%lu", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_SERIAL_NUMBER:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_SERIAL_NUMBER (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass != CKO_CERTIFICATE && objectclass != CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are not a certificate or Netscape trust object");

					break;
				}

				if (certificate_len >= 0) {
					x509_read_ret = x509_to_serial(certificate, certificate_len, &pValue);
					if (x509_read_ret < 0) {
						pValue = NULL;
					} else {
						ulValueLen = x509_read_ret;
					}
				}

				CACKEY_DEBUG_PRINTF(" ... returning (%p/%lu)", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_SUBJECT:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_SUBJECT (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass != CKO_CERTIFICATE) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are not a certificate");

					break;
				}

				if (certificate_len >= 0) {
					x509_read_ret = x509_to_subject(certificate, certificate_len, &pValue);
					if (x509_read_ret < 0) {
						pValue = NULL;
					} else {
						ulValueLen = x509_read_ret;
					}
				}

				CACKEY_DEBUG_PRINTF(" ... returning %p/%lu", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_ID:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_ID (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				ucTmpBuf[0] = ((identity_num + 1) >> 8) & 0xff;
				ucTmpBuf[1] =  (identity_num + 1) & 0xff;

				pValue = &ucTmpBuf;
				ulValueLen = 2;

				CACKEY_DEBUG_PRINTF(" ... returning %p/%lu", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_CERTIFICATE_TYPE:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_CERTIFICATE_TYPE (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass != CKO_CERTIFICATE) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are not a certificate.");

					break;
				}

				/* We only support one certificate type */
				ck_certificate_type = CKC_X_509;

				pValue = &ck_certificate_type;
				ulValueLen = sizeof(ck_certificate_type);

				CACKEY_DEBUG_PRINTF(" ... returning CKC_X_509 (%lu) (%p/%lu)", (unsigned long) *((CK_CERTIFICATE_TYPE *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_KEY_TYPE:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_KEY_TYPE (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass != CKO_PRIVATE_KEY && objectclass != CKO_PUBLIC_KEY) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are not a key.");

					break;
				}

				/* We only support one key type */
				ck_key_type = CKK_RSA;

				pValue = &ck_key_type;
				ulValueLen = sizeof(ck_key_type);

				CACKEY_DEBUG_PRINTF(" ... returning CKK_RSA (%lu) (%p/%lu)", (unsigned long) *((CK_CERTIFICATE_TYPE *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_SIGN:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_SIGN (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				if (objectclass == CKO_PRIVATE_KEY) {
					pValue = &ck_true;
					ulValueLen = sizeof(ck_true);
				} else {
					pValue = &ck_false;
					ulValueLen = sizeof(ck_false);
				}

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_SIGN_RECOVER:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_SIGN_RECOVER (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				/* We currently only support "Sign with Appendix" */
				pValue = &ck_false;
				ulValueLen = sizeof(ck_false);

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_DECRYPT:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_DECRYPT (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				if (objectclass == CKO_PRIVATE_KEY || objectclass == CKO_PUBLIC_KEY) {
					pValue = &ck_true;
					ulValueLen = sizeof(ck_true);
				} else {
					pValue = &ck_false;
					ulValueLen = sizeof(ck_false);
				}

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_SENSITIVE:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_SENSITIVE (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				if (objectclass == CKO_PRIVATE_KEY) {
					pValue = &ck_true;
					ulValueLen = sizeof(ck_true);
				} else {
					pValue = &ck_false;
					ulValueLen = sizeof(ck_false);
				}

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_EXTRACTABLE:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_EXTRACTABLE (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				if (objectclass == CKO_PRIVATE_KEY) {
					pValue = &ck_false;
					ulValueLen = sizeof(ck_true);
				} else {
					pValue = &ck_true;
					ulValueLen = sizeof(ck_false);
				}

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_BBOOL *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_MODULUS:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_MODULUS (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				if (certificate_len >= 0) {
					x509_read_ret = x509_to_modulus(certificate, certificate_len, &pValue);
					if (x509_read_ret < 0) {
						pValue = NULL;
					} else {
						ulValueLen = x509_read_ret;
					}
				}

				CACKEY_DEBUG_PRINTF(" ... returning (%p/%lu)", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_PUBLIC_EXPONENT:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_PUBLIC_EXPONENT (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass == CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are a Netscape trust object");

					break;
				}

				if (certificate_len >= 0) {
					x509_read_ret = x509_to_exponent(certificate, certificate_len, &pValue);
					if (x509_read_ret < 0) {
						pValue = NULL;
					} else {
						ulValueLen = x509_read_ret;
					}
				}

				CACKEY_DEBUG_PRINTF(" ... returning (%p/%lu)", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_TRUST_DIGITAL_SIGNATURE:
			case CKA_TRUST_NON_REPUDIATION:
			case CKA_TRUST_KEY_ENCIPHERMENT:
			case CKA_TRUST_DATA_ENCIPHERMENT:
			case CKA_TRUST_KEY_AGREEMENT:
			case CKA_TRUST_KEY_CERT_SIGN:
			case CKA_TRUST_CRL_SIGN:
			case CKA_TRUST_SERVER_AUTH:
			case CKA_TRUST_CLIENT_AUTH:
			case CKA_TRUST_CODE_SIGNING:
			case CKA_TRUST_EMAIL_PROTECTION:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_TRUST_... (0x%08lx) ...", (unsigned long) curr_attr_type);

				pValue = &ck_trusted;
				ulValueLen = sizeof(ck_trusted);

				CACKEY_DEBUG_PRINTF(" ... returning %lu (%p/%lu)", (unsigned long) *((CK_TRUST *) pValue), pValue, (unsigned long) ulValueLen);

				break;
			case CKA_CERT_SHA1_HASH:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_CERT_SHA1_HASH (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass != CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are not a Netscape trust object");

					break;
				}

				SHA1Reset(&sha1_ctx);
				SHA1Input(&sha1_ctx, certificate, certificate_len);
				SHA1Result(&sha1_ctx, sha1_hash);

				pValue = sha1_hash;
				ulValueLen = sizeof(sha1_hash);

				CACKEY_DEBUG_PRINTF(" ... returning %p/%lu", pValue, (unsigned long) ulValueLen);

				break;
			case CKA_CERT_MD5_HASH:
				CACKEY_DEBUG_PRINTF("Requesting attribute CKA_CERT_MD5_HASH (0x%08lx) ...", (unsigned long) curr_attr_type);

				if (objectclass != CKO_NETSCAPE_TRUST) {
					CACKEY_DEBUG_PRINTF(" ... but not getting it because we are not a Netscape trust object");

					break;
				}

				MD5Init(&md5_ctx);
				MD5Update(&md5_ctx, certificate, certificate_len);
				MD5Final(md5_hash, &md5_ctx);

				pValue = md5_hash;
				ulValueLen = sizeof(md5_hash);

				CACKEY_DEBUG_PRINTF(" ... returning %p/%lu", pValue, (unsigned long) ulValueLen);

				break;
			default:
				pValue = NULL;
				ulValueLen = (CK_LONG) -1;
				break;
		}

		if (((CK_LONG) ulValueLen) != ((CK_LONG) -1)) {
			/* Push curr_attr onto the stack */
			curr_attr.type = curr_attr_type;
			curr_attr.ulValueLen = ulValueLen;

			curr_attr.pValue = malloc(curr_attr.ulValueLen);
			memcpy(curr_attr.pValue, pValue, curr_attr.ulValueLen);

			if (pValue_free && pValue) {
				free(pValue);
			}

			if (numattrs >= retval_count) {
				retval = realloc(retval, retval_count * sizeof(*retval));
			}

			memcpy(&retval[numattrs], &curr_attr, sizeof(curr_attr));
			numattrs++;
		}
	}

	if (numattrs != 0) {
		retval_count = numattrs;
		retval = realloc(retval, retval_count * sizeof(*retval));
	} else {
		free(retval);

		retval = NULL;
	}

	*pulCount = numattrs;

	CACKEY_DEBUG_PRINTF("Returning %lu objects (%p).", numattrs, (void *) retval);

	return(retval);
}

static void cackey_free_identities(struct cackey_identity *identities, unsigned long identities_count) {
	CK_ATTRIBUTE *curr_attr;
	unsigned long id_idx, attr_idx;

	if (identities == NULL || identities_count == 0) {
		return;
	}

	for (id_idx = 0; id_idx < identities_count; id_idx++) {
		if (identities[id_idx].attributes) {
			for (attr_idx = 0; attr_idx < identities[id_idx].attributes_count; attr_idx++) {
				curr_attr = &identities[id_idx].attributes[attr_idx];

				if (curr_attr->pValue) {
					free(curr_attr->pValue);
				}
			}

			if (identities[id_idx].attributes) {
				free(identities[id_idx].attributes);
			}

			cackey_free_certs(identities[id_idx].pcsc_identity, 1, 1);
		}
	}

	free(identities);
}

static unsigned long cackey_read_dod_identities(struct cackey_identity *identities, unsigned long num_dod_certs) {
	unsigned long cert_idx, id_idx = 0;

	if (identities == NULL) {
		return(num_dod_certs * 3);
	}

	for (cert_idx = 0; cert_idx < num_dod_certs; cert_idx++) {
		identities[id_idx].pcsc_identity = NULL;
		identities[id_idx].attributes = cackey_get_attributes(CKO_CERTIFICATE, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count);
		id_idx++;

		identities[id_idx].pcsc_identity = NULL;
		identities[id_idx].attributes = cackey_get_attributes(CKO_PUBLIC_KEY, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count);
		id_idx++;

		identities[id_idx].pcsc_identity = NULL;
		identities[id_idx].attributes = cackey_get_attributes(CKO_NETSCAPE_TRUST, &extra_certs[cert_idx], 0xf000 | cert_idx, &identities[id_idx].attributes_count);
		id_idx++;
	}

	return(id_idx);
}

static struct cackey_identity *cackey_read_identities(struct cackey_slot *slot, unsigned long *ids_found) {
	struct cackey_pcsc_identity *pcsc_identities;
	struct cackey_identity *identities;
	unsigned long num_ids, id_idx, curr_id_type;
	unsigned long num_certs, num_dod_certs, cert_idx;
	int include_extra_certs = 0, include_dod_certs;

	CACKEY_DEBUG_PRINTF("Called.");

	if (ids_found == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  ids_found is NULL");

		return(NULL);
	}

#ifdef CACKEY_CARD_SLOT_INCLUDE_EXTRA_CERTS
	include_extra_certs = 1;
#endif

	if (getenv("CACKEY_DOD_CERTS_ON_HW_SLOTS") != NULL) {
		include_extra_certs = 1;
	}

	if (getenv("CACKEY_NO_DOD_CERTS_ON_HW_SLOTS") != NULL) {
		include_extra_certs = 0;
	}

#ifdef CACKEY_NO_EXTRA_CERTS
	if (getenv("CACKEY_EXTRA_CERTS") != NULL) {
		include_dod_certs = 1;
	} else {
		include_dod_certs = 0;
	}
#else
	if (getenv("CACKEY_NO_EXTRA_CERTS") != NULL) {
		include_dod_certs = 0;
	} else {
		include_dod_certs = 1;
	}
#endif

	if (include_dod_certs) {
		num_dod_certs = sizeof(extra_certs) / sizeof(extra_certs[0]);
	} else {
		num_dod_certs = 0;
	}

	if (slot->internal) {
		num_ids = cackey_read_dod_identities(NULL, num_dod_certs);

		if (num_ids != 0) {
			identities = malloc(num_ids * sizeof(*identities));

			cackey_read_dod_identities(identities, num_dod_certs);
		} else {
			identities = NULL;
		}

		*ids_found = num_ids;

		return(identities);
	}

	pcsc_identities = cackey_read_certs(slot, NULL, &num_certs);
	if (pcsc_identities != NULL) {
		/* Convert number of Certs to number of objects */
		num_ids = (CKO_PRIVATE_KEY - CKO_CERTIFICATE + 1) * num_certs;

		if (include_extra_certs) {
			num_ids += cackey_read_dod_identities(NULL, num_dod_certs);
		}

		identities = malloc(num_ids * sizeof(*identities));

		/* Add certificates, public keys, and private keys from the smartcard */
		id_idx = 0;
		for (cert_idx = 0; cert_idx < num_certs; cert_idx++) {
			for (curr_id_type = CKO_CERTIFICATE; curr_id_type <= CKO_PRIVATE_KEY; curr_id_type++) {
				identities[id_idx].attributes = cackey_get_attributes(curr_id_type, &pcsc_identities[cert_idx], cert_idx, &identities[id_idx].attributes_count);

				identities[id_idx].pcsc_identity = malloc(sizeof(*identities[id_idx].pcsc_identity));
				memcpy(identities[id_idx].pcsc_identity, &pcsc_identities[cert_idx], sizeof(*identities[id_idx].pcsc_identity));

				identities[id_idx].pcsc_identity->certificate = malloc(pcsc_identities[cert_idx].certificate_len);
				memcpy(identities[id_idx].pcsc_identity->certificate, pcsc_identities[cert_idx].certificate, pcsc_identities[cert_idx].certificate_len);

				id_idx++;
			}
		}

		if (include_extra_certs) {
			CACKEY_DEBUG_PRINTF("Including US Government Certificates on hardware slot");

			cackey_read_dod_identities(identities + id_idx, num_dod_certs);
		}

		cackey_free_certs(pcsc_identities, num_certs, 1);

		*ids_found = num_ids;

		return(identities);
	}

	*ids_found = 0;
	return(NULL);
}

static cackey_ret cackey_get_pin(char *pinbuf) {
	FILE *pinfd;
	char *fgets_ret;
	int pclose_ret;

	if (cackey_pin_command == NULL) {
		return(CACKEY_PCSC_E_GENERIC);
	}

	if (pinbuf == NULL) {
		return(CACKEY_PCSC_E_GENERIC);
	}

	CACKEY_DEBUG_PRINTF("CACKEY_PIN_COMMAND = %s", cackey_pin_command);

	pinfd = popen(cackey_pin_command, "r");
	if (pinfd == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  %s: Unable to run", cackey_pin_command);

		return(CACKEY_PCSC_E_BADPIN);
	}

	fgets_ret = fgets(pinbuf, 32, pinfd);
	if (fgets_ret == NULL) {
		pinbuf[0] = '\0';
	}

	pclose_ret = pclose(pinfd);
	if (pclose_ret == -1 && errno == ECHILD) {
		CACKEY_DEBUG_PRINTF("Notice.  pclose() indicated it could not get the status of the child, assuming it succeeeded !");

		pclose_ret = 0;
	}

	if (pclose_ret != 0) {
		CACKEY_DEBUG_PRINTF("Error.  %s: exited with non-zero status of %i", cackey_pin_command, pclose_ret);

		return(CACKEY_PCSC_E_BADPIN);
	}

	if (strlen(pinbuf) < 1) {
		CACKEY_DEBUG_PRINTF("Error.  %s: returned no data", cackey_pin_command);

		return(CACKEY_PCSC_E_BADPIN);
	}

	if (pinbuf[strlen(pinbuf) - 1] == '\n') {
		pinbuf[strlen(pinbuf) - 1] = '\0';
	}

	return(CACKEY_PCSC_S_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_Initialize)(CK_VOID_PTR pInitArgs) {
	CK_C_INITIALIZE_ARGS CK_PTR args;
	uint32_t idx, highest_slot;
	int mutex_init_ret;
	int include_dod_certs;

	CACKEY_DEBUG_PRINTF("Called.");

	if (cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Already initialized.");

		return(CKR_CRYPTOKI_ALREADY_INITIALIZED);
	}

	if (pInitArgs != NULL) {
		args = pInitArgs;
		memcpy(&cackey_args, args, sizeof(cackey_args));

		if (args->CreateMutex == NULL || args->DestroyMutex == NULL || args->LockMutex == NULL || args->UnlockMutex == NULL) {
			if (args->CreateMutex != NULL || args->DestroyMutex != NULL || args->LockMutex != NULL || args->UnlockMutex != NULL) {
				CACKEY_DEBUG_PRINTF("Error. Some, but not All threading primitives provided.");

				return(CKR_ARGUMENTS_BAD);
			}
		}
	} else {
		cackey_args.CreateMutex = NULL;
		cackey_args.DestroyMutex = NULL;
		cackey_args.LockMutex = NULL;
		cackey_args.UnlockMutex = NULL;
		cackey_args.flags = 0;
	}

	for (idx = 0; idx < (sizeof(cackey_sessions) / sizeof(cackey_sessions[0])); idx++) {
		cackey_sessions[idx].active = 0;
	}

	for (idx = 0; idx < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); idx++) {
		cackey_slots[idx].active = 0;
		cackey_slots[idx].pcsc_reader = NULL;
		cackey_slots[idx].transaction_depth = 0;
		cackey_slots[idx].transaction_need_hw_lock = 0;
		cackey_slots[idx].slot_reset = 0;
		cackey_slots[idx].token_flags = 0;
		cackey_slots[idx].label = NULL;
		cackey_slots[idx].internal = 0;
		cackey_slots[idx].id_type_hint = CACKEY_ID_TYPE_UNKNOWN;
	}

#ifdef CACKEY_NO_EXTRA_CERTS
	if (getenv("CACKEY_EXTRA_CERTS") != NULL) {
		include_dod_certs = 1;
	} else {
		include_dod_certs = 0;
	}
#else
	if (getenv("CACKEY_NO_EXTRA_CERTS") != NULL) {
		include_dod_certs = 0;
	} else {
		include_dod_certs = 1;
	}
#endif

	if (include_dod_certs == 0) {
		CACKEY_DEBUG_PRINTF("Asked not to include DoD certificates");
	} else {
		highest_slot = (sizeof(cackey_slots) / sizeof(cackey_slots[0])) - 1;

		CACKEY_DEBUG_PRINTF("Including DoD certs in slot %lu", (unsigned long) highest_slot);

		cackey_slots[highest_slot].active = 1;
		cackey_slots[highest_slot].internal = 1;
		cackey_slots[highest_slot].label = (unsigned char *) "US Government Certificates";
		cackey_slots[highest_slot].pcsc_reader = "CACKey";
		cackey_slots[highest_slot].token_flags = 0;
	}

	cackey_initialized = 1;

	if (!cackey_biglock_init) {
		mutex_init_ret = cackey_mutex_create(&cackey_biglock);

		if (mutex_init_ret != 0) {
			CACKEY_DEBUG_PRINTF("Error.  Mutex initialization failed.");

			return(CKR_CANT_LOCK);
		}

		cackey_biglock_init = 1;
	}

	/* Define a command to prompt user for a PIN */
#ifdef CACKEY_PIN_COMMAND_DEFAULT
	cackey_pin_command = strdup(CACKEY_MACRO_DEFAULT_XSTR(CACKEY_PIN_COMMAND_DEFAULT));
#endif

#ifdef CACKEY_PIN_COMMAND_XONLY_DEFAULT
	if (getenv("DISPLAY") != NULL) {
		cackey_pin_command = strdup(CACKEY_MACRO_DEFAULT_XSTR(CACKEY_PIN_COMMAND_XONLY_DEFAULT));
	}
#endif

	if (getenv("CACKEY_PIN_COMMAND") != NULL) {
		cackey_pin_command = strdup(getenv("CACKEY_PIN_COMMAND"));
	}

	if (getenv("CACKEY_PIN_COMMAND_XONLY") != NULL && getenv("DISPLAY") != NULL) {
		cackey_pin_command = strdup(getenv("CACKEY_PIN_COMMAND_XONLY"));
	}

	if (cackey_pin_command && strcmp(cackey_pin_command, "") == 0) {
		free(cackey_pin_command);
		cackey_pin_command = NULL;
	}

#ifdef CACKEY_READERS_INCLUDE_ONLY_DEFAULT
	cackey_readers_include_only = strdup(CACKEY_MACRO_DEFAULT_XSTR(CACKEY_READERS_INCLUDE_ONLY_DEFAULT));
#endif

#ifdef CACKEY_READERS_EXCLUDE_DEFAULT
	cackey_readers_exclude = strdup(CACKEY_MACRO_DEFAULT_XSTR(CACKEY_READERS_EXCLUDE_DEFAULT));
#endif

	if (getenv("CACKEY_READERS_INCLUDE_ONLY") != NULL) {
		cackey_readers_include_only = strdup(getenv("CACKEY_READERS_INCLUDE_ONLY"));

		if (cackey_readers_include_only[0] == '\0') {
			free(cackey_readers_include_only);

			cackey_readers_include_only = NULL;
		}
	}

	if (getenv("CACKEY_READERS_EXCLUDE") != NULL) {
		cackey_readers_exclude = strdup(getenv("CACKEY_READERS_EXCLUDE"));

		if (cackey_readers_exclude[0] == '\0') {
			free(cackey_readers_exclude);

			cackey_readers_exclude = NULL;
		}
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_Finalize)(CK_VOID_PTR pReserved) {
	uint32_t idx;

	CACKEY_DEBUG_PRINTF("Called.");

	if (pReserved != NULL) {
		CACKEY_DEBUG_PRINTF("Error. pReserved is not NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	for (idx = 0; idx < (sizeof(cackey_sessions) / sizeof(cackey_sessions[0])); idx++) {
		if (cackey_sessions[idx].active) {
			C_CloseSession(idx);
		}
	}

	cackey_slots_disconnect_all(1);

	for (idx = 0; idx < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); idx++) {
		if (cackey_slots[idx].internal) {
			continue;
		}

		if (cackey_slots[idx].cached_certs) {
			cackey_free_certs(cackey_slots[idx].cached_certs, cackey_slots[idx].cached_certs_count, 1);

			cackey_slots[idx].cached_certs = NULL;
		}
	}

	cackey_pcsc_disconnect();

	if (cackey_pin_command != NULL) {
		free(cackey_pin_command);

		cackey_pin_command = NULL;
	}

	if (cackey_readers_include_only != NULL) {
		free(cackey_readers_include_only);

		cackey_readers_include_only = NULL;
	}

	if (cackey_readers_exclude != NULL) {
		free(cackey_readers_exclude);

		cackey_readers_exclude = NULL;
	}

	cackey_initialized = 0;

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_GetInfo)(CK_INFO_PTR pInfo) {
	static CK_UTF8CHAR manufacturerID[] = "U.S. Government";
	static CK_UTF8CHAR libraryDescription[] = "CACKey";

	CACKEY_DEBUG_PRINTF("Called.");

	if (pInfo == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pInfo is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	pInfo->cryptokiVersion.major = ((CACKEY_CRYPTOKI_VERSION_CODE) >> 16) & 0xff;
	pInfo->cryptokiVersion.minor = ((CACKEY_CRYPTOKI_VERSION_CODE) >> 8) & 0xff;

	memset(pInfo->manufacturerID, ' ', sizeof(pInfo->manufacturerID));
	memcpy(pInfo->manufacturerID, manufacturerID, sizeof(manufacturerID) - 1);

	pInfo->flags = 0x00;

	memset(pInfo->libraryDescription, ' ', sizeof(pInfo->libraryDescription));
	memcpy(pInfo->libraryDescription, libraryDescription, sizeof(libraryDescription) - 1);

	pInfo->libraryVersion.major = (cackey_getversion() >> 16) & 0xff;
	pInfo->libraryVersion.minor = (cackey_getversion() >> 8) & 0xff;

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

/*
 * Process list of readers, and create mapping between reader name and slot ID
 */
CK_DEFINE_FUNCTION(CK_RV, C_GetSlotList)(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList, CK_ULONG_PTR pulCount) {
	static int first_call = 1;
	int mutex_retval;
	int pcsc_connect_ret;
	CK_ULONG count, slot_count = 0, currslot, slot_idx;
	char *pcsc_readers, *pcsc_readers_s, *pcsc_readers_e;
	char *reader_check_pattern;
	DWORD pcsc_readers_len;
	LONG scard_listreaders_ret;
	size_t curr_reader_len;
	int slot_reset;
	int include_reader;

	CACKEY_DEBUG_PRINTF("Called.");

	if (pulCount == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pulCount is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	/* Clear list of slots */
	slot_reset = 0;
	if (pSlotList) {
		if (first_call) {
			first_call = 0;

			slot_reset = 1;
		}

		/* If any of the slots have been reset then purge all information and check again */
		for (currslot = 0; currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); currslot++) {
			if (cackey_slots[currslot].internal) {
				continue;
			}

			if (!cackey_slots[currslot].active) {
				continue;
			}

			if (cackey_slots[currslot].slot_reset) {
				slot_reset = 1;

				break;
			}
		}

		if (slot_reset) {
			CACKEY_DEBUG_PRINTF("Purging all slot information.");

			/* Only update the list of slots if we are actually being supply the slot information */
			cackey_slots_disconnect_all(1);

			for (currslot = 0; currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); currslot++) {
				if (cackey_slots[currslot].internal) {
					continue;
				}

				cackey_slots[currslot].active = 0;
			}
		}
	}

	/* Determine list of readers */
	pcsc_connect_ret = cackey_pcsc_connect();
	if (pcsc_connect_ret != CACKEY_PCSC_S_OK) {
		CACKEY_DEBUG_PRINTF("Connection to PC/SC failed, assuming no hardware slots");
	} else {
		pcsc_readers_len = 0;

		scard_listreaders_ret = SCardListReaders(*cackey_pcsc_handle, NULL, NULL, &pcsc_readers_len);

		if (scard_listreaders_ret == SCARD_F_COMM_ERROR) {
			CACKEY_DEBUG_PRINTF("Error. SCardListReaders() returned SCARD_F_COMM_ERROR, assuming Connection to PC/SC went away. Reconnecting.");

			cackey_pcsc_disconnect();
			cackey_pcsc_connect();

			CACKEY_DEBUG_PRINTF("Trying SCardListReaders() again");
			scard_listreaders_ret = SCardListReaders(*cackey_pcsc_handle, NULL, NULL, &pcsc_readers_len);
		}

		if (scard_listreaders_ret == SCARD_E_INSUFFICIENT_BUFFER) {
			CACKEY_DEBUG_PRINTF("Error. SCardListReaders() returned SCARD_E_INSUFFICIENT_BUFFER, assuming this is a bug (e.g., Google PCSC) and allocating a massive amount of space to hold the reader list.");

			pcsc_readers_len = 32768;
			scard_listreaders_ret = SCARD_S_SUCCESS;
		}

		if (scard_listreaders_ret == SCARD_S_SUCCESS && pcsc_readers_len != 0) {
			pcsc_readers = malloc(pcsc_readers_len);
			pcsc_readers_s = pcsc_readers;

			scard_listreaders_ret = SCardListReaders(*cackey_pcsc_handle, NULL, pcsc_readers, &pcsc_readers_len);
			if (scard_listreaders_ret == SCARD_S_SUCCESS) {
				pcsc_readers_e = pcsc_readers + pcsc_readers_len;

				/* Start with Slot ID 1, to avoid a bug in GDM on RHEL */
				/* Bug 594911: https://bugzilla.redhat.com/show_bug.cgi?id=594911 */
				currslot = 1;
				while (pcsc_readers < pcsc_readers_e) {
					/* Find next available slot */
					for (; currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); currslot++) {
						if (!cackey_slots[currslot].active) {
							break;
						}
					}

					curr_reader_len = strlen(pcsc_readers);

					if ((pcsc_readers + curr_reader_len) > pcsc_readers_e) {
						break;
					}

					if (curr_reader_len == 0) {
						break;
					}

					if (currslot >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
						CACKEY_DEBUG_PRINTF("Found more readers than slots are available!");

						break;
					}

					CACKEY_DEBUG_PRINTF("Found reader: %s (currslot = %lu)", pcsc_readers, (unsigned long) currslot);

					if (cackey_readers_include_only != NULL) {
						CACKEY_DEBUG_PRINTF("Asked to include only readers matching: %s", cackey_readers_include_only);

						include_reader = 0;
						reader_check_pattern = cackey_readers_include_only;
					} else if (cackey_readers_exclude != NULL) {
						CACKEY_DEBUG_PRINTF("Asked to exclude readers matching: %s", cackey_readers_exclude);

						include_reader = 1;
						reader_check_pattern = cackey_readers_exclude;
					} else {
						include_reader = 1;
						reader_check_pattern = NULL;
					}

					if (reader_check_pattern != NULL) {
						if (strstr(pcsc_readers, reader_check_pattern) != NULL) {
							CACKEY_DEBUG_PRINTF("This reader matched the pattern.");
						
							include_reader = !include_reader;
						}
					}

					if (include_reader != 1) {
						CACKEY_DEBUG_PRINTF("Skipping this reader.");

						pcsc_readers += curr_reader_len + 1;

						continue;
					}

					/* Only update the list of slots if we are actually being asked supply the slot information */
					if (pSlotList) {
						if (slot_reset) {
							cackey_slots[currslot].active = 1;
							cackey_slots[currslot].internal = 0;
							cackey_slots[currslot].pcsc_reader = strdup(pcsc_readers);
							cackey_slots[currslot].pcsc_card_connected = 0;
							cackey_slots[currslot].transaction_depth = 0;
							cackey_slots[currslot].transaction_need_hw_lock = 0;
							if (cackey_pin_command == NULL) {
								cackey_slots[currslot].token_flags = CKF_LOGIN_REQUIRED;
							} else {
								cackey_slots[currslot].token_flags = 0;
							}
							cackey_slots[currslot].label = NULL;

							cackey_mark_slot_reset(&cackey_slots[currslot]);
						}
					} else {
						if (!cackey_slots[currslot].active) {
							/* Artificially increase the number of active slots by what will become active */
							CACKEY_DEBUG_PRINTF("Found in-active slot %lu, but it will be active after a reset -- marking as active for accounting purposes", (unsigned long) currslot);

							slot_count++;
						}
					}
					currslot++;

					pcsc_readers += curr_reader_len + 1;
				}
			} else {
				CACKEY_DEBUG_PRINTF("Second call to SCardListReaders failed, return %s/%li", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_listreaders_ret), (long) scard_listreaders_ret);
			}

			free(pcsc_readers_s);
		} else {
			CACKEY_DEBUG_PRINTF("First call to SCardListReaders failed, return %s/%li", CACKEY_DEBUG_FUNC_SCARDERR_TO_STR(scard_listreaders_ret), (long) scard_listreaders_ret);
		}
	}

	for (currslot = 0; currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0])); currslot++) {
		if (cackey_slots[currslot].active) {
			CACKEY_DEBUG_PRINTF("Found active slot %lu, reader = %s", (unsigned long) currslot, cackey_slots[currslot].pcsc_reader);

			slot_count++;
		}
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (pSlotList == NULL) {
		*pulCount = slot_count;

		CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i).  Found %lu readers, but not storing IDs (pSlotList == NULL)", CKR_OK, (unsigned long) slot_count);

		return(CKR_OK);
	}

	count = *pulCount;
	if (count < slot_count) {
		CACKEY_DEBUG_PRINTF("Error. User allocated %lu entries, but we have %lu entries.", count, slot_count);

		CACKEY_DEBUG_PRINTF("Returning CKR_BUFFER_TOO_SMALL");

		return(CKR_BUFFER_TOO_SMALL);	
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	slot_idx = 0;
	for (currslot = 0; (currslot < (sizeof(cackey_slots) / sizeof(cackey_slots[0]))); currslot++) {
		if (!cackey_slots[currslot].active) {
			continue;
		}

		if (slot_idx >= count) {
			CACKEY_DEBUG_PRINTF("Error. User allocated %lu entries, but we just tried to write to the %lu index -- ignoring", count, slot_idx);

			continue;
		}

		pSlotList[slot_idx] = currslot;
		slot_idx++;
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	*pulCount = slot_count;

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i).  Found %lu readers.", CKR_OK, (unsigned long) slot_count);

	return(CKR_OK);

	tokenPresent = tokenPresent; /* Supress unused variable warning */
}

CK_DEFINE_FUNCTION(CK_RV, C_GetSlotInfo)(CK_SLOT_ID slotID, CK_SLOT_INFO_PTR pInfo) {
	static CK_UTF8CHAR slotDescription[] = "CACKey Slot";
	int mutex_retval;
	int bytes_to_copy;

	CACKEY_DEBUG_PRINTF("Called.");

	if (pInfo == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pInfo is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_SLOT_ID_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_SLOT_ID_INVALID);
	}

	pInfo->flags = CKF_HW_SLOT;

	if (!cackey_slots[slotID].internal) {
		pInfo->flags |= CKF_REMOVABLE_DEVICE;
	}

	if (cackey_token_present(&cackey_slots[slotID]) == CACKEY_PCSC_S_TOKENPRESENT) {
		pInfo->flags |= CKF_TOKEN_PRESENT;
	}

	bytes_to_copy = strlen(cackey_slots[slotID].pcsc_reader);
	if (sizeof(pInfo->manufacturerID) < bytes_to_copy) {
		bytes_to_copy = sizeof(pInfo->manufacturerID);
	}
	memcpy(pInfo->manufacturerID, cackey_slots[slotID].pcsc_reader, bytes_to_copy);

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	memset(pInfo->slotDescription, ' ', sizeof(pInfo->slotDescription));
	memcpy(pInfo->slotDescription, slotDescription, sizeof(slotDescription) - 1);

	memset(pInfo->manufacturerID, ' ', sizeof(pInfo->manufacturerID));

	pInfo->hardwareVersion.major = (cackey_getversion() >> 16) & 0xff;
	pInfo->hardwareVersion.minor = (cackey_getversion() >> 8) & 0xff;

	pInfo->firmwareVersion.major = 0x00;
	pInfo->firmwareVersion.minor = 0x00;

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_GetTokenInfo)(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) {
	static CK_UTF8CHAR manufacturerID[] = "U.S. Government";
	static CK_UTF8CHAR defaultLabel[] = "Unknown Token";
	static CK_UTF8CHAR model[] = "CAC Token";
	struct cackey_pcsc_identity *pcsc_identities;
	unsigned long num_certs;
	ssize_t label_ret;
	int mutex_retval;
	int use_default_label;

	CACKEY_DEBUG_PRINTF("Called.");

	if (pInfo == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pInfo is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_SLOT_ID_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_SLOT_ID_INVALID);
	}

	if (cackey_token_present(&cackey_slots[slotID]) != CACKEY_PCSC_S_TOKENPRESENT) {
		CACKEY_DEBUG_PRINTF("No token is present in slotID = %lu", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_TOKEN_NOT_PRESENT);
	}

	/* Determine token label from certificates */
	memset(pInfo->label, ' ', sizeof(pInfo->label));
	use_default_label = 1;

	if (cackey_slots[slotID].label == NULL) {
		pcsc_identities = cackey_read_certs(&cackey_slots[slotID], NULL, &num_certs);
		if (pcsc_identities != NULL) {
			if (num_certs > 0) {
				label_ret = cackey_pcsc_identity_to_label(pcsc_identities, pInfo->label, sizeof(pInfo->label));
				if (label_ret > 0) {
					use_default_label = 0;

					cackey_slots[slotID].label = malloc(sizeof(pInfo->label));

					memcpy(cackey_slots[slotID].label, pInfo->label, sizeof(pInfo->label));
				}
			}

			cackey_free_certs(pcsc_identities, num_certs, 1);
		}
	} else {
		memcpy(pInfo->label, cackey_slots[slotID].label, sizeof(pInfo->label));

		use_default_label = 0;
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (use_default_label) {
		memcpy(pInfo->label, defaultLabel, sizeof(defaultLabel) - 1);
	}

	memset(pInfo->manufacturerID, ' ', sizeof(pInfo->manufacturerID));
	memcpy(pInfo->manufacturerID, manufacturerID, sizeof(manufacturerID) - 1);

	memset(pInfo->model, ' ', sizeof(pInfo->model));
	memcpy(pInfo->model, model, sizeof(model) - 1);

	memset(pInfo->serialNumber, ' ', sizeof(pInfo->serialNumber));

	memset(pInfo->utcTime, ' ', sizeof(pInfo->utcTime));

	pInfo->hardwareVersion.major = (cackey_getversion() >> 16) & 0xff;
	pInfo->hardwareVersion.minor = (cackey_getversion() >> 8) & 0xff;

	pInfo->firmwareVersion.major = 0x00;
	pInfo->firmwareVersion.minor = 0x00;

	pInfo->flags = CKF_WRITE_PROTECTED | CKF_USER_PIN_INITIALIZED | CKF_TOKEN_INITIALIZED | cackey_slots[slotID].token_flags;

	if (cackey_pin_command != NULL) {
		pInfo->flags |= CKF_PROTECTED_AUTHENTICATION_PATH;
	}

	pInfo->ulMaxSessionCount = (sizeof(cackey_sessions) / sizeof(cackey_sessions[0])) - 1;
	pInfo->ulSessionCount = CK_UNAVAILABLE_INFORMATION;
	pInfo->ulMaxRwSessionCount = 0;
	pInfo->ulRwSessionCount = CK_UNAVAILABLE_INFORMATION;
	pInfo->ulMaxPinLen = 128;
	pInfo->ulMinPinLen = 0;
	pInfo->ulTotalPublicMemory = CK_UNAVAILABLE_INFORMATION;
	pInfo->ulFreePublicMemory = CK_UNAVAILABLE_INFORMATION;
	pInfo->ulTotalPrivateMemory = CK_UNAVAILABLE_INFORMATION;
	pInfo->ulFreePrivateMemory = CK_UNAVAILABLE_INFORMATION;

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_WaitForSlotEvent)(CK_FLAGS flags, CK_SLOT_ID_PTR pSlotID, CK_VOID_PTR pReserved) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (pReserved != NULL) {
		CACKEY_DEBUG_PRINTF("Error. pReserved is not NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	/* XXX: TODO: Implement this... */
	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_GetMechanismList)(CK_SLOT_ID slotID, CK_MECHANISM_TYPE_PTR pMechanismList, CK_ULONG_PTR pulCount) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (pulCount == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  pulCount is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (pMechanismList == NULL) {
		*pulCount = 1;

		CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

		return(CKR_OK);
	}

	if (*pulCount < 1) {
		CACKEY_DEBUG_PRINTF("Error.  Buffer too small.");

		return(CKR_BUFFER_TOO_SMALL);
	}

	pMechanismList[0] = CKM_RSA_PKCS;
	*pulCount = 1;

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_GetMechanismInfo)(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, CK_MECHANISM_INFO_PTR pInfo) {
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (pInfo == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pInfo is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_SLOT_ID_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_SLOT_ID_INVALID);
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	switch (type) {
		case CKM_RSA_PKCS:
			pInfo->ulMinKeySize = 512;
			pInfo->ulMaxKeySize = 8192;
			pInfo->flags = CKF_HW | CKF_ENCRYPT | CKF_DECRYPT | CKF_SIGN | CKF_VERIFY;
			break;
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

/* We don't support this method. */
CK_DEFINE_FUNCTION(CK_RV, C_InitToken)(CK_SLOT_ID slotID, CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen, CK_UTF8CHAR_PTR pLabel) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_TOKEN_WRITE_PROTECTED (%i)", CKR_TOKEN_WRITE_PROTECTED);

	return(CKR_TOKEN_WRITE_PROTECTED);
}

/* We don't support this method. */
CK_DEFINE_FUNCTION(CK_RV, C_InitPIN)(CK_SESSION_HANDLE hSession, CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_TOKEN_WRITE_PROTECTED (%i)", CKR_TOKEN_WRITE_PROTECTED);

	return(CKR_TOKEN_WRITE_PROTECTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_SetPIN)(CK_SESSION_HANDLE hSession, CK_UTF8CHAR_PTR pOldPin, CK_ULONG ulOldPinLen, CK_UTF8CHAR_PTR pNewPin, CK_ULONG ulNewPinLen) {
	char oldpinbuf[64], newpinbuf[64];
	cackey_ret set_pin_ret, get_pin_ret;
	CK_SLOT_ID slotID;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	slotID = cackey_sessions[hSession].slotID;

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_pin_command != NULL) {
		/* Get old PIN */
		get_pin_ret = cackey_get_pin(oldpinbuf);

		if (get_pin_ret != CACKEY_PCSC_S_OK) {
			CACKEY_DEBUG_PRINTF("Error while getting Old PIN, returning CKR_PIN_INCORRECT.");

			cackey_mutex_unlock(cackey_biglock);
			
			return(CKR_PIN_INCORRECT);
		}

		pOldPin = (CK_UTF8CHAR_PTR) oldpinbuf;
		ulOldPinLen = strlen(oldpinbuf);

		/* Get new PIN */
		get_pin_ret = cackey_get_pin(newpinbuf);

		if (get_pin_ret != CACKEY_PCSC_S_OK) {
			CACKEY_DEBUG_PRINTF("Error while getting New PIN, returning CKR_PIN_INVALID.");

			cackey_mutex_unlock(cackey_biglock);
			
			return(CKR_PIN_INVALID);
		}

		pNewPin = (CK_UTF8CHAR_PTR) newpinbuf;
		ulNewPinLen = strlen(newpinbuf);
	}

	if (pOldPin == NULL) {
		CACKEY_DEBUG_PRINTF("Old PIN value is wrong (null).");

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_PIN_INCORRECT);
	}

	if (ulOldPinLen == 0 || ulOldPinLen > 8) {
		CACKEY_DEBUG_PRINTF("Old PIN length is wrong: %lu.", (unsigned long) ulOldPinLen);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_PIN_INCORRECT);
	}

	if (pNewPin == NULL) {
		CACKEY_DEBUG_PRINTF("New PIN value is wrong (either NULL, or too long/short).");

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_PIN_INVALID);
	}

	if (ulNewPinLen < 5 || ulNewPinLen > 8) {
		CACKEY_DEBUG_PRINTF("New PIN length is wrong: %lu, must be atleast 5 and no more than 8.", (unsigned long) ulNewPinLen);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_PIN_LEN_RANGE);
	}

	set_pin_ret = cackey_set_pin(&cackey_slots[slotID], pOldPin, ulOldPinLen, pNewPin, ulNewPinLen);

	if (set_pin_ret != CACKEY_PCSC_S_OK) {
		if (cackey_pin_command == NULL) {
			cackey_slots[slotID].token_flags |= CKF_LOGIN_REQUIRED;
		}

		if (set_pin_ret == CACKEY_PCSC_E_LOCKED) {
			cackey_slots[slotID].token_flags |= CKF_USER_PIN_LOCKED;
		}
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	switch (set_pin_ret) {
		case CACKEY_PCSC_S_OK:
			CACKEY_DEBUG_PRINTF("Successfully set PIN.");

			return(CKR_OK);
		case CACKEY_PCSC_E_BADPIN:
			CACKEY_DEBUG_PRINTF("PIN was invalid.");

			return(CKR_PIN_INVALID);
		case CACKEY_PCSC_E_LOCKED:
			CACKEY_DEBUG_PRINTF("Token is locked or this change is not permitted.");

			return(CKR_PIN_LOCKED);
		default:
			CACKEY_DEBUG_PRINTF("Something else went wrong changing the PIN: %i", set_pin_ret);

			return(CKR_GENERAL_ERROR);
	}

	return(CKR_GENERAL_ERROR);
}

CK_DEFINE_FUNCTION(CK_RV, C_OpenSession)(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication, CK_NOTIFY notify, CK_SESSION_HANDLE_PTR phSession) {
	unsigned long idx;
	int mutex_retval;
	int found_session = 0;

	CACKEY_DEBUG_PRINTF("Called.");

	if ((flags & CKF_SERIAL_SESSION) != CKF_SERIAL_SESSION) {
		return(CKR_SESSION_PARALLEL_NOT_SUPPORTED);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_SLOT_ID_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_SLOT_ID_INVALID);
	}

	/* Verify that the card is actually in the slot. */
	/* XXX: Check to make sure this is in the PKCS#11 specification */
	if (cackey_token_present(&cackey_slots[slotID]) != CACKEY_PCSC_S_TOKENPRESENT) {
		CACKEY_DEBUG_PRINTF("Error.  Card not present.  Returning CKR_DEVICE_REMOVED");

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_DEVICE_REMOVED);
	}

	for (idx = 1; idx < (sizeof(cackey_sessions) / sizeof(cackey_sessions[0])); idx++) {
		if (!cackey_sessions[idx].active) {
			found_session = 1;

			*phSession = idx;

			cackey_sessions[idx].active = 1;
			cackey_sessions[idx].slotID = slotID;
			cackey_sessions[idx].state = CKS_RO_PUBLIC_SESSION;
			cackey_sessions[idx].flags = flags;
			cackey_sessions[idx].ulDeviceError = 0;
			cackey_sessions[idx].pApplication = pApplication;
			cackey_sessions[idx].Notify = notify;

			cackey_sessions[idx].identities = NULL;
			cackey_sessions[idx].identities_count = 0;

			cackey_sessions[idx].search_active = 0;

			cackey_sessions[idx].sign_active = 0;

			cackey_sessions[idx].decrypt_active = 0;

			cackey_sessions[idx].identities = cackey_read_identities(&cackey_slots[slotID], &cackey_sessions[idx].identities_count);


			break;
		}
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!found_session) {
		CACKEY_DEBUG_PRINTF("Returning CKR_SESSION_COUNT (%i)", CKR_SESSION_COUNT);

		return(CKR_SESSION_COUNT);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_CloseSession)(CK_SESSION_HANDLE hSession) {
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	cackey_sessions[hSession].active = 0;
	cackey_free_identities(cackey_sessions[hSession].identities, cackey_sessions[hSession].identities_count);

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_CloseAllSessions)(CK_SLOT_ID slotID) {
	uint32_t idx;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_SLOT_ID_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_SLOT_ID_INVALID);
	}

	for (idx = 0; idx < (sizeof(cackey_sessions) / sizeof(cackey_sessions[0])); idx++) {
		if (cackey_sessions[idx].active) {
			if (cackey_sessions[idx].slotID != slotID) {
				continue;
			}

			cackey_mutex_unlock(cackey_biglock);
			C_CloseSession(idx);
			cackey_mutex_lock(cackey_biglock);
		}
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_GetSessionInfo)(CK_SESSION_HANDLE hSession, CK_SESSION_INFO_PTR pInfo) {
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (pInfo == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pInfo is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	pInfo->slotID = cackey_sessions[hSession].slotID;
	pInfo->state = cackey_sessions[hSession].state;
	pInfo->flags = cackey_sessions[hSession].flags;
	pInfo->ulDeviceError = cackey_sessions[hSession].ulDeviceError;

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_GetOperationState)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pOperationState, CK_ULONG_PTR pulOperationStateLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_SetOperationState)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pOperationState, CK_ULONG ulOperationStateLen, CK_OBJECT_HANDLE hEncryptionKey, CK_OBJECT_HANDLE hAuthenticationKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_LoginMutexArg)(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen, int lock_mutex) {
	CK_SLOT_ID slotID;
	cackey_ret get_pin_ret;
	char pinbuf[64];
	int mutex_retval;
	int tries_remaining;
	int login_ret;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (userType != CKU_USER) {
		CACKEY_DEBUG_PRINTF("Error.  We only support USER mode, asked for %lu mode.", (unsigned long) userType)

		return(CKR_USER_TYPE_INVALID);
	}

	if (lock_mutex) {
		mutex_retval = cackey_mutex_lock(cackey_biglock);
		if (mutex_retval != 0) {
			CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

			return(CKR_GENERAL_ERROR);
		}
	}

	if (!cackey_sessions[hSession].active) {
		if (lock_mutex) {
			cackey_mutex_unlock(cackey_biglock);
		}

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	slotID = cackey_sessions[hSession].slotID;

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		if (lock_mutex) {
			cackey_mutex_unlock(cackey_biglock);
		}

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		if (lock_mutex) {
			cackey_mutex_unlock(cackey_biglock);
		}

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_pin_command != NULL) {
		if (pPin != NULL) {
			CACKEY_DEBUG_PRINTF("Protected authentication path in effect and PIN provided !?");
		}

		get_pin_ret = cackey_get_pin(pinbuf);

		if (get_pin_ret != CACKEY_PCSC_S_OK) {
			CACKEY_DEBUG_PRINTF("cackey_get_pin() returned in failure, assuming the PIN was incorrect.");

			if (lock_mutex) {
				cackey_mutex_unlock(cackey_biglock);
			}

			return(CKR_PIN_INCORRECT);
		}

		pPin = (CK_UTF8CHAR_PTR) pinbuf;
		ulPinLen = strlen(pinbuf);
	}

	login_ret = cackey_login(&cackey_slots[slotID], pPin, ulPinLen, &tries_remaining, 3);
	if (login_ret != CACKEY_PCSC_S_OK) {
		if (lock_mutex) {
			cackey_mutex_unlock(cackey_biglock);
		}

		if (login_ret == CACKEY_PCSC_E_LOCKED) {
			CACKEY_DEBUG_PRINTF("Error.  Token is locked.");

			cackey_slots[slotID].token_flags |= CKF_USER_PIN_LOCKED;

			CACKEY_DEBUG_PRINTF("Returning CKR_PIN_LOCKED (%i)", (int) CKR_PIN_LOCKED);

			return(CKR_PIN_LOCKED);
		} else if (login_ret == CACKEY_PCSC_E_BADPIN) {
			CACKEY_DEBUG_PRINTF("Error.  Invalid PIN.");

			cackey_slots[slotID].token_flags |= CKF_USER_PIN_COUNT_LOW;

			if (tries_remaining == 1) {
				cackey_slots[slotID].token_flags |= CKF_USER_PIN_FINAL_TRY;
			}

			CACKEY_DEBUG_PRINTF("Returning CKR_PIN_INCORRECT (%i)", (int) CKR_PIN_INCORRECT);

			return(CKR_PIN_INCORRECT);
		}

		CACKEY_DEBUG_PRINTF("Error.  Unknown error returned from cackey_login() (%i)", login_ret);

		return(CKR_GENERAL_ERROR);
	}

	cackey_slots[slotID].token_flags &= ~(CKF_USER_PIN_LOCKED | CKF_USER_PIN_COUNT_LOW | CKF_LOGIN_REQUIRED | CKF_USER_PIN_FINAL_TRY);

	cackey_sessions[hSession].state = CKS_RO_USER_FUNCTIONS;

	if (lock_mutex) {
		mutex_retval = cackey_mutex_unlock(cackey_biglock);
		if (mutex_retval != 0) {
			CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

			return(CKR_GENERAL_ERROR);
		}
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_Login)(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, CK_UTF8CHAR_PTR pPin, CK_ULONG ulPinLen) {
	return(C_LoginMutexArg(hSession, userType, pPin, ulPinLen, 1));
}

CK_DEFINE_FUNCTION(CK_RV, C_Logout)(CK_SESSION_HANDLE hSession) {
	CK_SLOT_ID slotID;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	slotID = cackey_sessions[hSession].slotID;

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_GENERAL_ERROR);
	}

	cackey_sessions[hSession].state = CKS_RO_PUBLIC_SESSION;

	if (cackey_pin_command == NULL) {
		cackey_slots[slotID].token_flags = CKF_LOGIN_REQUIRED;
	} else {
		cackey_slots[slotID].token_flags = 0;
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_CreateObject)(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phObject) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_CopyObject)(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phNewObject) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DestroyObject)(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_GetObjectSize)(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, CK_ULONG_PTR pulSize) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_GetAttributeValue)(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) {
	CK_ATTRIBUTE *curr_attr;
	struct cackey_identity *identity;
	unsigned long identity_idx, attr_idx, sess_attr_idx, num_ids;
	int mutex_retval;
	CK_RV retval = CKR_OK;
	CK_VOID_PTR pValue;
	CK_ULONG ulValueLen;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (hObject == 0) {
		CACKEY_DEBUG_PRINTF("Error.  Object handle out of range.");
		
		return(CKR_OBJECT_HANDLE_INVALID);
	}

	if (ulCount == 0) {
		/* Short circuit, if zero objects were specified return zero items immediately */
		CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i) (short circuit)", CKR_OK);

		return(CKR_OK);
	}

	if (pTemplate == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  pTemplate is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	identity_idx = hObject - 1;

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	num_ids = cackey_sessions[hSession].identities_count;

	if (identity_idx >= num_ids) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Object handle out of range.  identity_idx = %lu, num_ids = %lu.", (unsigned long) identity_idx, (unsigned long) num_ids);

		return(CKR_OBJECT_HANDLE_INVALID);
	}

	identity = &cackey_sessions[hSession].identities[identity_idx];

	for (attr_idx = 0; attr_idx < ulCount; attr_idx++) {
		curr_attr = &pTemplate[attr_idx];

		pValue = NULL;
		ulValueLen = (CK_LONG) -1;

		CACKEY_DEBUG_PRINTF("Looking for attribute 0x%08lx (identity:%lu) ...", (unsigned long) curr_attr->type, (unsigned long) identity_idx);

		for (sess_attr_idx = 0; sess_attr_idx < identity->attributes_count; sess_attr_idx++) {
			if (identity->attributes[sess_attr_idx].type == curr_attr->type) {
				CACKEY_DEBUG_PRINTF(" ... found it, pValue = %p, ulValueLen = %lu", identity->attributes[sess_attr_idx].pValue, identity->attributes[sess_attr_idx].ulValueLen);
				
				pValue = identity->attributes[sess_attr_idx].pValue;
				ulValueLen = identity->attributes[sess_attr_idx].ulValueLen;
			}
		}

		if (curr_attr->pValue && pValue) {
			if (curr_attr->ulValueLen >= ulValueLen) {
				memcpy(curr_attr->pValue, pValue, ulValueLen);
			} else {
				ulValueLen = (CK_LONG) -1;

				retval = CKR_BUFFER_TOO_SMALL;
			}
		}

		curr_attr->ulValueLen = ulValueLen;
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (retval == CKR_ATTRIBUTE_TYPE_INVALID) {
		CACKEY_DEBUG_PRINTF("Returning CKR_ATTRIBUTE_TYPE_INVALID (%i)", (int) retval);
	} else if (retval == CKR_BUFFER_TOO_SMALL) {
		CACKEY_DEBUG_PRINTF("Returning CKR_BUFFER_TOO_SMALL (%i)", (int) retval);
	} else if (retval == CKR_OK) {
		CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", (int) retval);
	} else {
		CACKEY_DEBUG_PRINTF("Returning %i", (int) retval);
	}

	return(retval);
}

CK_DEFINE_FUNCTION(CK_RV, C_SetAttributeValue)(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_FindObjectsInit)(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount) {
	CK_SLOT_ID slotID;
	CK_ULONG idx;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (cackey_sessions[hSession].search_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Search already active.");
		
		return(CKR_OPERATION_ACTIVE);
	}

	slotID = cackey_sessions[hSession].slotID;

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].slot_reset) {
		CACKEY_DEBUG_PRINTF("The slot has been reset since we last looked for identities -- rescanning");

		if (cackey_sessions[hSession].identities != NULL) {
			cackey_free_identities(cackey_sessions[hSession].identities, cackey_sessions[hSession].identities_count);

			cackey_sessions[hSession].identities = NULL;
			cackey_sessions[hSession].identities_count = 0;
		}

		if (cackey_slots[slotID].label != NULL) {
			free(cackey_slots[slotID].label);
			cackey_slots[slotID].label = NULL;
		}

		cackey_mark_slot_reset(&cackey_slots[slotID]);
		cackey_slots[slotID].slot_reset = 0;
	}

	if (cackey_sessions[hSession].identities == NULL) {
		cackey_sessions[hSession].identities = cackey_read_identities(&cackey_slots[slotID], &cackey_sessions[hSession].identities_count);
	}

	if (pTemplate != NULL) {
		if (ulCount != 0) {
			cackey_sessions[hSession].search_query_count = ulCount;
			cackey_sessions[hSession].search_query = malloc(ulCount * sizeof(*pTemplate));

			memcpy(cackey_sessions[hSession].search_query, pTemplate, ulCount * sizeof(*pTemplate));
			for (idx = 0; idx < ulCount; idx++) {
				if (pTemplate[idx].ulValueLen == 0) {
					cackey_sessions[hSession].search_query[idx].pValue = NULL;

					continue;
				}

				cackey_sessions[hSession].search_query[idx].pValue = malloc(pTemplate[idx].ulValueLen);

				if (cackey_sessions[hSession].search_query[idx].pValue) {
					memcpy(cackey_sessions[hSession].search_query[idx].pValue, pTemplate[idx].pValue, pTemplate[idx].ulValueLen);
				}
			}
		} else {
			cackey_sessions[hSession].search_query_count = 0;
			cackey_sessions[hSession].search_query = NULL;
		}
	} else {
		if (ulCount != 0) {
			cackey_mutex_unlock(cackey_biglock);

			CACKEY_DEBUG_PRINTF("Error.  Search query specified as NULL, but number of query terms not specified as 0.");

			return(CKR_ARGUMENTS_BAD);
		}

		cackey_sessions[hSession].search_query_count = 0;
		cackey_sessions[hSession].search_query = NULL;
	}

	cackey_sessions[hSession].search_active = 1;
	cackey_sessions[hSession].search_curr_id = 0;

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

static int cackey_pkcs11_compare_attributes(CK_ATTRIBUTE *a, CK_ATTRIBUTE *b) {
	unsigned char *smallbuf, *largebuf;
	size_t smallbuf_len, largebuf_len;

	if (a->type != b->type) {
		return(0);
	}

	CACKEY_DEBUG_PRINTF("    ... found matching type ...");

	CACKEY_DEBUG_PRINTBUF("    ... our value:", a->pValue, a->ulValueLen);

	if (b->pValue == NULL) {
		CACKEY_DEBUG_PRINTF("       ... found wildcard match");

		return(1);
	}

	if (a->pValue == NULL) {
		return(0);
	}

 	if (b->ulValueLen == a->ulValueLen && memcmp(a->pValue, b->pValue, b->ulValueLen) == 0) {
		CACKEY_DEBUG_PRINTF("       ... found exact match");

		return(1);
	}

	switch (a->type) {
		case CKA_MODULUS:
			if (a->ulValueLen == b->ulValueLen) {
				break;
			}

			if (a->ulValueLen > b->ulValueLen) {
				smallbuf = b->pValue;
				smallbuf_len = b->ulValueLen;

				largebuf = a->pValue;
				largebuf_len = a->ulValueLen;
			} else {
				smallbuf = a->pValue;
				smallbuf_len = a->ulValueLen;

				largebuf = b->pValue;
				largebuf_len = b->ulValueLen;
			}

			for (; largebuf_len != smallbuf_len; largebuf++,largebuf_len--) {
				if (largebuf[0] != 0) {
					break;
				}
			}

			if (largebuf_len != smallbuf_len) {
				break;
			}

			if (memcmp(largebuf, smallbuf, smallbuf_len) == 0) {
				CACKEY_DEBUG_PRINTF("       ... found approximate match");

				return(1);
			}

			break;
	}

	return(0);
}

CK_DEFINE_FUNCTION(CK_RV, C_FindObjects)(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE_PTR phObject, CK_ULONG ulMaxObjectCount, CK_ULONG_PTR pulObjectCount) {
	struct cackey_identity *curr_id;
	CK_ATTRIBUTE *curr_attr;
	CK_ULONG curr_id_idx, curr_out_id_idx, curr_attr_idx, sess_attr_idx;
	CK_ULONG matched_count, prev_matched_count;
	int mutex_retval;
#ifdef CACKEY_DEBUG_SEARCH_SPEEDTEST
	struct timeval start, end;
	uint64_t start_int, end_int;
#endif

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (pulObjectCount == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  pulObjectCount is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (phObject == NULL && ulMaxObjectCount == 0) {
		/* Short circuit, if zero objects were specified return zero items immediately */
		*pulObjectCount = 0;

		CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i) (short circuit)", CKR_OK);

		return(CKR_OK);
	}

	if (phObject == NULL) {
		CACKEY_DEBUG_PRINTF("Error.  phObject is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (ulMaxObjectCount == 0) {
		CACKEY_DEBUG_PRINTF("Error.  Maximum number of objects specified as zero.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (!cackey_sessions[hSession].search_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Search not active.");
		
		return(CKR_OPERATION_NOT_INITIALIZED);
	}

#ifdef CACKEY_DEBUG_SEARCH_SPEEDTEST
	gettimeofday(&start, NULL);
#endif

	curr_out_id_idx = 0;
	for (curr_id_idx = cackey_sessions[hSession].search_curr_id; curr_id_idx < cackey_sessions[hSession].identities_count && ulMaxObjectCount; curr_id_idx++) {
		curr_id = &cackey_sessions[hSession].identities[curr_id_idx];

		CACKEY_DEBUG_PRINTF("Processing identity:%lu", (unsigned long) curr_id_idx);

		matched_count = 0;

		for (curr_attr_idx = 0; curr_attr_idx < cackey_sessions[hSession].search_query_count; curr_attr_idx++) {
			prev_matched_count = matched_count;

			curr_attr = &cackey_sessions[hSession].search_query[curr_attr_idx];

			CACKEY_DEBUG_PRINTF("  Checking for attribute %s (0x%08lx) in identity:%i...", CACKEY_DEBUG_FUNC_ATTRIBUTE_TO_STR(curr_attr->type), (unsigned long) curr_attr->type, (int) curr_id_idx);
			CACKEY_DEBUG_PRINTBUF("    Value looking for:", curr_attr->pValue, curr_attr->ulValueLen);

			for (sess_attr_idx = 0; sess_attr_idx < curr_id->attributes_count; sess_attr_idx++) {
				if (cackey_pkcs11_compare_attributes(&curr_id->attributes[sess_attr_idx], curr_attr)) {
					matched_count++;

					break;
				}
			}

			/* If the attribute could not be matched, do not try to match additional attributes */
			if (prev_matched_count == matched_count) {
				break;
			}
		}

		if (matched_count == cackey_sessions[hSession].search_query_count) {
			CACKEY_DEBUG_PRINTF("  ... All %i attributes checked for found, adding identity:%i to returned list", (int) cackey_sessions[hSession].search_query_count, (int) curr_id_idx);

			phObject[curr_out_id_idx] = curr_id_idx + 1;

			ulMaxObjectCount--;

			curr_out_id_idx++;
		} else {
			CACKEY_DEBUG_PRINTF("  ... Not all %i (only found %i) attributes checked for found, not adding identity:%i", (int) cackey_sessions[hSession].search_query_count, (int) matched_count, (int) curr_id_idx);
		}
	}
	cackey_sessions[hSession].search_curr_id = curr_id_idx;
	*pulObjectCount = curr_out_id_idx;

#ifdef CACKEY_DEBUG_SEARCH_SPEEDTEST
	gettimeofday(&end, NULL);
	start_int = (start.tv_sec * 1000000) + start.tv_usec;
	end_int = (end.tv_sec * 1000000) + end.tv_usec;
	fprintf(stderr, "Search took %lu microseconds\n", (unsigned long) (end_int - start_int));
#endif

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i), num objects = %lu", CKR_OK, *pulObjectCount);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_FindObjectsFinal)(CK_SESSION_HANDLE hSession) {
	CK_ULONG idx;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (!cackey_sessions[hSession].search_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Search not active.");
		
		return(CKR_OPERATION_NOT_INITIALIZED);
	}

	cackey_sessions[hSession].search_active = 0;

	for (idx = 0; idx < cackey_sessions[hSession].search_query_count; idx++) {
		if (cackey_sessions[hSession].search_query[idx].pValue) {
			free(cackey_sessions[hSession].search_query[idx].pValue);
		}
	}

	if (cackey_sessions[hSession].search_query) {
		free(cackey_sessions[hSession].search_query);
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_EncryptInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_Encrypt)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData, CK_ULONG_PTR pulEncryptedDataLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_EncryptUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, CK_ULONG ulPartLen, CK_BYTE_PTR pEncryptedPart, CK_ULONG_PTR pulEncryptedPartLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_EncryptFinal)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pLastEncryptedPart, CK_ULONG_PTR pulLastEncryptedPartLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DecryptInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) {
	int mutex_retval;

	hKey--;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (pMechanism == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pMechanism is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (pMechanism->mechanism != CKM_RSA_PKCS) {
		CACKEY_DEBUG_PRINTF("Error. pMechanism->mechanism not specified as CKM_RSA_PKCS");

		return(CKR_MECHANISM_PARAM_INVALID);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (cackey_sessions[hSession].decrypt_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Decrypt already in progress.");
		
		return(CKR_OPERATION_ACTIVE);
	}

	if (hKey >= cackey_sessions[hSession].identities_count) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Key handle out of range (requested key %lu, only %lu identities available).", (unsigned long) hKey, (unsigned long) cackey_sessions[hSession].identities_count);

		return(CKR_KEY_HANDLE_INVALID);
	}

	cackey_sessions[hSession].decrypt_active = 1;

	cackey_sessions[hSession].decrypt_mechanism = pMechanism->mechanism;
	cackey_sessions[hSession].decrypt_mech_parm = pMechanism->pParameter;
	cackey_sessions[hSession].decrypt_mech_parmlen = pMechanism->ulParameterLen;
	cackey_sessions[hSession].decrypt_identity = &cackey_sessions[hSession].identities[hKey];

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_Decrypt)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedData, CK_ULONG ulEncryptedDataLen, CK_BYTE_PTR pData, CK_ULONG_PTR pulDataLen) {
	CK_ULONG datalen_update, datalen_final;
	CK_RV decrypt_ret;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (pulDataLen == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pulDataLen is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	datalen_update = *pulDataLen;

	decrypt_ret = C_DecryptUpdate(hSession, pEncryptedData, ulEncryptedDataLen, pData, &datalen_update);
	if (decrypt_ret != CKR_OK) {
		CACKEY_DEBUG_PRINTF("Error.  DecryptUpdate() returned failure (rv = %lu).", (unsigned long) decrypt_ret);

		if (decrypt_ret != CKR_BUFFER_TOO_SMALL) {
			/* Terminate decryption operation */

			mutex_retval = cackey_mutex_lock(cackey_biglock);
			if (mutex_retval != 0) {
				CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

				return(CKR_GENERAL_ERROR);
			}

			if (!cackey_sessions[hSession].active) {
				cackey_mutex_unlock(cackey_biglock);

				CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
				return(CKR_SESSION_HANDLE_INVALID);
			}

			if (!cackey_sessions[hSession].decrypt_active) {
				cackey_mutex_unlock(cackey_biglock);

				CACKEY_DEBUG_PRINTF("Error.  Decrypt not active.");
		
				return(CKR_OPERATION_NOT_INITIALIZED);
			}

			cackey_sessions[hSession].decrypt_active = 0;

			mutex_retval = cackey_mutex_unlock(cackey_biglock);
			if (mutex_retval != 0) {
				CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

				return(CKR_GENERAL_ERROR);
			}
		}

		return(decrypt_ret);
	}

	if (pData) {
		pData += datalen_update;
	}
	datalen_final = *pulDataLen - datalen_update;

	decrypt_ret = C_DecryptFinal(hSession, pData, &datalen_final);
	if (decrypt_ret != CKR_OK) {
		CACKEY_DEBUG_PRINTF("Error.  DecryptFinal() returned failure (rv = %lu).", (unsigned long) decrypt_ret);

		return(decrypt_ret);
	}

	*pulDataLen = datalen_update + datalen_final;

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_DecryptUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedPart, CK_ULONG ulEncryptedPartLen, CK_BYTE_PTR pPart, CK_ULONG_PTR pulPartLen) {
	static CK_BYTE buf[16384];
	ssize_t buflen;
	CK_SLOT_ID slotID;
	CK_RV retval = CKR_GENERAL_ERROR;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (pEncryptedPart == NULL && ulEncryptedPartLen == 0) {
		/* Short circuit if we are asked to decrypt nothing... */
		CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i) (short circuit)", CKR_OK);

		return(CKR_OK);
	}

	if (pEncryptedPart == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pEncryptedPart is NULL, but ulEncryptedPartLen is not 0.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (ulEncryptedPartLen == 0) {
		CACKEY_DEBUG_PRINTF("Error. ulEncryptedPartLen is 0, but pPart is not NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (pulPartLen == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pulPartLen is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (!cackey_sessions[hSession].decrypt_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Decrypt not active.");
		
		return(CKR_OPERATION_NOT_INITIALIZED);
	}

	slotID = cackey_sessions[hSession].slotID;

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_GENERAL_ERROR);
	}

	switch (cackey_sessions[hSession].decrypt_mechanism) {
		case CKM_RSA_PKCS:
			/* Ask card to decrypt */
			buflen = cackey_signdecrypt(&cackey_slots[slotID], cackey_sessions[hSession].decrypt_identity, pEncryptedPart, ulEncryptedPartLen, buf, sizeof(buf), 0, 1);

			if (buflen == CACKEY_PCSC_E_NEEDLOGIN && cackey_pin_command != NULL) {
				if (C_LoginMutexArg(hSession, CKU_USER, NULL, 0, 0) == CKR_OK) {
					buflen = cackey_signdecrypt(&cackey_slots[slotID], cackey_sessions[hSession].decrypt_identity, pEncryptedPart, ulEncryptedPartLen, buf, sizeof(buf), 0, 1);
				}
			}

			if (buflen < 0) {
				/* Decryption failed. */
				if (buflen == CACKEY_PCSC_E_NEEDLOGIN) {
					retval = CKR_USER_NOT_LOGGED_IN;
				} else if (buflen == CACKEY_PCSC_E_TOKENABSENT) {
					retval = CKR_DEVICE_REMOVED;
				} else {
					CACKEY_DEBUG_PRINTF("Failed to send APDU, error = %li", (long int) buflen);

					retval = CKR_GENERAL_ERROR;
				}
			} else if (((unsigned long) buflen) > *pulPartLen && pPart) {
				/* Decrypted data too large */
				retval = CKR_BUFFER_TOO_SMALL;
			} else {
				if (pPart) {
					memcpy(pPart, buf, buflen);
				}

				*pulPartLen = buflen;

				retval = CKR_OK;
			}

			break;
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning %i", (int) retval);

	return(retval);
}

CK_DEFINE_FUNCTION(CK_RV, C_DecryptFinal)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pLastPart, CK_ULONG_PTR pulLastPartLen) {
	int mutex_retval;
	int terminate_decrypt = 1;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (pulLastPartLen == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pulLastPartLen is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (!cackey_sessions[hSession].decrypt_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Decrypt not active.");
		
		return(CKR_OPERATION_NOT_INITIALIZED);
	}

	*pulLastPartLen = 0;

	if (pLastPart == NULL) {
		terminate_decrypt = 0;
	}

	if (terminate_decrypt) {
		cackey_sessions[hSession].decrypt_active = 0;
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_DigestInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_Digest)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, CK_BYTE_PTR pDigest, CK_ULONG_PTR pulDigestLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DigestUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, CK_ULONG ulPartLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DigestKey)(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DigestFinal)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest, CK_ULONG_PTR pulDigestLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_SignInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) {
	int mutex_retval;

	hKey--;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (pMechanism == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pMechanism is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (pMechanism->mechanism != CKM_RSA_PKCS) {
		CACKEY_DEBUG_PRINTF("Error. pMechanism->mechanism not specified as CKM_RSA_PKCS");

		return(CKR_MECHANISM_PARAM_INVALID);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (cackey_sessions[hSession].sign_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Sign already in progress.");
		
		return(CKR_OPERATION_ACTIVE);
	}

	if (hKey >= cackey_sessions[hSession].identities_count) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Key handle out of range (requested key %lu, only %lu identities available).", (unsigned long) hKey, (unsigned long) cackey_sessions[hSession].identities_count);

		return(CKR_KEY_HANDLE_INVALID);
	}

	cackey_sessions[hSession].sign_active = 1;

	cackey_sessions[hSession].sign_mechanism = pMechanism->mechanism;

	cackey_sessions[hSession].sign_buflen = 128;
	cackey_sessions[hSession].sign_bufused = 0;
	cackey_sessions[hSession].sign_buf = malloc(sizeof(*cackey_sessions[hSession].sign_buf) * cackey_sessions[hSession].sign_buflen);

	CACKEY_DEBUG_PRINTF("Session %lu sign_identity is %p (identity #%lu)", (unsigned long) hSession, (void *) &cackey_sessions[hSession].identities[hKey], (unsigned long) hKey);
	cackey_sessions[hSession].sign_identity = &cackey_sessions[hSession].identities[hKey];

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_Sign)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen) {
	unsigned long start_sign_bufused;
	CK_RV sign_ret;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	start_sign_bufused = cackey_sessions[hSession].sign_bufused;

	sign_ret = C_SignUpdate(hSession, pData, ulDataLen);
	if (sign_ret != CKR_OK) {
		CACKEY_DEBUG_PRINTF("Error.  SignUpdate() returned failure (rv = %lu).", (unsigned long) sign_ret);

		if (sign_ret != CKR_BUFFER_TOO_SMALL) {
			mutex_retval = cackey_mutex_lock(cackey_biglock);
			if (mutex_retval != 0) {
				CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

				return(CKR_GENERAL_ERROR);
			}

			if (!cackey_sessions[hSession].active) {
				cackey_mutex_unlock(cackey_biglock);

				CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
				return(CKR_SESSION_HANDLE_INVALID);
			}

			if (!cackey_sessions[hSession].sign_active) {
				cackey_mutex_unlock(cackey_biglock);

				CACKEY_DEBUG_PRINTF("Error.  Sign not active.");
		
				return(CKR_OPERATION_NOT_INITIALIZED);
			}

			cackey_sessions[hSession].sign_active = 0;

			mutex_retval = cackey_mutex_unlock(cackey_biglock);
			if (mutex_retval != 0) {
				CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

				return(CKR_GENERAL_ERROR);
			}
		}

		return(sign_ret);
	}

	sign_ret = C_SignFinal(hSession, pSignature, pulSignatureLen);
	if (sign_ret != CKR_OK) {
		if (sign_ret == CKR_BUFFER_TOO_SMALL) {
			CACKEY_DEBUG_PRINTF("SignFinal() returned CKR_BUFFER_TOO_SMALL (rv = %lu), undoing C_SignUpdate()", (unsigned long) sign_ret);

			cackey_sessions[hSession].sign_bufused = start_sign_bufused;

			return(sign_ret);
		}

		CACKEY_DEBUG_PRINTF("Error.  SignFinal() returned failure (rv = %lu).", (unsigned long) sign_ret);

		return(sign_ret);
	}

	if (pSignature == NULL) {
		CACKEY_DEBUG_PRINTF("pSignature specified as NULL, undoing C_SignUpdate()");

		cackey_sessions[hSession].sign_bufused = start_sign_bufused;

		return(sign_ret);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_SignUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, CK_ULONG ulPartLen) {
	int mutex_retval;
	int resizeRetry;
	int needResize;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (pPart == NULL && ulPartLen == 0) {
		/* Short circuit if we are asked to sign nothing... */
		CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i) (short circuit)", CKR_OK);

		return(CKR_OK);
	}

	if (pPart == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pPart is NULL, but ulPartLen is not 0.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (ulPartLen == 0) {
		CACKEY_DEBUG_PRINTF("Error. ulPartLen is 0, but pPart is not NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (!cackey_sessions[hSession].sign_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Sign not active.");
		
		return(CKR_OPERATION_NOT_INITIALIZED);
	}

	switch (cackey_sessions[hSession].sign_mechanism) {
		case CKM_RSA_PKCS:
			/* Accumulate directly */
			for (resizeRetry = 0; resizeRetry < 11; resizeRetry++) {
				needResize = 0;
				if ((cackey_sessions[hSession].sign_bufused + ulPartLen) > cackey_sessions[hSession].sign_buflen) {
					needResize = 1;
				}

				if (!needResize) {
					break;
				}

				CACKEY_DEBUG_PRINTF("Resizing signing buffer (try #%i of 10 -- 11th is fatal)", resizeRetry);

				if (resizeRetry == 10) {
					free(cackey_sessions[hSession].sign_buf);

					cackey_sessions[hSession].sign_buflen = 0;
					cackey_sessions[hSession].sign_buf = NULL;

					break;
				}

				cackey_sessions[hSession].sign_buflen *= 2;

				cackey_sessions[hSession].sign_buf = realloc(cackey_sessions[hSession].sign_buf, sizeof(*cackey_sessions[hSession].sign_buf) * cackey_sessions[hSession].sign_buflen);
			}

			if (cackey_sessions[hSession].sign_buf == NULL) {
				cackey_mutex_unlock(cackey_biglock);

				CACKEY_DEBUG_PRINTF("Error.  Signing buffer is NULL.");

				return(CKR_GENERAL_ERROR);
			}

			memcpy(cackey_sessions[hSession].sign_buf + cackey_sessions[hSession].sign_bufused, pPart, ulPartLen);

			cackey_sessions[hSession].sign_bufused += ulPartLen;

			break;
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}

CK_DEFINE_FUNCTION(CK_RV, C_SignFinal)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen) {
	static CK_BYTE sigbuf[1024];
	ssize_t sigbuflen;
	CK_SLOT_ID slotID;
	CK_RV retval = CKR_GENERAL_ERROR;
	int terminate_sign = 1;
	int mutex_retval;

	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	if (pulSignatureLen == NULL) {
		CACKEY_DEBUG_PRINTF("Error. pulSignatureLen is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (hSession == 0 || hSession >= (sizeof(cackey_sessions) / sizeof(cackey_sessions[0]))) {
		CACKEY_DEBUG_PRINTF("Error.  Session out of range.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	mutex_retval = cackey_mutex_lock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Locking failed.");

		return(CKR_GENERAL_ERROR);
	}

	if (!cackey_sessions[hSession].active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Session not active.");
		
		return(CKR_SESSION_HANDLE_INVALID);
	}

	if (!cackey_sessions[hSession].sign_active) {
		cackey_mutex_unlock(cackey_biglock);

		CACKEY_DEBUG_PRINTF("Error.  Sign not active.");
		
		return(CKR_OPERATION_NOT_INITIALIZED);
	}

	slotID = cackey_sessions[hSession].slotID;

	if (slotID < 0 || slotID >= (sizeof(cackey_slots) / sizeof(cackey_slots[0]))) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), outside of valid range", slotID);

		return(CKR_GENERAL_ERROR);
	}

	if (cackey_slots[slotID].active == 0) {
		CACKEY_DEBUG_PRINTF("Error. Invalid slot requested (%lu), slot not currently active", slotID);

		cackey_mutex_unlock(cackey_biglock);

		return(CKR_GENERAL_ERROR);
	}

	switch (cackey_sessions[hSession].sign_mechanism) {
		case CKM_RSA_PKCS:
			/* Ask card to sign */
			CACKEY_DEBUG_PRINTF("Asking to sign from identity %p in session %lu", (void *) cackey_sessions[hSession].sign_identity, (unsigned long) hSession);
			sigbuflen = cackey_signdecrypt(&cackey_slots[slotID], cackey_sessions[hSession].sign_identity, cackey_sessions[hSession].sign_buf, cackey_sessions[hSession].sign_bufused, sigbuf, sizeof(sigbuf), 1, 0);

			if (sigbuflen == CACKEY_PCSC_E_NEEDLOGIN && cackey_pin_command != NULL) {
				if (C_LoginMutexArg(hSession, CKU_USER, NULL, 0, 0) == CKR_OK) {
					sigbuflen = cackey_signdecrypt(&cackey_slots[slotID], cackey_sessions[hSession].sign_identity, cackey_sessions[hSession].sign_buf, cackey_sessions[hSession].sign_bufused, sigbuf, sizeof(sigbuf), 1, 0);
				}
			}

			if (sigbuflen < 0) {
				/* Signing failed. */
				if (sigbuflen == CACKEY_PCSC_E_NEEDLOGIN) {
					retval = CKR_USER_NOT_LOGGED_IN;
				} else if (sigbuflen == CACKEY_PCSC_E_TOKENABSENT) {
					retval = CKR_DEVICE_REMOVED;
				} else {
					retval = CKR_GENERAL_ERROR;
				}
			} else if (((unsigned long) sigbuflen) > *pulSignatureLen && pSignature) {
				/* Signed data too large */
				CACKEY_DEBUG_PRINTF("retval = CKR_BUFFER_TOO_SMALL;  sigbuflen = %lu, pulSignatureLen = %lu", (unsigned long) sigbuflen, (unsigned long) *pulSignatureLen);

				retval = CKR_BUFFER_TOO_SMALL;

				terminate_sign = 0;
			} else {
				terminate_sign = 0;

				if (pSignature) {
					memcpy(pSignature, sigbuf, sigbuflen);

					terminate_sign = 1;
				}

				*pulSignatureLen = sigbuflen;

				retval = CKR_OK;
			}

			break;
	}

	if (terminate_sign) {
		if (cackey_sessions[hSession].sign_buf) {
			free(cackey_sessions[hSession].sign_buf);
		}

		cackey_sessions[hSession].sign_active = 0;
	}

	mutex_retval = cackey_mutex_unlock(cackey_biglock);
	if (mutex_retval != 0) {
		CACKEY_DEBUG_PRINTF("Error.  Unlocking failed.");

		return(CKR_GENERAL_ERROR);
	}

	CACKEY_DEBUG_PRINTF("Returning %i", (int) retval);

	return(retval);
}

CK_DEFINE_FUNCTION(CK_RV, C_SignRecoverInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_SignRecover)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_VerifyInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_Verify)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_VerifyUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, CK_ULONG ulPartLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_VerifyFinal)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_VerifyRecoverInit)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_VerifyRecover)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen, CK_BYTE_PTR pData, CK_ULONG_PTR pulDataLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DigestEncryptUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, CK_ULONG ulPartLen, CK_BYTE_PTR pEncryptedPart, CK_ULONG_PTR pulEncryptedPartLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DecryptDigestUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedPart, CK_ULONG ulEncryptedPartLen, CK_BYTE_PTR pPart, CK_ULONG_PTR pulPartLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_SignEncryptUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, CK_ULONG ulPartLen, CK_BYTE_PTR pEncryptedPart, CK_ULONG_PTR pulEncryptedPartLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DecryptVerifyUpdate)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pEncryptedPart, CK_ULONG ulEncryptedPartLen, CK_BYTE_PTR pPart, CK_ULONG_PTR pulPartLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_GenerateKey)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_GenerateKeyPair)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_ATTRIBUTE_PTR pPublicKeyTemplate, CK_ULONG ulPublicKeyAttributeCount, CK_ATTRIBUTE_PTR pPrivateKeyTemplate, CK_ULONG ulPrivateKeyAttributeCount, CK_OBJECT_HANDLE_PTR phPublicKey, CK_OBJECT_HANDLE_PTR phPrivateKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_WrapKey)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hWrappingKey, CK_OBJECT_HANDLE hKey, CK_BYTE_PTR pWrappedKey, CK_ULONG_PTR pulWrappedKeyLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_UnwrapKey)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hUnwrappingKey, CK_BYTE_PTR pWrappedKey, CK_ULONG ulWrappedKeyLen, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_DeriveKey)(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_SeedRandom)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSeed, CK_ULONG ulSeedLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

CK_DEFINE_FUNCTION(CK_RV, C_GenerateRandom)(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pRandomData, CK_ULONG ulRandomLen) {
	CACKEY_DEBUG_PRINTF("Called.");

	if (!cackey_initialized) {
		CACKEY_DEBUG_PRINTF("Error.  Not initialized.");

		return(CKR_CRYPTOKI_NOT_INITIALIZED);
	}

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_SUPPORTED (%i)", CKR_FUNCTION_NOT_SUPPORTED);

	return(CKR_FUNCTION_NOT_SUPPORTED);
}

/* Deprecated Function */
CK_DEFINE_FUNCTION(CK_RV, C_GetFunctionStatus)(CK_SESSION_HANDLE hSession) {
	CACKEY_DEBUG_PRINTF("Called.");

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_PARALLEL (%i)", CKR_FUNCTION_NOT_PARALLEL);

	return(CKR_FUNCTION_NOT_PARALLEL);

	hSession = hSession; /* Supress unused variable warning */
}

/* Deprecated Function */
CK_DEFINE_FUNCTION(CK_RV, C_CancelFunction)(CK_SESSION_HANDLE hSession) {
	CACKEY_DEBUG_PRINTF("Called.");

	CACKEY_DEBUG_PRINTF("Returning CKR_FUNCTION_NOT_PARALLEL (%i)", CKR_FUNCTION_NOT_PARALLEL);

	return(CKR_FUNCTION_NOT_PARALLEL);

	hSession = hSession; /* Supress unused variable warning */
}

CK_DEFINE_FUNCTION(CK_RV, C_GetFunctionList)(CK_FUNCTION_LIST_PTR_PTR ppFunctionList) {
	static CK_FUNCTION_LIST_PTR spFunctionList = NULL;
	CK_FUNCTION_LIST_PTR pFunctionList;

	CACKEY_DEBUG_PRINTF("Called.");

	if (ppFunctionList == NULL) {
		CACKEY_DEBUG_PRINTF("Error. ppFunctionList is NULL.");

		return(CKR_ARGUMENTS_BAD);
	}

	if (spFunctionList != NULL) {
		*ppFunctionList = spFunctionList;

		CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

		return(CKR_OK);
	}

	pFunctionList = malloc(sizeof(*pFunctionList));

	pFunctionList->version.major = ((CACKEY_CRYPTOKI_VERSION_CODE) >> 16) & 0xff;
	pFunctionList->version.minor = ((CACKEY_CRYPTOKI_VERSION_CODE) >> 8) & 0xff;

	pFunctionList->C_Initialize = C_Initialize;
	pFunctionList->C_Finalize = C_Finalize;
	pFunctionList->C_GetInfo = C_GetInfo;
	pFunctionList->C_GetSlotList = C_GetSlotList;
	pFunctionList->C_GetSlotInfo = C_GetSlotInfo;
	pFunctionList->C_GetTokenInfo = C_GetTokenInfo;
	pFunctionList->C_WaitForSlotEvent = C_WaitForSlotEvent;
	pFunctionList->C_GetMechanismList = C_GetMechanismList;
	pFunctionList->C_GetMechanismInfo = C_GetMechanismInfo;
	pFunctionList->C_InitToken = C_InitToken;
	pFunctionList->C_InitPIN = C_InitPIN;
	pFunctionList->C_SetPIN = C_SetPIN;
	pFunctionList->C_OpenSession = C_OpenSession;
	pFunctionList->C_CloseSession = C_CloseSession;
	pFunctionList->C_CloseAllSessions = C_CloseAllSessions;
	pFunctionList->C_GetSessionInfo = C_GetSessionInfo;
	pFunctionList->C_GetOperationState = C_GetOperationState;
	pFunctionList->C_SetOperationState = C_SetOperationState;
	pFunctionList->C_Login = C_Login;
	pFunctionList->C_Logout = C_Logout;
	pFunctionList->C_CreateObject = C_CreateObject;
	pFunctionList->C_CopyObject = C_CopyObject;
	pFunctionList->C_DestroyObject = C_DestroyObject;
	pFunctionList->C_GetObjectSize = C_GetObjectSize;
	pFunctionList->C_GetAttributeValue = C_GetAttributeValue;
	pFunctionList->C_SetAttributeValue = C_SetAttributeValue;
	pFunctionList->C_FindObjectsInit = C_FindObjectsInit;
	pFunctionList->C_FindObjects = C_FindObjects;
	pFunctionList->C_FindObjectsFinal = C_FindObjectsFinal;
	pFunctionList->C_EncryptInit = C_EncryptInit;
	pFunctionList->C_Encrypt = C_Encrypt;
	pFunctionList->C_EncryptUpdate = C_EncryptUpdate;
	pFunctionList->C_EncryptFinal = C_EncryptFinal;
	pFunctionList->C_DecryptInit = C_DecryptInit;
	pFunctionList->C_Decrypt = C_Decrypt;
	pFunctionList->C_DecryptUpdate = C_DecryptUpdate;
	pFunctionList->C_DecryptFinal = C_DecryptFinal;
	pFunctionList->C_DigestInit = C_DigestInit;
	pFunctionList->C_Digest = C_Digest;
	pFunctionList->C_DigestUpdate = C_DigestUpdate;
	pFunctionList->C_DigestKey = C_DigestKey;
	pFunctionList->C_DigestFinal = C_DigestFinal;
	pFunctionList->C_SignInit = C_SignInit;
	pFunctionList->C_Sign = C_Sign;
	pFunctionList->C_SignUpdate = C_SignUpdate;
	pFunctionList->C_SignFinal = C_SignFinal;
	pFunctionList->C_SignRecoverInit = C_SignRecoverInit;
	pFunctionList->C_SignRecover = C_SignRecover;
	pFunctionList->C_VerifyInit = C_VerifyInit;
	pFunctionList->C_Verify = C_Verify;
	pFunctionList->C_VerifyUpdate = C_VerifyUpdate;
	pFunctionList->C_VerifyFinal = C_VerifyFinal;
	pFunctionList->C_VerifyRecoverInit = C_VerifyRecoverInit;
	pFunctionList->C_VerifyRecover = C_VerifyRecover;
	pFunctionList->C_DigestEncryptUpdate = C_DigestEncryptUpdate;
	pFunctionList->C_DecryptDigestUpdate = C_DecryptDigestUpdate;
	pFunctionList->C_SignEncryptUpdate = C_SignEncryptUpdate;
	pFunctionList->C_DecryptVerifyUpdate = C_DecryptVerifyUpdate;
	pFunctionList->C_GenerateKey = C_GenerateKey;
	pFunctionList->C_GenerateKeyPair = C_GenerateKeyPair;
	pFunctionList->C_WrapKey = C_WrapKey;
	pFunctionList->C_UnwrapKey = C_UnwrapKey;
	pFunctionList->C_DeriveKey = C_DeriveKey;
	pFunctionList->C_SeedRandom = C_SeedRandom;
	pFunctionList->C_GenerateRandom = C_GenerateRandom;
	pFunctionList->C_GetFunctionStatus = C_GetFunctionStatus;
	pFunctionList->C_CancelFunction = C_CancelFunction;
	pFunctionList->C_GetFunctionList = C_GetFunctionList;

	spFunctionList  = pFunctionList;
	*ppFunctionList = pFunctionList;

	CACKEY_DEBUG_PRINTF("Returning CKR_OK (%i)", CKR_OK);

	return(CKR_OK);
}
