/*
 * Copyright (C) 2012-2015 Tobias Brunner
 * Copyright (C) 2016-2019 Andreas Steffen
 * HSR Hochschule fuer Technik Rapperswil
 *
 * Copyright (C) 2011 Martin Willi
 * Copyright (C) 2011 revosec AG
 *
 * 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.
 */

#define _GNU_SOURCE
#include <stdio.h>

#include "plugin_feature.h"

#include <utils/debug.h>

ENUM(plugin_feature_names, FEATURE_NONE, FEATURE_CUSTOM,
	"NONE",
	"CRYPTER",
	"AEAD",
	"SIGNER",
	"HASHER",
	"PRF",
	"XOF",
	"DRBG",
	"DH",
	"RNG",
	"NONCE_GEN",
	"PRIVKEY",
	"PRIVKEY_GEN",
	"PRIVKEY_SIGN",
	"PRIVKEY_DECRYPT",
	"PUBKEY",
	"PUBKEY_VERIFY",
	"PUBKEY_ENCRYPT",
	"CERT_DECODE",
	"CERT_ENCODE",
	"CONTAINER_DECODE",
	"CONTAINER_ENCODE",
	"EAP_SERVER",
	"EAP_CLIENT",
	"XAUTH_SERVER",
	"XAUTH_CLIENT",
	"DATABASE",
	"FETCHER",
	"RESOLVER",
	"CUSTOM",
);

/**
 * See header.
 */
uint32_t plugin_feature_hash(plugin_feature_t *feature)
{
	chunk_t data = chunk_empty;

	switch (feature->type)
	{
		case FEATURE_NONE:
		case FEATURE_RNG:
		case FEATURE_NONCE_GEN:
		case FEATURE_DATABASE:
		case FEATURE_FETCHER:
		case FEATURE_RESOLVER:
			/* put these special cases in their (type-specific) buckets */
			data = chunk_empty;
			break;
		case FEATURE_CRYPTER:
			data = chunk_from_thing(feature->arg.crypter);
			break;
		case FEATURE_AEAD:
			data = chunk_from_thing(feature->arg.aead);
			break;
		case FEATURE_SIGNER:
			data = chunk_from_thing(feature->arg.signer);
			break;
		case FEATURE_HASHER:
			data = chunk_from_thing(feature->arg.hasher);
			break;
		case FEATURE_PRF:
			data = chunk_from_thing(feature->arg.prf);
			break;
		case FEATURE_XOF:
			data = chunk_from_thing(feature->arg.xof);
			break;
		case FEATURE_DRBG:
			data = chunk_from_thing(feature->arg.drbg);
			break;
		case FEATURE_DH:
			data = chunk_from_thing(feature->arg.dh_group);
			break;
		case FEATURE_PRIVKEY:
			data = chunk_from_thing(feature->arg.privkey);
			break;
		case FEATURE_PRIVKEY_GEN:
			data = chunk_from_thing(feature->arg.privkey_gen);
			break;
		case FEATURE_PUBKEY:
			data = chunk_from_thing(feature->arg.pubkey);
			break;
		case FEATURE_PRIVKEY_SIGN:
			data = chunk_from_thing(feature->arg.privkey_sign);
			break;
		case FEATURE_PUBKEY_VERIFY:
			data = chunk_from_thing(feature->arg.pubkey_verify);
			break;
		case FEATURE_PRIVKEY_DECRYPT:
			data = chunk_from_thing(feature->arg.privkey_decrypt);
			break;
		case FEATURE_PUBKEY_ENCRYPT:
			data = chunk_from_thing(feature->arg.pubkey_encrypt);
			break;
		case FEATURE_CERT_DECODE:
		case FEATURE_CERT_ENCODE:
			data = chunk_from_thing(feature->arg.cert);
			break;
		case FEATURE_CONTAINER_DECODE:
		case FEATURE_CONTAINER_ENCODE:
			data = chunk_from_thing(feature->arg.container);
			break;
		case FEATURE_EAP_SERVER:
		case FEATURE_EAP_PEER:
			data = chunk_from_thing(feature->arg.eap);
			break;
		case FEATURE_CUSTOM:
			data = chunk_create(feature->arg.custom,
								strlen(feature->arg.custom));
			break;
		case FEATURE_XAUTH_SERVER:
		case FEATURE_XAUTH_PEER:
			data = chunk_create(feature->arg.xauth,
								strlen(feature->arg.xauth));
			break;
	}
	return chunk_hash_inc(chunk_from_thing(feature->type),
						  chunk_hash(data));
}

