/* Support of smartcards and cryptotokens
 * Copyright (C) 2003 Christoph Gysin, Simon Zwahlen
 * Copyright (C) 2004 David Buechi, Michael Meier
 * Zuercher Hochschule Winterthur, Switzerland
 *
 * Copyright (C) 2005 Michael Joosten
 *
 * Copyright (C) 2005 Andreas Steffen
 * Hochschule fuer Technik Rapperswil, Switzerland
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <dlfcn.h>

#include <freeswan.h>

#include <asn1/asn1.h>
#include <credentials/keys/public_key.h>
#include <credentials/certificates/x509.h>

#include "constants.h"

#ifdef SMARTCARD
#include "rsaref/unix.h"
#include "rsaref/pkcs11.h"
#endif

#include "defs.h"
#include "log.h"
#include "x509.h"
#include "ca.h"
#include "certs.h"
#include "keys.h"
#include "smartcard.h"
#include "whack.h"
#include "fetch.h"

#define DEFAULT_BASE    16

/* chained list of smartcard records */
static smartcard_t *smartcards   = NULL;

/* number of generated sc objects */
static int sc_number = 0;

const smartcard_t empty_sc = {
	  NULL         , /* next */
			0      , /* last_load */
	  NULL         , /* last_cert */
			0      , /* count */
			0      , /* number */
	  999999       , /* slot */
	  NULL         , /* id */
	  NULL         , /* label */
	{ NULL, 0 }    , /* pin */
	  FALSE        , /* pinpad */
	  FALSE        , /* valid */
	  FALSE        , /* session_opened */
	  FALSE        , /* logged_in */
	  TRUE         , /* any_slot */
			0L     , /* session */
};

#ifdef SMARTCARD        /* compile with smartcard support */

#define SCX_MAGIC       0xd00bed00

struct scx_pkcs11_module {
		u_int _magic;
		void *handle;
};

typedef struct scx_pkcs11_module scx_pkcs11_module_t;

/* PKCS #11 cryptoki context */
static bool scx_initialized = FALSE;
static scx_pkcs11_module_t *pkcs11_module = NULL_PTR;
static CK_FUNCTION_LIST_PTR pkcs11_functions = NULL_PTR;

/* crytoki v2.11 - return values of PKCS #11 functions*/

static const char *const pkcs11_return_name[] = {
		"CKR_OK",
		"CKR_CANCEL",
		"CKR_HOST_MEMORY",
		"CKR_SLOT_ID_INVALID",
		"CKR_FLAGS_INVALID",
		"CKR_GENERAL_ERROR",
		"CKR_FUNCTION_FAILED",
		"CKR_ARGUMENTS_BAD",
		"CKR_NO_EVENT",
		"CKR_NEED_TO_CREATE_THREADS",
		"CKR_CANT_LOCK"
	};

static const char *const pkcs11_return_name_10[] = {
		"CKR_ATTRIBUTE_READ_ONLY",
		"CKR_ATTRIBUTE_SENSITIVE",
		"CKR_ATTRIBUTE_TYPE_INVALID",
		"CKR_ATTRIBUTE_VALUE_INVALID"
	};

static const char *const pkcs11_return_name_20[] = {
		"CKR_DATA_INVALID",
		"CKR_DATA_LEN_RANGE"
	};

static const char *const pkcs11_return_name_30[] = {
		"CKR_DEVICE_ERROR",
		"CKR_DEVICE_MEMORY",
		"CKR_DEVICE_REMOVED"
	};

static const char *const pkcs11_return_name_40[] = {
		"CKR_ENCRYPTED_DATA_INVALID",
		"CKR_ENCRYPTED_DATA_LEN_RANGE"
	};

static const char *const pkcs11_return_name_50[] = {
		"CKR_FUNCTION_CANCELED",
		"CKR_FUNCTION_NOT_PARALLEL",
		"CKR_0x52_UNDEFINED",
		"CKR_0x53_UNDEFINED",
		"CKR_FUNCTION_NOT_SUPPORTED"
	};

static const char *const pkcs11_return_name_60[] = {
		"CKR_KEY_HANDLE_INVALID",
		"CKR_KEY_SENSITIVE",
		"CKR_KEY_SIZE_RANGE",
		"CKR_KEY_TYPE_INCONSISTENT",
		"CKR_KEY_NOT_NEEDED",
		"CKR_KEY_CHANGED",
		"CKR_KEY_NEEDED",
		"CKR_KEY_INDIGESTIBLE",
		"CKR_KEY_FUNCTION_NOT_PERMITTED",
		"CKR_KEY_NOT_WRAPPABLE",
		"CKR_KEY_UNEXTRACTABLE"
	 };

static const char *const pkcs11_return_name_70[] = {
		"CKR_MECHANISM_INVALID",
		"CKR_MECHANISM_PARAM_INVALID"
	 };

static const char *const pkcs11_return_name_80[] = {
		"CKR_OBJECT_HANDLE_INVALID"
	 };

static const char *const pkcs11_return_name_90[] = {
		"CKR_OPERATION_ACTIVE",
		"CKR_OPERATION_NOT_INITIALIZED"
	 };

static const char *const pkcs11_return_name_A0[] = {
		"CKR_PIN_INCORRECT",
		"CKR_PIN_INVALID",
		"CKR_PIN_LEN_RANGE",
		"CKR_PIN_EXPIRED",
		"CKR_PIN_LOCKED"
	 };

static const char *const pkcs11_return_name_B0[] = {
		"CKR_SESSION_CLOSED",
		"CKR_SESSION_COUNT",
		"CKR_0xB2_UNDEFINED",
		"CKR_SESSION_HANDLE_INVALID",
		"CKR_SESSION_PARALLEL_NOT_SUPPORTED",
		"CKR_SESSION_READ_ONLY",
		"CKR_SESSION_EXISTS",
		"CKR_SESSION_READ_ONLY_EXISTS",
		"CKR_SESSION_READ_WRITE_SO_EXISTS"
	 };

static const char *const pkcs11_return_name_C0[] = {
		"CKR_SIGNATURE_INVALID",
		"CKR_SIGNATURE_LEN_RANGE"
	 };

static const char *const pkcs11_return_name_D0[] = {
		"CKR_TEMPLATE_INCOMPLETE",
		"CKR_TEMPLATE_INCONSISTENT"
	 };

static const char *const pkcs11_return_name_E0[] = {
		"CKR_TOKEN_NOT_PRESENT",
		"CKR_TOKEN_NOT_RECOGNIZED",
		"CKR_TOKEN_WRITE_PROTECTED"
	 };

static const char *const pkcs11_return_name_F0[] = {
		"CKR_UNWRAPPING_KEY_HANDLE_INVALID",
		"CKR_UNWRAPPING_KEY_SIZE_RANGE",
		"CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT"
	 };

static const char *const pkcs11_return_name_100[] = {
		"CKR_USER_ALREADY_LOGGED_IN",
		"CKR_USER_NOT_LOGGED_IN",
		"CKR_USER_PIN_NOT_INITIALIZED",
		"CKR_USER_TYPE_INVALID",
		"CKR_USER_ANOTHER_ALREADY_LOGGED_IN",
		"CKR_USER_TOO_MANY_TYPES"
	 };

static const char *const pkcs11_return_name_110[] = {
		"CKR_WRAPPED_KEY_INVALID",
		"CKR_0x111_UNDEFINED",
		"CKR_WRAPPED_KEY_LEN_RANGE",
		"CKR_WRAPPING_KEY_HANDLE_INVALID",
		"CKR_WRAPPING_KEY_SIZE_RANGE",
		"CKR_WRAPPING_KEY_TYPE_INCONSISTENT"
	 };

