/*
 * misc.c: Miscellaneous PKCS#11 library helper functions
 *
 * Copyright (C) 2002  Timo Teräs <timo.teras@iki.fi>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>

#include "sc-pkcs11.h"

#define DUMP_TEMPLATE_MAX	32

struct sc_to_cryptoki_error_conversion  {
	const char *context;
	int sc_error;
	CK_RV ck_error;
};

static struct sc_to_cryptoki_error_conversion sc_to_cryptoki_error_map[]  = {
	{ "C_GenerateKeyPair",	SC_ERROR_INVALID_PIN_LENGTH,	CKR_GENERAL_ERROR },
	{ "C_Sign",		SC_ERROR_NOT_ALLOWED,		CKR_FUNCTION_FAILED},
	{ "C_Decrypt",		SC_ERROR_NOT_ALLOWED,		CKR_FUNCTION_FAILED},
	{NULL, 0, 0}
};


void strcpy_bp(u8 * dst, const char *src, size_t dstsize)
{
	if (!dst || !dstsize)
		return;

	memset((char *)dst, ' ', dstsize);

	if (src) {
		size_t src_len = strlen(src);

		if (src_len > dstsize) {
			/* string will be truncated */
			memcpy((char *)dst, src, dstsize);
			if (dstsize > 3) {
				/* show truncation with '...' */
				/* FIXME avoid breaking an UTF-8 character on multiple bytes */
				memset((char *)dst + dstsize - 3, '.', 3);
			}
		} else {
			memcpy((char *)dst, src, src_len);
		}
	}
}


static CK_RV sc_to_cryptoki_error_common(int rc)
{
	sc_log(context, "libopensc return value: %d (%s)\n", rc, sc_strerror(rc));
	switch (rc) {
	case SC_SUCCESS:
		return CKR_OK;
	case SC_ERROR_NOT_SUPPORTED:
		return CKR_FUNCTION_NOT_SUPPORTED;
	case SC_ERROR_OUT_OF_MEMORY:
		return CKR_HOST_MEMORY;
	case SC_ERROR_PIN_CODE_INCORRECT:
		return CKR_PIN_INCORRECT;
	case SC_ERROR_AUTH_METHOD_BLOCKED:
		return CKR_PIN_LOCKED;
	case SC_ERROR_BUFFER_TOO_SMALL:
		return CKR_BUFFER_TOO_SMALL;
	case SC_ERROR_CARD_NOT_PRESENT:
		return CKR_TOKEN_NOT_PRESENT;
	case SC_ERROR_INVALID_CARD:
	case SC_ERROR_WRONG_CARD:
	case SC_ERROR_NO_CARD_SUPPORT:
		return CKR_TOKEN_NOT_RECOGNIZED;
	case SC_ERROR_WRONG_LENGTH:
		return CKR_DATA_LEN_RANGE;
	case SC_ERROR_INVALID_PIN_LENGTH:
		return CKR_PIN_LEN_RANGE;
	case SC_ERROR_KEYPAD_CANCELLED:
	case SC_ERROR_KEYPAD_TIMEOUT:
		return CKR_FUNCTION_CANCELED;
	case SC_ERROR_CARD_REMOVED:
		return CKR_DEVICE_REMOVED;
	case SC_ERROR_SECURITY_STATUS_NOT_SATISFIED:
		return CKR_USER_NOT_LOGGED_IN;
	case SC_ERROR_KEYPAD_PIN_MISMATCH:
		return CKR_PIN_INVALID;
	case SC_ERROR_INVALID_ARGUMENTS:
		return CKR_ARGUMENTS_BAD;
	case SC_ERROR_INVALID_DATA:
	case SC_ERROR_INCORRECT_PARAMETERS:
		return CKR_DATA_INVALID;
	case SC_ERROR_CARD_UNRESPONSIVE:
	case SC_ERROR_READER_LOCKED:
		return CKR_DEVICE_ERROR;
	case SC_ERROR_READER_DETACHED:
		return CKR_TOKEN_NOT_PRESENT;	/* Maybe CKR_DEVICE_REMOVED ? */
	case SC_ERROR_NOT_ENOUGH_MEMORY:
		return CKR_DEVICE_MEMORY;
	case SC_ERROR_MEMORY_FAILURE:	/* EEPROM has failed */
		return CKR_DEVICE_ERROR;
	case SC_ERROR_WRONG_PADDING:
		return CKR_ENCRYPTED_DATA_INVALID;
	}
	return CKR_GENERAL_ERROR;
}