/**
 * See header.
 */
bool plugin_feature_matches(plugin_feature_t *a, plugin_feature_t *b)
{
	if (a->type == b->type)
	{
		switch (a->type)
		{
			case FEATURE_NONE:
				return FALSE;
			case FEATURE_CRYPTER:
				return a->arg.crypter.alg == b->arg.crypter.alg &&
					   a->arg.crypter.key_size == b->arg.crypter.key_size;
			case FEATURE_AEAD:
				return a->arg.aead.alg == b->arg.aead.alg &&
					   a->arg.aead.key_size == b->arg.aead.key_size;
			case FEATURE_SIGNER:
				return a->arg.signer == b->arg.signer;
			case FEATURE_HASHER:
				return a->arg.hasher == b->arg.hasher;
			case FEATURE_PRF:
				return a->arg.prf == b->arg.prf;
			case FEATURE_XOF:
				return a->arg.xof == b->arg.xof;
			case FEATURE_DRBG:
				return a->arg.drbg == b->arg.drbg;
			case FEATURE_DH:
				return a->arg.dh_group == b->arg.dh_group;
			case FEATURE_RNG:
				return a->arg.rng_quality <= b->arg.rng_quality;
			case FEATURE_NONCE_GEN:
			case FEATURE_RESOLVER:
				return TRUE;
			case FEATURE_PRIVKEY:
			case FEATURE_PRIVKEY_GEN:
			case FEATURE_PUBKEY:
				return a->arg.privkey == b->arg.privkey;
			case FEATURE_PRIVKEY_SIGN:
			case FEATURE_PUBKEY_VERIFY:
				return a->arg.privkey_sign == b->arg.privkey_sign;
			case FEATURE_PRIVKEY_DECRYPT:
			case FEATURE_PUBKEY_ENCRYPT:
				return a->arg.privkey_decrypt == b->arg.privkey_decrypt;
			case FEATURE_CERT_DECODE:
			case FEATURE_CERT_ENCODE:
				return a->arg.cert == b->arg.cert;
			case FEATURE_CONTAINER_DECODE:
			case FEATURE_CONTAINER_ENCODE:
				return a->arg.container == b->arg.container;
			case FEATURE_EAP_SERVER:
			case FEATURE_EAP_PEER:
				return a->arg.eap.vendor == b->arg.eap.vendor &&
					   a->arg.eap.type == b->arg.eap.type;
			case FEATURE_DATABASE:
				return a->arg.database == DB_ANY ||
					   a->arg.database == b->arg.database;
			case FEATURE_FETCHER:
				return a->arg.fetcher == NULL ||
					   streq(a->arg.fetcher, b->arg.fetcher);
			case FEATURE_CUSTOM:
				return streq(a->arg.custom, b->arg.custom);
			case FEATURE_XAUTH_SERVER:
			case FEATURE_XAUTH_PEER:
				return streq(a->arg.xauth, b->arg.xauth);
		}
	}
	return FALSE;
}

/**
 * See header.
 */