static const char *const pkcs11_return_name_120[] = {
		"CKR_RANDOM_SEED_NOT_SUPPORTED",
		"CKR_RANDOM_NO_RNG"
	 };

static const char *const pkcs11_return_name_130[] = {
		"CKR_DOMAIN_PARAMS_INVALID"
	 };

static const char *const pkcs11_return_name_150[] = {
		"CKR_BUFFER_TOO_SMALL"
	 };

static const char *const pkcs11_return_name_160[] = {
		"CKR_SAVED_STATE_INVALID"
	 };

static const char *const pkcs11_return_name_170[] = {
		"CKR_INFORMATION_SENSITIVE"
	 };

static const char *const pkcs11_return_name_180[] = {
		"CKR_STATE_UNSAVEABLE"
	 };

static const char *const pkcs11_return_name_190[] = {
		"CKR_CRYPTOKI_NOT_INITIALIZED",
		"CKR_CRYPTOKI_ALREADY_INITIALIZED"
	 };

static const char *const pkcs11_return_name_1A0[] = {
		"CKR_MUTEX_BAD",
		"CKR_MUTEX_NOT_LOCKED"
	 };

static const char *const pkcs11_return_name_200[] = {
		"CKR_FUNCTION_REJECTED"
	 };

static const char *const pkcs11_return_name_vendor[] = {
		"CKR_VENDOR_DEFINED"
	 };

static enum_names pkcs11_return_names_vendor =
	{ CKR_VENDOR_DEFINED, CKR_VENDOR_DEFINED
		, pkcs11_return_name_vendor, NULL };

static enum_names pkcs11_return_names_200 =
	{ CKR_FUNCTION_REJECTED, CKR_FUNCTION_REJECTED
		, pkcs11_return_name_200, &pkcs11_return_names_vendor };

static enum_names pkcs11_return_names_1A0 =
	{ CKR_MUTEX_BAD, CKR_MUTEX_NOT_LOCKED
		, pkcs11_return_name_1A0, &pkcs11_return_names_200 };

static enum_names pkcs11_return_names_190 =
	{ CKR_CRYPTOKI_NOT_INITIALIZED, CKR_CRYPTOKI_ALREADY_INITIALIZED
		, pkcs11_return_name_190, &pkcs11_return_names_1A0 };

static enum_names pkcs11_return_names_180 =
	{ CKR_STATE_UNSAVEABLE, CKR_STATE_UNSAVEABLE
		, pkcs11_return_name_180, &pkcs11_return_names_190 };

static enum_names pkcs11_return_names_170 =
	{ CKR_INFORMATION_SENSITIVE, CKR_INFORMATION_SENSITIVE
		, pkcs11_return_name_170, &pkcs11_return_names_180 };

static enum_names pkcs11_return_names_160 =
	{ CKR_SAVED_STATE_INVALID, CKR_SAVED_STATE_INVALID
		, pkcs11_return_name_160, &pkcs11_return_names_170 };

static enum_names pkcs11_return_names_150 =
	{ CKR_BUFFER_TOO_SMALL, CKR_BUFFER_TOO_SMALL
		, pkcs11_return_name_150, &pkcs11_return_names_160 };

static enum_names pkcs11_return_names_130 =
	{ CKR_DOMAIN_PARAMS_INVALID, CKR_DOMAIN_PARAMS_INVALID
		, pkcs11_return_name_130, &pkcs11_return_names_150 };

static enum_names pkcs11_return_names_120 =
	{ CKR_RANDOM_SEED_NOT_SUPPORTED, CKR_RANDOM_NO_RNG
		, pkcs11_return_name_120, &pkcs11_return_names_130 };

static enum_names pkcs11_return_names_110 =
	{ CKR_WRAPPED_KEY_INVALID, CKR_WRAPPING_KEY_TYPE_INCONSISTENT
		, pkcs11_return_name_110, &pkcs11_return_names_120 };

static enum_names pkcs11_return_names_100 =
	{ CKR_USER_ALREADY_LOGGED_IN, CKR_USER_TOO_MANY_TYPES
		, pkcs11_return_name_100, &pkcs11_return_names_110 };

static enum_names pkcs11_return_names_F0 =
	{ CKR_UNWRAPPING_KEY_HANDLE_INVALID, CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT
		, pkcs11_return_name_F0, &pkcs11_return_names_100 };

static enum_names pkcs11_return_names_E0 =
	{ CKR_TOKEN_NOT_PRESENT, CKR_TOKEN_WRITE_PROTECTED
		, pkcs11_return_name_E0, &pkcs11_return_names_F0 };

static enum_names pkcs11_return_names_D0 =
	{ CKR_TEMPLATE_INCOMPLETE, CKR_TEMPLATE_INCONSISTENT
		, pkcs11_return_name_D0,&pkcs11_return_names_E0 };

static enum_names pkcs11_return_names_C0 =
	{ CKR_SIGNATURE_INVALID, CKR_SIGNATURE_LEN_RANGE
		, pkcs11_return_name_C0, &pkcs11_return_names_D0 };

static enum_names pkcs11_return_names_B0 =
	{ CKR_SESSION_CLOSED, CKR_SESSION_READ_WRITE_SO_EXISTS
		, pkcs11_return_name_B0, &pkcs11_return_names_C0 };

static enum_names pkcs11_return_names_A0 =
	{ CKR_PIN_INCORRECT, CKR_PIN_LOCKED
		, pkcs11_return_name_A0, &pkcs11_return_names_B0 };

static enum_names pkcs11_return_names_90 =
	{ CKR_OPERATION_ACTIVE, CKR_OPERATION_NOT_INITIALIZED
		, pkcs11_return_name_90, &pkcs11_return_names_A0 };

static enum_names pkcs11_return_names_80 =
	{ CKR_OBJECT_HANDLE_INVALID, CKR_OBJECT_HANDLE_INVALID
		, pkcs11_return_name_80, &pkcs11_return_names_90 };

static enum_names pkcs11_return_names_70 =
	{ CKR_MECHANISM_INVALID, CKR_MECHANISM_PARAM_INVALID
		, pkcs11_return_name_70, &pkcs11_return_names_80 };

static enum_names pkcs11_return_names_60 =
	{ CKR_KEY_HANDLE_INVALID, CKR_KEY_UNEXTRACTABLE
		, pkcs11_return_name_60, &pkcs11_return_names_70 };

static enum_names pkcs11_return_names_50 =
	{ CKR_FUNCTION_CANCELED, CKR_FUNCTION_NOT_SUPPORTED
		, pkcs11_return_name_50, &pkcs11_return_names_60 };

static enum_names pkcs11_return_names_40 =
	{ CKR_ENCRYPTED_DATA_INVALID, CKR_ENCRYPTED_DATA_LEN_RANGE
		, pkcs11_return_name_40, &pkcs11_return_names_50 };

static enum_names pkcs11_return_names_30 =
	{ CKR_DEVICE_ERROR, CKR_DEVICE_REMOVED
		, pkcs11_return_name_30, &pkcs11_return_names_40 };

static enum_names pkcs11_return_names_20 =
	{ CKR_DATA_INVALID, CKR_DATA_LEN_RANGE
		, pkcs11_return_name_20, &pkcs11_return_names_30 };

static enum_names pkcs11_return_names_10 =
	{ CKR_ATTRIBUTE_READ_ONLY, CKR_ATTRIBUTE_VALUE_INVALID
		, pkcs11_return_name_10, &pkcs11_return_names_20};