CK_RV sc_to_cryptoki_error(int rc, const char *ctx)
{
	if (ctx)   {
		int ii;

		for (ii = 0; sc_to_cryptoki_error_map[ii].context; ii++) {
			if (sc_to_cryptoki_error_map[ii].sc_error != rc)
				continue;
			if (strcmp(sc_to_cryptoki_error_map[ii].context, ctx))
				continue;
			return sc_to_cryptoki_error_map[ii].ck_error;
		}
	}
	return sc_to_cryptoki_error_common(rc);
}


struct sc_pkcs11_login {
	CK_USER_TYPE userType;
	CK_CHAR_PTR pPin;
	CK_ULONG ulPinLen;
};

CK_RV restore_login_state(struct sc_pkcs11_slot *slot)
{
	CK_RV r = CKR_OK;

	if (sc_pkcs11_conf.atomic && slot) {
		if (list_iterator_start(&slot->logins)) {
			struct sc_pkcs11_login *login = list_iterator_next(&slot->logins);
			while (login && slot->p11card && slot->p11card->framework) {
				r = slot->p11card->framework->login(slot, login->userType,
						login->pPin, login->ulPinLen);
				if (r != CKR_OK)
					break;
				login = list_iterator_next(&slot->logins);
			}
			list_iterator_stop(&slot->logins);
		}
	}

	return r;
}

CK_RV reset_login_state(struct sc_pkcs11_slot *slot, CK_RV rv)
{
	if (slot) {
		if (sc_pkcs11_conf.atomic
				&& slot->p11card && slot->p11card->framework) {
			slot->p11card->framework->logout(slot);
		}

		if (rv == CKR_USER_NOT_LOGGED_IN) {
			slot->login_user = -1;
			pop_all_login_states(slot);
		}
	}

	return rv;
}

CK_RV push_login_state(struct sc_pkcs11_slot *slot,
		CK_USER_TYPE userType, CK_CHAR_PTR pPin, CK_ULONG ulPinLen)
{
	CK_RV r = CKR_HOST_MEMORY;
	struct sc_pkcs11_login *login = NULL;

	if (!sc_pkcs11_conf.atomic || !slot) {
		return CKR_OK;
	}

	login = (struct sc_pkcs11_login *) calloc(1, sizeof *login);
	if (login == NULL) {
		goto err;
	}

	if (pPin && ulPinLen) {
		login->pPin = sc_mem_secure_alloc((sizeof *pPin)*ulPinLen);
		if (login->pPin == NULL) {
			goto err;
		}
		memcpy(login->pPin, pPin, (sizeof *pPin)*ulPinLen);
		login->ulPinLen = ulPinLen;
	}
	login->userType = userType;

	if (0 > list_append(&slot->logins, login)) {
		goto err;
	}

	login = NULL;
	r = CKR_OK;

err:
	if (login) {
		if (login->pPin) {
			sc_mem_secure_clear_free(login->pPin, login->ulPinLen);
		}
		free(login);
	}

	return r;
}

void pop_login_state(struct sc_pkcs11_slot *slot)
{
	if (slot) {
		unsigned int size = list_size(&slot->logins);
		if (size > 0) {
			struct sc_pkcs11_login *login = list_get_at(&slot->logins, size-1);
			if (login) {
				sc_mem_secure_clear_free(login->pPin, login->ulPinLen);
				free(login);
			}
			if (0 > list_delete_at(&slot->logins, size-1))
				sc_log(context, "Error deleting login state");
		}
	}
}

void pop_all_login_states(struct sc_pkcs11_slot *slot)
{
	if (sc_pkcs11_conf.atomic && slot) {
		struct sc_pkcs11_login *login = list_fetch(&slot->logins);
		while (login) {
			sc_mem_secure_clear_free(login->pPin, login->ulPinLen);
			free(login);
			login = list_fetch(&slot->logins);
		}
	}
}


/* Session manipulation */
CK_RV session_start_operation(struct sc_pkcs11_session * session,
			      int type, sc_pkcs11_mechanism_type_t * mech, struct sc_pkcs11_operation ** operation)
{
	sc_pkcs11_operation_t *op;

	if (context == NULL)
		return CKR_CRYPTOKI_NOT_INITIALIZED;

	LOG_FUNC_CALLED(context);
	sc_log(context, "Session 0x%lx, type %d", session->handle, type);
	if (type < 0 || type >= SC_PKCS11_OPERATION_MAX)
		return CKR_ARGUMENTS_BAD;

	if (session->operation[type] != NULL)
		return CKR_OPERATION_ACTIVE;

	if (!(op = sc_pkcs11_new_operation(session, mech)))
		return CKR_HOST_MEMORY;

	session->operation[type] = op;
	if (operation)
		*operation = op;

	return CKR_OK;
}