bool plugin_feature_equals(plugin_feature_t *a, plugin_feature_t *b)
{
	if (a->type == b->type)
	{
		switch (a->type)
		{
			case FEATURE_NONE:
			case FEATURE_CRYPTER:
			case FEATURE_AEAD:
			case FEATURE_SIGNER:
			case FEATURE_HASHER:
			case FEATURE_PRF:
			case FEATURE_XOF:
			case FEATURE_DRBG:
			case FEATURE_DH:
			case FEATURE_NONCE_GEN:
			case FEATURE_RESOLVER:
			case FEATURE_PRIVKEY:
			case FEATURE_PRIVKEY_GEN:
			case FEATURE_PUBKEY:
			case FEATURE_PRIVKEY_SIGN:
			case FEATURE_PUBKEY_VERIFY:
			case FEATURE_PRIVKEY_DECRYPT:
			case FEATURE_PUBKEY_ENCRYPT:
			case FEATURE_CERT_DECODE:
			case FEATURE_CERT_ENCODE:
			case FEATURE_CONTAINER_DECODE:
			case FEATURE_CONTAINER_ENCODE:
			case FEATURE_EAP_SERVER:
			case FEATURE_EAP_PEER:
			case FEATURE_CUSTOM:
			case FEATURE_XAUTH_SERVER:
			case FEATURE_XAUTH_PEER:
				return plugin_feature_matches(a, b);
			case FEATURE_RNG:
				return a->arg.rng_quality == b->arg.rng_quality;
			case FEATURE_DATABASE:
				return a->arg.database == b->arg.database;
			case FEATURE_FETCHER:
				if (a->arg.fetcher && b->arg.fetcher)
				{
					return streq(a->arg.fetcher, b->arg.fetcher);
				}
				return !a->arg.fetcher && !b->arg.fetcher;
		}
	}
	return FALSE;
}

/**
 * See header.
 */
char* plugin_feature_get_string(plugin_feature_t *feature)
{
	char *str = NULL;

	if (feature->kind == FEATURE_REGISTER)
	{
		return strdup("(register function)");
	}
	switch (feature->type)
	{
		case FEATURE_NONE:
			return strdup("NONE");
		case FEATURE_CRYPTER:
			if (asprintf(&str, "%N:%N-%d", plugin_feature_names, feature->type,
					encryption_algorithm_names, feature->arg.crypter.alg,
					feature->arg.crypter.key_size) > 0)
			{
				return str;
			}
			break;
		case FEATURE_AEAD:
			if (asprintf(&str, "%N:%N-%d", plugin_feature_names, feature->type,
					encryption_algorithm_names, feature->arg.aead.alg,
					feature->arg.aead.key_size) > 0)
			{
				return str;
			}
			break;
		case FEATURE_SIGNER:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					integrity_algorithm_names, feature->arg.signer) > 0)
			{
				return str;
			}
			break;
		case FEATURE_HASHER:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					hash_algorithm_names, feature->arg.hasher) > 0)
			{
				return str;
			}
			break;
		case FEATURE_PRF:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					pseudo_random_function_names, feature->arg.prf) > 0)
			{
				return str;
			}
			break;
		case FEATURE_XOF:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					ext_out_function_names, feature->arg.xof) > 0)
			{
				return str;
			}
			break;
		case FEATURE_DRBG:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					drbg_type_names, feature->arg.drbg) > 0)
			{
				return str;
			}
			break;
		case FEATURE_DH:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					diffie_hellman_group_names, feature->arg.dh_group) > 0)
			{
				return str;
			}
			break;
		case FEATURE_RNG:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					rng_quality_names, feature->arg.rng_quality) > 0)
			{
				return str;
			}
			break;
		case FEATURE_NONCE_GEN:
		case FEATURE_RESOLVER:
			if (asprintf(&str, "%N", plugin_feature_names, feature->type) > 0)
			{
				return str;
			}
			break;
		case FEATURE_PRIVKEY:
		case FEATURE_PRIVKEY_GEN:
		case FEATURE_PUBKEY:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					key_type_names, feature->arg.privkey) > 0)
			{
				return str;
			}
			break;
		case FEATURE_PRIVKEY_SIGN:
		case FEATURE_PUBKEY_VERIFY:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					signature_scheme_names, feature->arg.privkey_sign) > 0)
			{
				return str;
			}
			break;
		case FEATURE_PRIVKEY_DECRYPT:
		case FEATURE_PUBKEY_ENCRYPT:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					encryption_scheme_names, feature->arg.privkey_decrypt) > 0)
			{
				return str;
			}
			break;
		case FEATURE_CERT_DECODE:
		case FEATURE_CERT_ENCODE:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					certificate_type_names, feature->arg.cert) > 0)
			{
				return str;
			}
			break;
		case FEATURE_CONTAINER_DECODE:
		case FEATURE_CONTAINER_ENCODE:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					container_type_names, feature->arg.container) > 0)
			{
				return str;
			}
			break;
		case FEATURE_EAP_SERVER:
		case FEATURE_EAP_PEER:
			if (feature->arg.eap.vendor &&
				asprintf(&str, "%N:%d-%d", plugin_feature_names, feature->type,
					feature->arg.eap.type, feature->arg.eap.vendor) > 0)
			{
				return str;
			}
			else if (!feature->arg.eap.vendor &&
				asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					eap_type_short_names, feature->arg.eap.type) > 0)
			{
				return str;
			}
			break;
		case FEATURE_DATABASE:
			if (asprintf(&str, "%N:%N", plugin_feature_names, feature->type,
					db_driver_names, feature->arg.database) > 0)
			{
				return str;
			}
			break;
		case FEATURE_FETCHER:
			if (asprintf(&str, "%N:%s", plugin_feature_names, feature->type,
					feature->arg.fetcher) > 0)
			{
				return str;
			}
			break;
		case FEATURE_CUSTOM:
			if (asprintf(&str, "%N:%s", plugin_feature_names, feature->type,
					feature->arg.custom) > 0)
			{
				return str;
			}
			break;
		case FEATURE_XAUTH_SERVER:
		case FEATURE_XAUTH_PEER:
			if (asprintf(&str, "%N:%s", plugin_feature_names, feature->type,
					feature->arg.xauth) > 0)
			{
				return str;
			}
			break;
	}
	if (!str)
	{
		str = strdup("(invalid)");
	}
	return str;
}