static enum_names pkcs11_return_names =
	{ CKR_OK, CKR_CANT_LOCK
		, pkcs11_return_name, &pkcs11_return_names_10};

/*
 * Unload a PKCS#11 module.
 * The calling application is responsible for cleaning up
 * and calling C_Finalize()
 */
static CK_RV scx_unload_pkcs11_module(scx_pkcs11_module_t *mod)
{
	if (!mod || mod->_magic != SCX_MAGIC)
		return CKR_ARGUMENTS_BAD;

	if (dlclose(mod->handle) < 0)
		return CKR_FUNCTION_FAILED;

	memset(mod, 0, sizeof(*mod));
	free(mod);
	return CKR_OK;
}

static scx_pkcs11_module_t* scx_load_pkcs11_module(const char *name,
								 CK_FUNCTION_LIST_PTR_PTR funcs)
{
	CK_RV (*c_get_function_list)(CK_FUNCTION_LIST_PTR_PTR);
	scx_pkcs11_module_t *mod;
	void *handle;
	int rv;

	if (name == NULL || *name == '\0')
		return NULL;

	/* Try to load PKCS#11 library module*/
	handle = dlopen(name, RTLD_NOW);
	if (handle == NULL)
		return NULL;

	mod = malloc_thing(scx_pkcs11_module_t);
	mod->_magic = SCX_MAGIC;
	mod->handle = handle;

   /* Get the list of function pointers */
	c_get_function_list = (CK_RV (*)(CK_FUNCTION_LIST_PTR_PTR))
			dlsym(mod->handle, "C_GetFunctionList");
	if (!c_get_function_list)
		goto failed;

	rv = c_get_function_list(funcs);
	if (rv == CKR_OK)
		return mod;

failed: scx_unload_pkcs11_module(mod);
		return NULL;
}

/*
 * retrieve a certificate object
 */
static cert_t* scx_find_cert_object(CK_SESSION_HANDLE session,
									CK_OBJECT_HANDLE object, smartcard_t *sc)
{
	size_t hex_len, label_len;
	u_char *hex_id = NULL;
	cert_t *cert;
	chunk_t blob;

	CK_ATTRIBUTE attr[] = {
		{ CKA_ID,    NULL_PTR, 0L },
		{ CKA_LABEL, NULL_PTR, 0L },
		{ CKA_VALUE, NULL_PTR, 0L }
	};

	/* get the length of the attributes first */
	CK_RV rv = pkcs11_functions->C_GetAttributeValue(session, object, attr, 3);
	if (rv != CKR_OK)
	{
		plog("couldn't read the attribute sizes: %s"
			, enum_show(&pkcs11_return_names, rv));
		return NULL;
	}

	free(sc->label);

	hex_id    = malloc(attr[0].ulValueLen);
	hex_len   = attr[0].ulValueLen;
	sc->label = malloc(attr[1].ulValueLen + 1);
	label_len = attr[1].ulValueLen;
	blob.ptr  = malloc(attr[2].ulValueLen);
	blob.len  = attr[2].ulValueLen;

	attr[0].pValue = hex_id;
	attr[1].pValue = sc->label;
	attr[2].pValue = blob.ptr;

	/* now get the attributes */
	rv = pkcs11_functions->C_GetAttributeValue(session, object, attr, 3);
	if (rv != CKR_OK)
	{
		plog("couldn't read the attributes: %s"
			, enum_show(&pkcs11_return_names, rv));
		free(hex_id);
		free(sc->label);
		free(blob.ptr);
		return NULL;
	}

	free(sc->id);

	/* convert id from hex to ASCII */
	sc->id = malloc(2*hex_len + 1);
	datatot(hex_id, hex_len, 16, sc->id, 2*hex_len + 1);
	free(hex_id);

	/* safeguard in case the label is not null terminated */
	sc->label[label_len] = '\0';

	/* parse the retrieved cert */

	/* initialize the return argument */
	cert = malloc_thing(cert_t);
	*cert = cert_empty;
	cert->smartcard = TRUE;
	cert->cert = lib->creds->create(lib->creds,
							  		CRED_CERTIFICATE, CERT_X509,
							  		BUILD_BLOB_ASN1_DER, blob,
							  		BUILD_END);
	if (cert->cert)
	{
		return cert;
	}

	plog("failed to load cert from smartcard, error in X.509 certificate");
	cert_free(cert);
	return NULL;
}


/*
 * search a given slot for PKCS#11 certificate objects
 */
static void scx_find_cert_objects(CK_SLOT_ID slot, CK_SESSION_HANDLE session)
{
	CK_RV rv;
	CK_OBJECT_CLASS class = CKO_CERTIFICATE;
	CK_ATTRIBUTE attr[] = {{ CKA_CLASS, &class, sizeof(class) }};

	rv = pkcs11_functions->C_FindObjectsInit(session, attr, 1);
	if (rv != CKR_OK)
	{
		plog("error in C_FindObjectsInit: %s"
			, enum_show(&pkcs11_return_names, rv));
		return;
	}

	for (;;)
	{
		CK_OBJECT_HANDLE object;
		CK_ULONG obj_count = 0;
		time_t valid_until;
		smartcard_t *sc;
		certificate_t *certificate;
		x509_t *x509;

		rv = pkcs11_functions->C_FindObjects(session, &object, 1, &obj_count);
		if (rv != CKR_OK)
		{
			plog("error in C_FindObjects: %s"
				, enum_show(&pkcs11_return_names, rv));
			break;
		}

		/* no objects left */
		if (obj_count == 0)
			break;

		/* create and initialize a new smartcard object */
		sc  = malloc_thing(smartcard_t);
		*sc = empty_sc;
		sc->any_slot = FALSE;
		sc->slot  = slot;
		sc->last_cert = scx_find_cert_object(session, object, sc);
		if (sc->last_cert == NULL)
		{
			scx_free(sc);
			continue;
		}
		DBG(DBG_CONTROL,
			DBG_log("found cert in %s with id: %s, label: '%s'"
				, scx_print_slot(sc, ""), sc->id, sc->label)
		)

		/* check validity of certificate */
		certificate = sc->last_cert->cert;
		if (!certificate->get_validity(certificate, NULL, NULL, &valid_until))
		{
			scx_free(sc);
			continue;
		}
		DBG(DBG_CONTROL,
			DBG_log("  certificate is valid")
		)

		sc = scx_add(sc);
		x509 = (x509_t*)certificate;

		/* put end entity and ca certificates into different chains */
		if (x509->get_flags(x509) & X509_CA)
		{
			sc->last_cert = add_authcert(sc->last_cert, X509_CA);
		}
		else
		{
			add_public_key_from_cert(sc->last_cert, valid_until, DAL_LOCAL);
			sc->last_cert = cert_add(sc->last_cert);
		}

		cert_share(sc->last_cert);
		time(&sc->last_load);
	}

	rv = pkcs11_functions->C_FindObjectsFinal(session);
	if (rv != CKR_OK)
	{
		plog("error in C_FindObjectsFinal: %s"
				, enum_show(&pkcs11_return_names, rv));
	}
}

/*
 * search all slots for PKCS#11 certificate objects
 */