CK_RV session_get_operation(struct sc_pkcs11_session * session, int type, sc_pkcs11_operation_t ** operation)
{
	sc_pkcs11_operation_t *op;

	LOG_FUNC_CALLED(context);

	if (type < 0 || type >= SC_PKCS11_OPERATION_MAX)
		return CKR_ARGUMENTS_BAD;

	if (!(op = session->operation[type]))
		return CKR_OPERATION_NOT_INITIALIZED;

	if (operation)
		*operation = op;

	return CKR_OK;
}

CK_RV session_stop_operation(struct sc_pkcs11_session * session, int type)
{
	if (type < 0 || type >= SC_PKCS11_OPERATION_MAX)
		return CKR_ARGUMENTS_BAD;

	if (session->operation[type] == NULL)
		return CKR_OPERATION_NOT_INITIALIZED;

	sc_pkcs11_release_operation(&session->operation[type]);
	return CKR_OK;
}

CK_RV attr_extract(CK_ATTRIBUTE_PTR pAttr, void *ptr, size_t * sizep)
{
	size_t size;

	if (sizep) {
		size = *sizep;
		if (size < pAttr->ulValueLen)
			return CKR_ATTRIBUTE_VALUE_INVALID;
		*sizep = pAttr->ulValueLen;
	} else {
		switch (pAttr->type) {
		case CKA_CLASS:
			size = sizeof(CK_OBJECT_CLASS);
			break;
		case CKA_KEY_TYPE:
			size = sizeof(CK_KEY_TYPE);
			break;
		case CKA_PRIVATE:
		case CKA_TOKEN:
			size = sizeof(CK_BBOOL);
			break;
		case CKA_CERTIFICATE_TYPE:
			size = sizeof(CK_CERTIFICATE_TYPE);
			break;
		case CKA_VALUE_LEN:
		case CKA_MODULUS_BITS:
			size = sizeof(CK_ULONG);
			break;
		case CKA_OBJECT_ID:
			size = sizeof(struct sc_object_id);
			break;
		default:
			return CKR_FUNCTION_FAILED;
		}
		if (size != pAttr->ulValueLen)
			return CKR_ATTRIBUTE_VALUE_INVALID;
	}
	memcpy(ptr, pAttr->pValue, pAttr->ulValueLen);
	return CKR_OK;
}

CK_RV attr_find(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, CK_ULONG type, void *ptr, size_t * sizep)
{
	unsigned int n;

	for (n = 0; n < ulCount; n++, pTemplate++) {
		if (pTemplate->type == type)
			break;
	}

	if (n >= ulCount)
		return CKR_TEMPLATE_INCOMPLETE;
	return attr_extract(pTemplate, ptr, sizep);
}

CK_RV attr_find2(CK_ATTRIBUTE_PTR pTemp1, CK_ULONG ulCount1,
		 CK_ATTRIBUTE_PTR pTemp2, CK_ULONG ulCount2, CK_ULONG type, void *ptr, size_t * sizep)
{
	CK_RV rv;

	rv = attr_find(pTemp1, ulCount1, type, ptr, sizep);
	if (rv != CKR_OK)
		rv = attr_find(pTemp2, ulCount2, type, ptr, sizep);

	return rv;
}

CK_RV attr_find_and_allocate_ptr(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, CK_ULONG type, void **out, size_t *out_len)
{
	void *ptr;
	size_t len;
	CK_RV rv;

	if (!out || !out_len)
		return CKR_ARGUMENTS_BAD;
	len = *out_len;

	rv = attr_find_ptr(pTemplate, ulCount, type, &ptr, &len);
	if (rv != CKR_OK)
		return rv;

	*out = calloc(1, len);
	if (*out == NULL)
		return CKR_HOST_MEMORY;

	memcpy(*out, ptr, len);
	*out_len = len;

	return CKR_OK;
}

CK_RV attr_find_ptr(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, CK_ULONG type, void **ptr, size_t * sizep)
{
	unsigned int n;

	for (n = 0; n < ulCount; n++, pTemplate++) {
		if (pTemplate->type == type)
			break;
	}

	if (n >= ulCount)
		return CKR_TEMPLATE_INCOMPLETE;

	if (sizep)
		*sizep = pTemplate->ulValueLen;
	*ptr = pTemplate->pValue;
	return CKR_OK;
}