/**
 * See header.
 */
bool plugin_feature_load(plugin_t *plugin, plugin_feature_t *feature,
						 plugin_feature_t *reg)
{
	char *name;

	if (!reg)
	{	/* noting to do for this feature */
		return TRUE;
	}
	if (reg->kind == FEATURE_CALLBACK)
	{
		if (!reg->arg.cb.f ||
			 reg->arg.cb.f(plugin, feature, TRUE, reg->arg.cb.data))
		{
			return TRUE;
		}
		return FALSE;
	}
	name = plugin->get_name(plugin);
	switch (feature->type)
	{
		case FEATURE_CRYPTER:
			lib->crypto->add_crypter(lib->crypto, feature->arg.crypter.alg,
								feature->arg.crypter.key_size,
								name, reg->arg.reg.f);
			break;
		case FEATURE_AEAD:
			lib->crypto->add_aead(lib->crypto, feature->arg.aead.alg,
								feature->arg.aead.key_size,
								name, reg->arg.reg.f);
			break;
		case FEATURE_SIGNER:
			lib->crypto->add_signer(lib->crypto, feature->arg.signer,
								name, reg->arg.reg.f);
			break;
		case FEATURE_HASHER:
			lib->crypto->add_hasher(lib->crypto, feature->arg.hasher,
								name, reg->arg.reg.f);
			break;
		case FEATURE_PRF:
			lib->crypto->add_prf(lib->crypto, feature->arg.prf,
								name, reg->arg.reg.f);
			break;
		case FEATURE_XOF:
			lib->crypto->add_xof(lib->crypto, feature->arg.xof,
								name, reg->arg.reg.f);
			break;
		case FEATURE_DRBG:
			lib->crypto->add_drbg(lib->crypto, feature->arg.drbg,
								name, reg->arg.reg.f);
			break;
		case FEATURE_DH:
			lib->crypto->add_dh(lib->crypto, feature->arg.dh_group,
								name, reg->arg.reg.f);
			break;
		case FEATURE_RNG:
			lib->crypto->add_rng(lib->crypto, feature->arg.rng_quality,
								name, reg->arg.reg.f);
			break;
		case FEATURE_NONCE_GEN:
			lib->crypto->add_nonce_gen(lib->crypto,
								name, reg->arg.reg.f);
			break;
		case FEATURE_PRIVKEY:
		case FEATURE_PRIVKEY_GEN:
			lib->creds->add_builder(lib->creds, CRED_PRIVATE_KEY,
								feature->arg.privkey, reg->arg.reg.final,
								name, reg->arg.reg.f);
			break;
		case FEATURE_PUBKEY:
			lib->creds->add_builder(lib->creds, CRED_PUBLIC_KEY,
								feature->arg.pubkey, reg->arg.reg.final,
								name, reg->arg.reg.f);
			break;
		case FEATURE_CERT_DECODE:
		case FEATURE_CERT_ENCODE:
			lib->creds->add_builder(lib->creds, CRED_CERTIFICATE,
								feature->arg.cert, reg->arg.reg.final,
								name, reg->arg.reg.f);
			break;
		case FEATURE_CONTAINER_DECODE:
		case FEATURE_CONTAINER_ENCODE:
			lib->creds->add_builder(lib->creds, CRED_CONTAINER,
								feature->arg.container, reg->arg.reg.final,
								name, reg->arg.reg.f);
			break;
		case FEATURE_DATABASE:
			lib->db->add_database(lib->db, reg->arg.reg.f);
			break;
		case FEATURE_FETCHER:
			lib->fetcher->add_fetcher(lib->fetcher, reg->arg.reg.f,
									  feature->arg.fetcher);
			break;
		case FEATURE_RESOLVER:
			lib->resolver->add_resolver(lib->resolver, reg->arg.reg.f);
			break;
		default:
			break;
	}
	return TRUE;
}