static void scx_find_all_cert_objects(void)
{
	CK_RV rv;
	CK_SLOT_ID_PTR slots = NULL_PTR;
	CK_ULONG slot_count = 0;
	CK_ULONG i;

	if (!scx_initialized)
	{
		plog("pkcs11 module not initialized");
		return;
	}

	/* read size, always returns CKR_OK ! */
	rv = pkcs11_functions->C_GetSlotList(FALSE, NULL_PTR, &slot_count);

	/* allocate memory for the slots */
	slots = (CK_SLOT_ID *)malloc(slot_count * sizeof(CK_SLOT_ID));

	rv = pkcs11_functions->C_GetSlotList(FALSE, slots, &slot_count);
	if (rv != CKR_OK)
	{
		plog("error in C_GetSlotList: %s", enum_show(&pkcs11_return_names, rv));
		free(slots);
		return;
	}

	/* look in every slot for certificate objects */
	for (i = 0; i < slot_count; i++)
	{
		CK_SLOT_ID slot = slots[i];
		CK_SLOT_INFO info;
		CK_SESSION_HANDLE session;

		rv = pkcs11_functions->C_GetSlotInfo(slot, &info);

		if (rv != CKR_OK)
		{
			plog("error in C_GetSlotInfo: %s"
				, enum_show(&pkcs11_return_names, rv));
			continue;
		}

		if (!(info.flags & CKF_TOKEN_PRESENT))
		{
			plog("no token present in slot %lu", slot);
			continue;
		}

		rv = pkcs11_functions->C_OpenSession(slot
				, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &session);
		if (rv != CKR_OK)
		{
			plog("failed to open a session on slot %lu: %s"
				, slot, enum_show(&pkcs11_return_names, rv));
			continue;
		}
		DBG(DBG_CONTROLMORE,
			DBG_log("pkcs11 session #%ld for searching slot %lu", session, slot)
		)
		scx_find_cert_objects(slot, session);

		rv = pkcs11_functions->C_CloseSession(session);
		if (rv != CKR_OK)
		{
			plog("error in C_CloseSession: %s"
				, enum_show(&pkcs11_return_names, rv));
		}
	}
	free(slots);
}
#endif

/*
 * load and initialize PKCS#11 cryptoki module
 *
 * init_args should be unused when we have a PKCS#11 compliant module,
 * but NSS softoken breaks that API.
 */
void scx_init(const char* module, const char *init_args)
{
#ifdef SMARTCARD
	CK_C_INITIALIZE_ARGS args = { .pReserved = (char *)init_args, };
	CK_RV rv;

	if (scx_initialized)
	{
		plog("weird - pkcs11 module seems already to be initialized");
		return;
	}

	if (module == NULL)
#ifdef PKCS11_DEFAULT_LIB
		module = PKCS11_DEFAULT_LIB;
#else
	{
		plog("no pkcs11 module defined");
		return;
	}
#endif

	DBG(DBG_CONTROL | DBG_CRYPT,
		DBG_log("pkcs11 module '%s' loading...", module)
	)
	pkcs11_module = scx_load_pkcs11_module(module, &pkcs11_functions);
	if (pkcs11_module == NULL)
	{
		 plog("failed to load pkcs11 module '%s'", module);
		 return;
	}

	DBG(DBG_CONTROL | DBG_CRYPT,
		DBG_log("pkcs11 module initializing...")
	)
	rv = pkcs11_functions->C_Initialize(init_args ? &args : NULL);
	if (rv != CKR_OK)
	{
		plog("failed to initialize pkcs11 module: %s"
			, enum_show(&pkcs11_return_names, rv));
		return;
	}

	scx_initialized = TRUE;
	DBG(DBG_CONTROL | DBG_CRYPT,
		DBG_log("pkcs11 module loaded and initialized")
	)

	scx_find_all_cert_objects();
#endif
}

/*
 * finalize and unload PKCS#11 cryptoki module
 */
void scx_finalize(void)
{
#ifdef SMARTCARD
	while (smartcards != NULL)
	{
		scx_release(smartcards);
	}

	if (pkcs11_functions != NULL_PTR)
	{
		pkcs11_functions->C_Finalize(NULL_PTR);
		pkcs11_functions = NULL_PTR;
	}

	if (pkcs11_module != NULL)
	{
		scx_unload_pkcs11_module(pkcs11_module);
		pkcs11_module = NULL;
	}

	scx_initialized = FALSE;
	DBG(DBG_CONTROL | DBG_CRYPT,
		DBG_log("pkcs11 module finalized and unloaded")
	)
#endif
}

/*
 * does a filename contain the token %smartcard?
 */
bool scx_on_smartcard(const char *filename)
{
	return strneq(filename, SCX_TOKEN, strlen(SCX_TOKEN));
}

#ifdef SMARTCARD
/*
 * find a specific object on the smartcard
 */
static bool scx_pkcs11_find_object(CK_SESSION_HANDLE session,
								   CK_OBJECT_HANDLE_PTR object,
								   CK_OBJECT_CLASS class, const char* id)
{
	size_t len;
	char buf[BUF_LEN];
	CK_RV rv;
	CK_ULONG obj_count = 0;
	CK_ULONG attr_count = 1;

	CK_ATTRIBUTE attr[] = {
		{ CKA_CLASS, &class, sizeof(class) },
		{ CKA_ID, &buf, 0L }
	};

	if (id != NULL)
	{
		ttodata(id, strlen(id), 16, buf, BUF_LEN, &len);
		attr[1].ulValueLen = len;
		attr_count = 2;
	}

	/* get info for certificate with id */
	rv = pkcs11_functions->C_FindObjectsInit(session, attr, attr_count);
	if (rv != CKR_OK)
	{
		plog("error in C_FindObjectsInit: %s"
				, enum_show(&pkcs11_return_names, rv));
		return FALSE;
	}

	rv = pkcs11_functions->C_FindObjects(session, object, 1,  &obj_count);
	if (rv != CKR_OK)
	{
		plog("error in C_FindObjects: %s"
				, enum_show(&pkcs11_return_names, rv));
		return FALSE;
	}

	rv = pkcs11_functions->C_FindObjectsFinal(session);
	if (rv != CKR_OK)
	{
		plog("error in C_FindObjectsFinal: %s"
				, enum_show(&pkcs11_return_names, rv));
		return FALSE;
	}

	return (obj_count != 0);
}

/*
 * check if a given certificate object id is found in a slot
 */
static bool scx_find_cert_id_in_slot(smartcard_t *sc, CK_SLOT_ID slot)
{
	CK_SESSION_HANDLE session;
	CK_OBJECT_HANDLE object;
	CK_SLOT_INFO info;

	CK_RV rv = pkcs11_functions->C_GetSlotInfo(slot, &info);

	if (rv != CKR_OK)
	{
		plog("error in C_GetSlotInfo: %s"
			, enum_show(&pkcs11_return_names, rv));
		 return FALSE;
	}

	if (!(info.flags & CKF_TOKEN_PRESENT))
	{
		plog("no token present in slot %lu", slot);
		return FALSE;
	}

	rv = pkcs11_functions->C_OpenSession(slot
				, CKF_SERIAL_SESSION, NULL_PTR, NULL_PTR, &session);
	if (rv != CKR_OK)
	{
		plog("failed to open a session on slot %lu: %s"
			, slot, enum_show(&pkcs11_return_names, rv));
		return FALSE;
	}
	DBG(DBG_CONTROLMORE,
		DBG_log("pkcs11 session #%ld for searching slot %lu", session, slot)
	)

	/* check if there is a certificate on the card in the specified slot */
	if (scx_pkcs11_find_object(session, &object, CKO_CERTIFICATE, sc->id))
	{
		sc->slot = slot;
		sc->any_slot = FALSE;
		sc->session = session;
		sc->session_opened = TRUE;
		return TRUE;
	}

	rv = pkcs11_functions->C_CloseSession(session);
	if (rv != CKR_OK)
	{
		plog("error in C_CloseSession: %s"
				, enum_show(&pkcs11_return_names, rv));
	}
	return FALSE;
}
#endif