CK_RV attr_find_ptr2(CK_ATTRIBUTE_PTR pTemp1, CK_ULONG ulCount1,
		 CK_ATTRIBUTE_PTR pTemp2, CK_ULONG ulCount2, CK_ULONG type, void **ptr, size_t * sizep)
{
	CK_RV rv;

	rv = attr_find_ptr(pTemp1, ulCount1, type, ptr, sizep);
	if (rv != CKR_OK)
		rv = attr_find_ptr(pTemp2, ulCount2, type, ptr, sizep);

	return rv;
}

CK_RV attr_find_var(CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, CK_ULONG type, void *ptr, size_t * sizep)
{
	unsigned int n;

	for (n = 0; n < ulCount; n++, pTemplate++) {
		if (pTemplate->type == type)
			break;
	}

	if (n >= ulCount)
		return CKR_TEMPLATE_INCOMPLETE;

	return attr_extract(pTemplate, ptr, sizep);
}

void load_pkcs11_parameters(struct sc_pkcs11_config *conf, sc_context_t * ctx)
{
	scconf_block *conf_block = NULL;
	char *unblock_style = NULL;
	char *create_slots_for_pins = NULL, *op, *tmp;

	/* Set defaults */
	conf->max_virtual_slots = 16;
	if (strcmp(ctx->app_name, "onepin-opensc-pkcs11") == 0) {
		conf->slots_per_card = 1;
	} else {
		conf->slots_per_card = 4;
	}
	conf->atomic = 0;
	conf->lock_login = 0;
	conf->init_sloppy = 1;
	conf->pin_unblock_style = SC_PKCS11_PIN_UNBLOCK_NOT_ALLOWED;
	conf->create_puk_slot = 0;
	conf->create_slots_flags = SC_PKCS11_SLOT_CREATE_ALL;

	conf_block = sc_get_conf_block(ctx, "pkcs11", NULL, 1);
	if (!conf_block)
		return;

	/* contains the defaults, if there is a "pkcs11" config block */
	conf->max_virtual_slots = scconf_get_int(conf_block, "max_virtual_slots", conf->max_virtual_slots);
	conf->slots_per_card = scconf_get_int(conf_block, "slots_per_card", conf->slots_per_card);
	conf->atomic = scconf_get_bool(conf_block, "atomic", conf->atomic);
	if (conf->atomic)
		conf->lock_login = 1;
	conf->lock_login = scconf_get_bool(conf_block, "lock_login", conf->lock_login);
	conf->init_sloppy = scconf_get_bool(conf_block, "init_sloppy", conf->init_sloppy);

	unblock_style = (char *)scconf_get_str(conf_block, "user_pin_unblock_style", NULL);
	if (unblock_style && !strcmp(unblock_style, "set_pin_in_unlogged_session"))
		conf->pin_unblock_style = SC_PKCS11_PIN_UNBLOCK_UNLOGGED_SETPIN;
	else if (unblock_style && !strcmp(unblock_style, "set_pin_in_specific_context"))
		conf->pin_unblock_style = SC_PKCS11_PIN_UNBLOCK_SCONTEXT_SETPIN;
	else if (unblock_style && !strcmp(unblock_style, "init_pin_in_so_session"))
		conf->pin_unblock_style = SC_PKCS11_PIN_UNBLOCK_SO_LOGGED_INITPIN;

	conf->create_puk_slot = scconf_get_bool(conf_block, "create_puk_slot", conf->create_puk_slot);

	create_slots_for_pins = (char *)scconf_get_str(conf_block, "create_slots_for_pins", "all");
	conf->create_slots_flags = 0;
	tmp = strdup(create_slots_for_pins);
	op = strtok(tmp, " ,");
	while (op) {
		if (!strcmp(op, "user"))
			conf->create_slots_flags |= SC_PKCS11_SLOT_FOR_PIN_USER;
		else if (!strcmp(op, "sign"))
			conf->create_slots_flags |= SC_PKCS11_SLOT_FOR_PIN_SIGN;
		else if (!strcmp(op, "all"))
			conf->create_slots_flags |= SC_PKCS11_SLOT_CREATE_ALL;
		op = strtok(NULL, " ,");
	}
	free(tmp);

	sc_log(ctx, "PKCS#11 options: max_virtual_slots=%d slots_per_card=%d "
		 "lock_login=%d atomic=%d pin_unblock_style=%d "
		 "create_slots_flags=0x%X",
		 conf->max_virtual_slots, conf->slots_per_card,
		 conf->lock_login, conf->atomic, conf->pin_unblock_style,
		 conf->create_slots_flags);
}