/**
 * See header.
 */
bool plugin_feature_unload(plugin_t *plugin, plugin_feature_t *feature,
						   plugin_feature_t *reg)
{
	if (!reg)
	{	/* noting to do for this feature */
		return TRUE;
	}
	if (reg->kind == FEATURE_CALLBACK)
	{
		if (!reg->arg.cb.f ||
			 reg->arg.cb.f(plugin, feature, FALSE, reg->arg.cb.data))
		{
			return TRUE;
		}
		return FALSE;
	}
	switch (feature->type)
	{
		case FEATURE_CRYPTER:
			lib->crypto->remove_crypter(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_AEAD:
			lib->crypto->remove_aead(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_SIGNER:
			lib->crypto->remove_signer(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_HASHER:
			lib->crypto->remove_hasher(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_PRF:
			lib->crypto->remove_prf(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_XOF:
			lib->crypto->remove_xof(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_DRBG:
			lib->crypto->remove_drbg(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_DH:
			lib->crypto->remove_dh(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_RNG:
			lib->crypto->remove_rng(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_NONCE_GEN:
			lib->crypto->remove_nonce_gen(lib->crypto, reg->arg.reg.f);
			break;
		case FEATURE_PRIVKEY:
		case FEATURE_PRIVKEY_GEN:
			lib->creds->remove_builder(lib->creds, reg->arg.reg.f);
			break;
		case FEATURE_PUBKEY:
			lib->creds->remove_builder(lib->creds, reg->arg.reg.f);
			break;
		case FEATURE_CERT_DECODE:
		case FEATURE_CERT_ENCODE:
			lib->creds->remove_builder(lib->creds, reg->arg.reg.f);
			break;
		case FEATURE_CONTAINER_DECODE:
		case FEATURE_CONTAINER_ENCODE:
			lib->creds->remove_builder(lib->creds, reg->arg.reg.f);
			break;
		case FEATURE_DATABASE:
			lib->db->remove_database(lib->db, reg->arg.reg.f);
			break;
		case FEATURE_FETCHER:
			lib->fetcher->remove_fetcher(lib->fetcher, reg->arg.reg.f);
			break;
		case FEATURE_RESOLVER:
			lib->resolver->remove_resolver(lib->resolver, reg->arg.reg.f);
			break;
		default:
			break;
	}
	return TRUE;
}