/*
 * Connect to the smart card in the reader and select the correct slot
 */
bool scx_establish_context(smartcard_t *sc)
{
#ifdef SMARTCARD
	bool id_found = FALSE;

	if (!scx_initialized)
	{
		plog("pkcs11 module not initialized");
		return FALSE;
	}

	if (sc->session_opened)
	{
		DBG(DBG_CONTROL | DBG_CRYPT,
			DBG_log("pkcs11 session #%ld already open", sc->session)
		)
		return TRUE;
	}

	if (!sc->any_slot)
		id_found = scx_find_cert_id_in_slot(sc, sc->slot);

	if (!id_found)
	{
		CK_RV rv;
		CK_SLOT_ID slot;
		CK_SLOT_ID_PTR slots = NULL_PTR;
		CK_ULONG slot_count = 0;
		CK_ULONG i;

		/* read size, always returns CKR_OK ! */
		rv = pkcs11_functions->C_GetSlotList(FALSE, NULL_PTR, &slot_count);

		/* allocate memory for the slots */
		slots = (CK_SLOT_ID *)malloc(slot_count * sizeof(CK_SLOT_ID));

		rv = pkcs11_functions->C_GetSlotList(FALSE, slots, &slot_count);
		if (rv != CKR_OK)
		{
			plog("error in C_GetSlotList: %s"
				, enum_show(&pkcs11_return_names, rv));
			free(slots);
			return FALSE;
		}

		/* look in every slot for a certificate with a given object ID */
		for (i = 0; i < slot_count; i++)
		{
			slot = slots[i];
			id_found = scx_find_cert_id_in_slot(sc, slot);
			if (id_found)
				break;
		}
		free(slots);
	}

	if (id_found)
	{
		DBG(DBG_CONTROL | DBG_CRYPT,
			DBG_log("found token with id %s in slot %lu", sc->id, sc->slot);
			DBG_log("pkcs11 session #%ld opened", sc->session)
		)
	}
	else
	{
		plog("  no certificate with id %s found on smartcard", sc->id);
	}
	return id_found;
#else
	plog("warning: SMARTCARD support is deactivated in pluto/Makefile!");
	return FALSE;
#endif
}

/*
 * log in to a session
 */
bool scx_login(smartcard_t *sc)
{
#ifdef SMARTCARD
	CK_RV rv;

	if (sc->logged_in)
	{
		DBG(DBG_CONTROL | DBG_CRYPT,
			DBG_log("pkcs11 session #%ld login already done", sc->session)
		)
		return TRUE;
	}

	if (sc->pin.ptr == NULL)
	{
		plog("unable to log in without PIN!");
		return FALSE;
	}

	if (!sc->session_opened)
	{
		plog("session not opened");
		return FALSE;
	}

	rv = pkcs11_functions->C_Login(sc->session, CKU_USER
								, (CK_UTF8CHAR *) sc->pin.ptr, sc->pin.len);
	if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN)
	{
		plog("unable to login: %s"
			, enum_show(&pkcs11_return_names, rv));
		return FALSE;
	}
	DBG(DBG_CONTROL | DBG_CRYPT,
		DBG_log("pkcs11 session #%ld login successful", sc->session)
	)
	sc->logged_in = TRUE;
	return TRUE;
#else
   return FALSE;
#endif
}

#ifdef SMARTCARD
/*
 * logout from a session
 */
static void scx_logout(smartcard_t *sc)
{
	CK_RV rv;

	rv = pkcs11_functions->C_Logout(sc->session);
	if (rv != CKR_OK)
		plog("error in C_Logout: %s"
			, enum_show(&pkcs11_return_names, rv));
	else
		DBG(DBG_CONTROL | DBG_CRYPT,
			DBG_log("pkcs11 session #%ld logout", sc->session)
		)
	sc->logged_in = FALSE;
}
#endif


/*
 * Release context and disconnect from card
 */
void scx_release_context(smartcard_t *sc)
{
#ifdef SMARTCARD
	CK_RV rv;

	if (!scx_initialized)
		return;

	if (sc->session_opened)
	{
		if (sc->logged_in)
			scx_logout(sc);

		sc->session_opened = FALSE;

		rv = pkcs11_functions->C_CloseSession(sc->session);
		if (rv != CKR_OK)
			plog("error in C_CloseSession: %s"
				, enum_show(&pkcs11_return_names, rv));
		else
			DBG(DBG_CONTROL | DBG_CRYPT,
				DBG_log("pkcs11 session #%ld closed", sc->session)
			)
	}
#endif
}

/*
 * Load host certificate from smartcard
 */
cert_t* scx_load_cert(const char *filename, smartcard_t **scp, bool *cached)
{
#ifdef SMARTCARD        /* compile with smartcard support */
	const char *number_slot_id = filename + strlen(SCX_TOKEN);
	CK_OBJECT_HANDLE object;
	smartcard_t *sc;
	cert_t *cert = NULL;

	/* return the smartcard object */
	*scp = sc = scx_add(scx_parse_number_slot_id(number_slot_id));

	/* is there a cached smartcard certificate? */
	*cached = sc->last_cert && 
			  (time(NULL) - sc->last_load) < SCX_CERT_CACHE_INTERVAL;

	if (*cached)
	{
		plog("  using cached cert from smartcard #%d (%s, id: %s, label: '%s')"
				, sc->number
				, scx_print_slot(sc, "")
				, sc->id
				, sc->label);
		return sc->last_cert;
	}

	if (!scx_establish_context(sc))
	{
		scx_release_context(sc);
		return NULL;
	}

	/* find the certificate object */
	if (!scx_pkcs11_find_object(sc->session, &object, CKO_CERTIFICATE, sc->id))
	{
		scx_release_context(sc);
		return NULL;
	}

	/* retrieve the certificate object */
	cert = scx_find_cert_object(sc->session, object, sc);
	if (cert == NULL)
	{
		scx_release_context(sc);
		return NULL;
	}

	if (!pkcs11_keep_state)
	{
		scx_release_context(sc);
	}
	plog("  loaded cert from smartcard #%d (%s, id: %s, label: '%s')"
		, sc->number
		, scx_print_slot(sc, "")
		, sc->id
		, sc->label);

	return cert;
#else
	plog("  warning: SMARTCARD support is deactivated in pluto/Makefile!");
	return NULL;
#endif
}

/*
 * parse slot number and key id
 * the following syntax is allowed
 *               number   slot   id
 * %smartcard      1       -     -
 * %smartcard#2    2       -     -
 * %smartcard0     -       0     -
 * %smartcard:45   -       -     45
 * %smartcard0:45  -       0     45
 */
smartcard_t* scx_parse_number_slot_id(const char *number_slot_id)
{
	int len = strlen(number_slot_id);
	smartcard_t *sc = malloc_thing(smartcard_t);

	/* assign default values */
	*sc = empty_sc;

	if (len == 0)                       /* default: use certificate #1 */
	{
		sc->number = 1;
	}
	else if (*number_slot_id == '#')    /* #number scheme */
	{
		err_t ugh;
		unsigned long ul;

		ugh = atoul(number_slot_id+1, len-1 , 10, &ul);
		if (ugh == NULL)
			sc->number = (int)ul;
		else
			plog("error parsing smartcard number: %s", ugh);
	}
	else                                /* slot:id scheme */
	{
		int slot_len = len;
		char *p = strchr(number_slot_id, ':');

		if (p != NULL)
		{
			int id_len = len - (p + 1 - number_slot_id);
			slot_len -= (1 + id_len);

			if (id_len > 0)             /* we have an id */
				sc->id = p + 1;
		}
		if (slot_len > 0)               /* we have a slot */
		{
			err_t ugh = NULL;
			unsigned long ul;

			ugh = atoul(number_slot_id, slot_len, 10, &ul);
			if (ugh == NULL)
			{
				sc->slot = ul;
				sc->any_slot = FALSE;
			}
			else
				plog("error parsing smartcard slot number: %s", ugh);
		}
	}
	/* unshare the id string */
	sc->id = clone_str(sc->id);
	return sc;
}

/*
 * Verify pin on card
 */
bool scx_verify_pin(smartcard_t *sc)
{
#ifdef SMARTCARD
	CK_RV rv;

	if (!sc->pinpad)
		sc->valid = FALSE;

	if (sc->pin.ptr == NULL)
	{
		plog("unable to verify without PIN");
		return FALSE;
	}

	/* establish context */
	if (!scx_establish_context(sc))
	{
		scx_release_context(sc);
		return FALSE;
	}

	rv = pkcs11_functions->C_Login(sc->session, CKU_USER,
							  (CK_UTF8CHAR *) sc->pin.ptr, sc->pin.len);
	if (rv == CKR_OK || rv == CKR_USER_ALREADY_LOGGED_IN)
	{
		sc->valid = TRUE;
		sc->logged_in = TRUE;
		DBG(DBG_CONTROL | DBG_CRYPT,
			DBG_log((rv == CKR_OK)
				? "PIN code correct"
				: "already logged in, no PIN entry required");
			DBG_log("pkcs11 session #%ld login successful", sc->session)
		)
	}
	else
	{
		DBG(DBG_CONTROL | DBG_CRYPT,
			DBG_log("PIN code incorrect")
		)
	}
	if (!pkcs11_keep_state)
		scx_release_context(sc);
#else
	sc->valid = FALSE;
#endif
	return sc->valid;
}

/*
 * Sign hash on smartcard
 */
bool scx_sign_hash(smartcard_t *sc, const u_char *in, size_t inlen, u_char *out,
				   size_t outlen)
{
#ifdef SMARTCARD
	CK_RV rv;
	CK_OBJECT_HANDLE object;
	CK_ULONG siglen = (CK_ULONG)outlen;
	CK_BBOOL sign_flag, decrypt_flag;
	CK_ATTRIBUTE attr[] = {
		{ CKA_SIGN,    &sign_flag,    sizeof(sign_flag) },
		{ CKA_DECRYPT, &decrypt_flag, sizeof(decrypt_flag) }
	};

	if (!sc->logged_in)
		return FALSE;

	if (!scx_pkcs11_find_object(sc->session, &object, CKO_PRIVATE_KEY, sc->id))
	{
		plog("unable to find private key with id '%s'", sc->id);
		return FALSE;
	}

	rv = pkcs11_functions->C_GetAttributeValue(sc->session, object, attr, 2);
	if (rv != CKR_OK)
	{
		plog("couldn't read the private key attributes: %s"
			, enum_show(&pkcs11_return_names, rv));
		return FALSE;
	}
	DBG(DBG_CONTROL,
		DBG_log("RSA key flags: sign = %s, decrypt = %s"
			, (sign_flag)?    "true":"false"
			, (decrypt_flag)? "true":"false")
	)

	if (sign_flag)
	{
		CK_MECHANISM mech  = { CKM_RSA_PKCS, NULL_PTR, 0 };

		rv = pkcs11_functions->C_SignInit(sc->session, &mech, object);
		if (rv != CKR_OK)
		{
			plog("error in C_SignInit: %s"
				, enum_show(&pkcs11_return_names, rv));
			return FALSE;
		}

		rv = pkcs11_functions->C_Sign(sc->session, (CK_BYTE_PTR)in, inlen
				, out, &siglen);
		if (rv != CKR_OK)
		{
			plog("error in C_Sign: %s"
				, enum_show(&pkcs11_return_names, rv));
			return FALSE;
		}
	}
	else if (decrypt_flag)
	{
		CK_MECHANISM mech = { CKM_RSA_X_509, NULL_PTR, 0 };
		size_t padlen;
		u_char *p = out ;

		/* PKCS#1 v1.5 8.1 encryption-block formatting */
		*p++ = 0x00;
		*p++ = 0x01;    /* BT (block type) 01 */
		padlen = outlen - 3 - inlen;
		memset(p, 0xFF, padlen);
		p += padlen;
		*p++ = 0x00;
		memcpy(p, in, inlen);

		rv = pkcs11_functions->C_DecryptInit(sc->session, &mech, object);
		if (rv != CKR_OK)
		{
			plog("error in C_DecryptInit: %s"
				, enum_show(&pkcs11_return_names, rv));
			return FALSE;
		}

		rv = pkcs11_functions->C_Decrypt(sc->session, out, outlen
				, out, &siglen);
		if (rv != CKR_OK)
		{
			plog("error in C_Decrypt: %s"
				, enum_show(&pkcs11_return_names, rv));
			return FALSE;
		}
	}
	else
	{
		plog("private key has neither sign nor decrypt flag set");
		return FALSE;
	}

	if (siglen > (CK_ULONG)outlen)
	{
		plog("signature length (%lu) larger than allocated buffer (%d)"
			, siglen, (int)outlen);
		return FALSE;
	}
	return TRUE;
#else
	return FALSE;
#endif
}

/*
 * encrypt data block with an RSA public key
 */
bool scx_encrypt(smartcard_t *sc, const u_char *in, size_t inlen, u_char *out,
				 size_t *outlen)
{
#ifdef SMARTCARD
	CK_RV rv;
	CK_OBJECT_HANDLE object;
	CK_ULONG len = (CK_ULONG)(*outlen);
	CK_BBOOL encrypt_flag;
	CK_ATTRIBUTE attr[] = {
		{ CKA_MODULUS,         NULL_PTR, 0L },
		{ CKA_PUBLIC_EXPONENT, NULL_PTR, 0L },
		{ CKA_ENCRYPT,         &encrypt_flag, sizeof(encrypt_flag) }
	};
	CK_MECHANISM mech = { CKM_RSA_PKCS, NULL_PTR, 0 };

	if (!scx_establish_context(sc))
	{
		scx_release_context(sc);
			return FALSE;
	}

	if (!scx_pkcs11_find_object(sc->session, &object, CKO_PUBLIC_KEY, sc->id))
	{
		plog("unable to find public key with id '%s'", sc->id);
		return FALSE;
	}

	rv = pkcs11_functions->C_GetAttributeValue(sc->session, object, attr, 3);
	if (rv != CKR_OK)
	{
		plog("couldn't read the public key attributes: %s"
			, enum_show(&pkcs11_return_names, rv));
		scx_release_context(sc);
		return FALSE;
	}

	if (!encrypt_flag)
	{
		plog("public key cannot be used for encryption");
		scx_release_context(sc);
		return FALSE;
	}

	/* there must be enough space left for the PKCS#1 v1.5 padding */
	if (inlen > attr[0].ulValueLen - 11)
	{
		plog("smartcard input data length (%d) exceeds maximum of %lu bytes"
				, (int)inlen, attr[0].ulValueLen - 11);
		if (!pkcs11_keep_state)
			scx_release_context(sc);
		return FALSE;
	}

	rv = pkcs11_functions->C_EncryptInit(sc->session, &mech, object);

	if (rv != CKR_OK)
	{
		if (rv == CKR_FUNCTION_NOT_SUPPORTED)
		{
			public_key_t *key;
			chunk_t rsa_modulus, rsa_exponent, rsa_key, cipher_text;
			chunk_t plain_text = {(u_char*)in, inlen};

			DBG(DBG_CONTROL,
				DBG_log("doing RSA encryption in software")
			)
			attr[0].pValue = malloc(attr[0].ulValueLen);
			attr[1].pValue = malloc(attr[1].ulValueLen);

			rv = pkcs11_functions->C_GetAttributeValue(sc->session, object, attr, 2);
			if (rv != CKR_OK)
			{
				plog("couldn't read modulus and public exponent: %s"
					, enum_show(&pkcs11_return_names, rv));
				free(attr[0].pValue);
				free(attr[1].pValue);
				scx_release_context(sc);
				return FALSE;
			}
			rsa_modulus  = chunk_create((u_char*) attr[0].pValue,
									    (size_t)  attr[0].ulValueLen);
			rsa_exponent = chunk_create((u_char*) attr[1].pValue,
									    (size_t)  attr[1].ulValueLen);
			rsa_key = asn1_wrap(ASN1_SEQUENCE, "mm",
								asn1_integer("m", rsa_modulus),
								asn1_integer("m", rsa_exponent));
			key = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_RSA,
								BUILD_BLOB_ASN1_DER, rsa_key, BUILD_END);
			free(rsa_key.ptr);
			if (key == NULL)
			{
				return FALSE;
			}
			key->encrypt(key, plain_text, &cipher_text);
			key->destroy(key);

			if (cipher_text.ptr == NULL)
			{
				plog("smartcard input data length is too large");
				if (!pkcs11_keep_state)
				{
					scx_release_context(sc);
				}
				return FALSE;
			}

			memcpy(out, cipher_text.ptr, cipher_text.len);
			*outlen = cipher_text.len;
			free(cipher_text.ptr);

			if (!pkcs11_keep_state)
			{
				scx_release_context(sc);
			}
			return TRUE;
		}
		else
		{
			plog("error in C_EncryptInit: %s"
				, enum_show(&pkcs11_return_names, rv));
			scx_release_context(sc);
			return FALSE;
		}
	}

	DBG(DBG_CONTROL,
		DBG_log("doing RSA encryption on smartcard")
	)
	rv = pkcs11_functions->C_Encrypt(sc->session, (u_char*)in, inlen
				, out, &len);
	if (rv != CKR_OK)
	{
		plog("error in C_Encrypt: %s"
			, enum_show(&pkcs11_return_names, rv));
		scx_release_context(sc);
		return FALSE;
	}
	if (!pkcs11_keep_state)
		scx_release_context(sc);

	*outlen = (size_t)len;
	return TRUE;
#else
	return FALSE;
#endif
}
/*
 * decrypt a data block with an RSA private key
 */
bool scx_decrypt(smartcard_t *sc, const u_char *in, size_t inlen, u_char *out,
				 size_t *outlen)
{
#ifdef SMARTCARD
	CK_RV rv;
	CK_OBJECT_HANDLE object;
	CK_ULONG len = (CK_ULONG)(*outlen);
	CK_BBOOL decrypt_flag;
	CK_ATTRIBUTE attr[] = {
		{ CKA_DECRYPT, &decrypt_flag, sizeof(decrypt_flag) }
	};
	CK_MECHANISM mech = { CKM_RSA_PKCS, NULL_PTR, 0 };

	if (!scx_establish_context(sc) || !scx_login(sc))
	{
		scx_release_context(sc);
			return FALSE;
	}

	if (!scx_pkcs11_find_object(sc->session, &object, CKO_PRIVATE_KEY, sc->id))
	{
		plog("unable to find private key with id '%s'", sc->id);
		return FALSE;
	}

	rv = pkcs11_functions->C_GetAttributeValue(sc->session, object, attr, 1);
	if (rv != CKR_OK)
	{
		plog("couldn't read the private key attributes: %s"
			, enum_show(&pkcs11_return_names, rv));
		return FALSE;
	}

	if (!decrypt_flag)
	{
		plog("private key cannot be used for decryption");
		scx_release_context(sc);
		return FALSE;
	}

	DBG(DBG_CONTROL,
		DBG_log("doing RSA decryption on smartcard")
	)
	rv = pkcs11_functions->C_DecryptInit(sc->session, &mech, object);
	if (rv != CKR_OK)
	{
		plog("error in C_DecryptInit: %s"
			, enum_show(&pkcs11_return_names, rv));
		scx_release_context(sc);
		return FALSE;
	}

	rv = pkcs11_functions->C_Decrypt(sc->session, (u_char*)in, inlen
				, out, &len);
	if (rv != CKR_OK)
	{
		plog("error in C_Decrypt: %s"
			, enum_show(&pkcs11_return_names, rv));
		scx_release_context(sc);
		return FALSE;
	}
	if (!pkcs11_keep_state)
		scx_release_context(sc);

	*outlen = (size_t)len;
	return TRUE;
#else
	return FALSE;
#endif
}

/* receive an encrypted data block via whack,
 * decrypt it using a private RSA key and
 * return the decrypted data block via whack
 */
bool scx_op_via_whack(const char* msg, int inbase, int outbase, sc_op_t op,
					  const char* keyid, int whackfd)
{
	char inbuf[RSA_MAX_OCTETS];
	char outbuf[2*RSA_MAX_OCTETS + 1];
	size_t outlen = sizeof(inbuf);
	size_t inlen;
	smartcard_t *sc,*sc_new;

	const char *number_slot_id = "";

	err_t ugh = ttodata(msg, 0, inbase, inbuf, sizeof(inbuf), &inlen);

	/* no prefix - use default base */
	if (ugh != NULL  && inbase == 0)
	   ugh = ttodata(msg, 0, DEFAULT_BASE, inbuf, sizeof(inbuf), &inlen);

	if (ugh != NULL)
	{
		plog("format error in smartcard input data: %s", ugh);
		return FALSE;
	}

	if (keyid != NULL)
	{
		number_slot_id = (strneq(keyid, SCX_TOKEN, strlen(SCX_TOKEN)))
						 ? keyid + strlen(SCX_TOKEN) : keyid;
	}

	sc_new = scx_parse_number_slot_id(number_slot_id);
	sc = scx_add(sc_new);
	if (sc == sc_new)
		scx_share(sc);

	DBG((op == SC_OP_ENCRYPT)? DBG_PRIVATE:DBG_RAW,
		DBG_dump("smartcard input data:\n", inbuf, inlen)
	)

	if (op == SC_OP_DECRYPT)
	{
		if (!sc->valid && whackfd != NULL_FD)
			scx_get_pin(sc, whackfd);

		if (!sc->valid)
		{
			loglog(RC_NOVALIDPIN, "cannot decrypt without valid PIN");
			return FALSE;
		}
	}

	DBG(DBG_CONTROL | DBG_CRYPT,
		DBG_log("using RSA key from smartcard (slot: %d, id: %s)"
			, (int)sc->slot, sc->id)
	)

	switch (op)
	{
	case SC_OP_ENCRYPT:
		if (!scx_encrypt(sc, inbuf, inlen, inbuf, &outlen))
			return FALSE;
		break;
	case SC_OP_DECRYPT:
		if (!scx_decrypt(sc, inbuf, inlen, inbuf, &outlen))
			return FALSE;
		break;
	default:
		break;
	}

	DBG((op == SC_OP_DECRYPT)? DBG_PRIVATE:DBG_RAW,
		DBG_dump("smartcard output data:\n", inbuf, outlen)
	)

	if (outbase == 0)  /* use default base */
		outbase = DEFAULT_BASE;

	if (outbase == 256) /* ascii plain text */
		whack_log(RC_COMMENT, "%.*s", (int)outlen, inbuf);
	else
	{
		outlen = datatot(inbuf, outlen, outbase, outbuf, sizeof(outbuf));
		if (outlen == 0)
		{
			plog("error in output format conversion");
			return FALSE;
		}
		whack_log(RC_COMMENT, "%s", outbuf);
	}
	return TRUE;
}

 /*
 * get length of RSA key in bytes
 */
size_t scx_get_keylength(smartcard_t *sc)
{
#ifdef SMARTCARD
	CK_RV rv;
	CK_OBJECT_HANDLE object;
	CK_ATTRIBUTE attr[] = {{ CKA_MODULUS, NULL_PTR, 0}};

	if (!sc->logged_in)
		return FALSE;

	if (!scx_pkcs11_find_object(sc->session, &object, CKO_PRIVATE_KEY, sc->id))
	{
		plog("unable to find private key with id '%s'", sc->id);
		return FALSE;
	}

	/* get the length of the private key */
	rv = pkcs11_functions->C_GetAttributeValue(sc->session, object
				, (CK_ATTRIBUTE_PTR)&attr, 1);
	if (rv != CKR_OK)
	{
		plog("failed to get key length: %s"
			, enum_show(&pkcs11_return_names, rv));
		return FALSE;
	}

	return attr[0].ulValueLen;  /*Return key length in bytes */
#else
	return 0;
#endif
}

/*
 * prompt for pin and verify it
 */
bool scx_get_pin(smartcard_t *sc, int whackfd)
{
#ifdef SMARTCARD
	char pin[BUF_LEN];
	int i, n;

	whack_log(RC_ENTERSECRET, "need PIN for #%d (%s, id: %s, label: '%s')"
		, sc->number, scx_print_slot(sc, ""), sc->id, sc->label);

	for (i = 0; i < SCX_MAX_PIN_TRIALS; i++)
	{
		if (i > 0)
			whack_log(RC_ENTERSECRET, "invalid PIN, please try again");

		n = read(whackfd, pin, BUF_LEN);

		if (n == -1)
		{
			whack_log(RC_LOG_SERIOUS, "read(whackfd) failed");
			return FALSE;
		}

		if (strlen(pin) == 0)
		{
			whack_log(RC_LOG_SERIOUS, "no PIN entered, aborted");
			return FALSE;
		}

		sc->pin.ptr = pin;
		sc->pin.len = strlen(pin);

		/* verify the pin */
		if (scx_verify_pin(sc))
		{
			sc->pin = chunk_create(pin, strlen(pin));
			sc->pin = chunk_clone(sc->pin);
			break;
		}

		/* wrong pin - we try another round */
		sc->pin = chunk_empty;
	}

	if (sc->valid)
		whack_log(RC_SUCCESS, "valid PIN");
	else
		whack_log(RC_LOG_SERIOUS, "invalid PIN, too many trials");
#else
	sc->valid = FALSE;
	whack_log(RC_LOG_SERIOUS, "SMARTCARD support is deactivated in pluto/Makefile!");
#endif
	return sc->valid;
}


/*
 * free the pin code
 */
void scx_free_pin(chunk_t *pin)
{
	if (pin->ptr != NULL)
	{
		/* clear pin field in memory */
		memset(pin->ptr, '\0', pin->len);
		free(pin->ptr);
		*pin = chunk_empty;
	}
}

/*
 * frees a smartcard record
 */
void scx_free(smartcard_t *sc)
{
	if (sc != NULL)
	{
		scx_release_context(sc);
		cert_release(sc->last_cert);
		free(sc->id);
		free(sc->label);
		scx_free_pin(&sc->pin);
		free(sc);
	}
}

/*  release of a smartcard record decreases the count by one
 "  the record is freed when the counter reaches zero
 */
void scx_release(smartcard_t *sc)
{
	if (sc != NULL && --sc->count == 0)
	{
		smartcard_t **pp = &smartcards;
		while (*pp != sc)
			pp = &(*pp)->next;
		*pp = sc->next;
		scx_free(sc);
	}
}

/*
 *  compare two smartcard records by comparing their slots and ids
 */
static bool scx_same(smartcard_t *a, smartcard_t *b)
{
	if  (a->number && b->number)
	{
		/* same number */
		return a->number == b->number;
	}
	else
	{
		/* same id and/or same slot */
		return (!a->id || (b->id && streq(a->id, b->id)))
			&& (a->any_slot || b->any_slot || a->slot == b->slot);
	}
}

/*  for each link pointing to the smartcard record
 "  increase the count by one
 */
void scx_share(smartcard_t *sc)
{
	if (sc != NULL)
		sc->count++;
}

/*
 *  adds a smartcard record to the chained list
 */
smartcard_t* scx_add(smartcard_t *smartcard)
{
	smartcard_t *sc = smartcards;
	smartcard_t **psc = &smartcards;

	while (sc != NULL)
	{
		if (scx_same(smartcard, sc)) /* already in chain, free smartcard record */
		{
			scx_free(smartcard);
			return sc;
		}
		psc = &sc->next;
		sc = sc->next;
	}

	/* insert new smartcard record at the end of the chain */
	*psc = smartcard;
	smartcard->number = ++sc_number;
	smartcard->count = 1;
	DBG(DBG_CONTROL | DBG_PARSING,
		DBG_log("  smartcard #%d added", sc_number)
	)
	return smartcard;
}

/*
 * get the smartcard that belongs to an X.509 certificate
 */
smartcard_t* scx_get(cert_t *cert)
{
	smartcard_t *sc = smartcards;

	while (sc != NULL)
	{
		if (sc->last_cert == cert)
		{
			return sc;
		}
		sc = sc->next;
	}
	return NULL;
}

/*
 * prints either the slot number or 'any slot'
 */
char *scx_print_slot(smartcard_t *sc, const char *whitespace)
{
	char *buf = temporary_cyclic_buffer();

	if (sc->any_slot)
		snprintf(buf, BUF_LEN, "any slot");
	else
		snprintf(buf, BUF_LEN, "slot: %s%lu", whitespace, sc->slot);
	return buf;
}

/*
 *  list all smartcard info records in a chained list
 */
void scx_list(bool utc)
{
	smartcard_t *sc = smartcards;

	if (sc != NULL)
	{
		whack_log(RC_COMMENT, " ");
		whack_log(RC_COMMENT, "List of Smartcard Objects:");
	}

	while (sc != NULL)
	{
		whack_log(RC_COMMENT, " ");
		whack_log(RC_COMMENT, "  %s, session %s, logged %s, has %s"
			, scx_print_slot(sc, "    ")
			, sc->session_opened? "opened" : "closed"
			, sc->logged_in? "in" : "out"
			, sc->pinpad? "pin pad"
				: ((sc->pin.ptr == NULL)? "no pin"
					: sc->valid? "valid pin" : "invalid pin"));
		if (sc->id != NULL)
			whack_log(RC_COMMENT, "  id:       %s", sc->id);
		if (sc->label != NULL)
			whack_log(RC_COMMENT, "  label:   '%s'", sc->label);
		if (sc->last_cert)
		{
			certificate_t *certificate = sc->last_cert->cert;

			whack_log(RC_COMMENT, "  subject: '%Y'",
					  certificate->get_subject(certificate));
		}
		sc = sc->next;
	}
}