
#include "ssl_trusted_cert_storage.h"
#include "ssl_fetch_cert.h"
#include "ssl_stream.h"
#include "MESA_htable_aux.h"
#include <MESA/MESA_htable.h>

#include <ssl_utils.h>
#include <tfe_utils.h>

#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int SSL_EX_DATA_IDX_VERIFY_PARAM=-1;

struct ssl_X509_object
{
	char* filename;	
	enum ssl_X509_obj_type type;
};

enum cert_store_status
{
    UPDATING,
	STABLE,
};

static void free_ssl_x509_obj(void* data)
{
	struct ssl_X509_object* obj=(struct ssl_X509_object*)data;
	free(obj->filename);
	free(obj);
	return;
}
struct ssl_trusted_cert_storage
{
	enum cert_store_status status;
	struct cert_store_param param;
	char* pem_bundle, *pem_dir;
    MESA_htable_handle hash_table;
    MESA_htable_handle temp_table;
    pthread_rwlock_t rwlock;
	X509_STORE *effective_store;
    X509_STORE *temp_store;
};
 static int _X509_add_cert_or_crl_add(X509_STORE* store,  enum ssl_X509_obj_type type, const char* filename)
 {
	 int ret=0;
	 BIO *bio=NULL;
	 X509* x=NULL;
	 X509_CRL* x_crl=NULL;
	 
	 bio=BIO_new_file(filename, "r");
	 if(bio==NULL)
	 {
		 return -1;
	 }
	 ret=0;
	 if(type==SSL_X509_OBJ_CERT)
	 {
		 while(NULL!=(x=PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL)))
		 {
			 ret=X509_STORE_add_cert(store, x);
             X509_free(x);
             if(ret==0)
			 {
				break;
			 }
		 }
	 }
	 else if(type==SSL_X509_OBJ_CRL)
	 {
		 while(NULL!=(x_crl=PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)))
		 {
			 ret=X509_STORE_add_crl(store, x_crl);
             X509_CRL_free(x_crl);
             if(ret==0)
			 {
				break;
			 }		 
		 }
	 }
	 if(ret==0)
	 {
		 BIO_free(bio);
		 return -1;
	 }
	 BIO_free(bio);
	 return 1;
 }

 UNUSED static int filter_pem_fn(const struct dirent * ent)
 {
 	const char* fn_suffix=".pem";
 	if(strlen(ent->d_name)< strlen(fn_suffix))
	{
		return 0;
	}
	if(0!=strcmp(ent->d_name+strlen(ent->d_name)-strlen(fn_suffix), fn_suffix))
	{
		return 0;
	}
	return 1;
 }

static X509_STORE* _X509_store_create(const char* pem_bundle, const char* pem_dir, struct cert_store_param* param)
{
	int ret=0, n=0, i=0;

	X509_STORE* store=X509_STORE_new();
	char path[TFE_STRING_MAX]={0};
	if (store == NULL)
	{
		return NULL;
	}

	ret = X509_STORE_set_default_paths(store);
	if (ret == 0)
	{
		return NULL;
	}
	if(pem_bundle&&strlen(pem_bundle)>0)
	{
		ret = X509_STORE_load_locations(store, pem_bundle, NULL);
		if (ret == 0)
		{
			return NULL;
		}
		TFE_LOG_INFO(g_default_logger, "X509 trust store load pem boundle: %s", pem_bundle);
	}
	X509_VERIFY_PARAM *x509_param=NULL;
	if(param->check_crl)
	{	
		x509_param = X509_VERIFY_PARAM_new();
		X509_VERIFY_PARAM_set_flags(x509_param, X509_V_FLAG_CRL_CHECK);
		X509_STORE_set1_param(store, x509_param);
		X509_VERIFY_PARAM_free(x509_param);
		TFE_LOG_INFO(g_default_logger, "X509 trust store enable CRL check");
	}
	struct dirent **namelist = NULL;
	if(pem_dir&&strlen(pem_dir)>0)
	{
		n=tfe_scandir(pem_dir, &namelist, NULL, (int (*)(const void*, const void*))alphasort);
		if(n < 0)
		{
			return store;
		}
		
		for(i=0;i<n;i++)
		{
			snprintf(path, sizeof(path), "%s/%s",pem_dir, namelist[i]->d_name);
			if(0==strcasecmp(namelist[i]->d_name+strlen(namelist[i]->d_name)-strlen(".pem"), ".pem"))
			{
				_X509_add_cert_or_crl_add(store, SSL_X509_OBJ_CERT, path);
			}
			else if(0==strcasecmp(namelist[i]->d_name+strlen(namelist[i]->d_name)-strlen(".crl"), ".crl"))
			{
				_X509_add_cert_or_crl_add(store, SSL_X509_OBJ_CRL, path);
			}

			TFE_LOG_INFO(g_default_logger, "X509 trust store found X509 additive trust CA: %s", path);
			free(namelist[i]);
		}

		free(namelist);
	}
	return store;
}

static MESA_htable_handle _create_mesa_htable(void)
{
	UNUSED int ret=0;
	MESA_htable_handle htable = MESA_htable_born();
	ret = __wrapper_MESA_htable_set_opt_int(htable, MHO_SCREEN_PRINT_CTRL, 0);
	ret = __wrapper_MESA_htable_set_opt_int(htable, MHO_THREAD_SAFE, 0);
	ret = __wrapper_MESA_htable_set_opt_int(htable, MHO_MUTEX_NUM, 16);
	ret = __wrapper_MESA_htable_set_opt_int(htable, MHO_HASH_SLOT_SIZE, 1024*4);
	ret = __wrapper_MESA_htable_set_opt_int(htable, MHO_HASH_MAX_ELEMENT_NUM, 0);
	ret = __wrapper_MESA_htable_set_opt_int(htable, MHO_EXPIRE_TIME, 0);
	ret = __wrapper_MESA_htable_set_opt_int(htable, MHO_ELIMIMINATE_TYPE,
		HASH_ELIMINATE_ALGO_FIFO);
	ret = __wrapper_MESA_htable_set_opt_func(htable, MHO_CBFUN_DATA_FREE,
		(void *)free_ssl_x509_obj, sizeof(&free_ssl_x509_obj));
	//ret = __wrapper_MESA_htable_set_opt(htable, MHO_CBFUN_DATA_EXPIRE_NOTIFY,
	//	(void *)key_keeper_verify_cb);
	ret = MESA_htable_mature(htable);
	return htable;
}
struct ssl_trusted_cert_storage* ssl_trusted_cert_storage_create(const char* pem_bundle, 
																const char* pem_dir, struct cert_store_param* param)
{
	struct ssl_trusted_cert_storage* storage=ALLOC(struct ssl_trusted_cert_storage, 1);
	storage->param=*param;
	storage->effective_store=_X509_store_create(pem_bundle, pem_dir, &(storage->param));
	if (storage->effective_store == NULL)
	{
		return NULL;
	}

	storage->pem_bundle=tfe_strdup(pem_bundle);
	storage->pem_dir=tfe_strdup(pem_dir);
	storage->hash_table=_create_mesa_htable();
	pthread_rwlock_init(&(storage->rwlock), NULL);
	assert(SSL_EX_DATA_IDX_VERIFY_PARAM<0);
	SSL_EX_DATA_IDX_VERIFY_PARAM = X509_STORE_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
    storage->status = STABLE;
	return storage;
}
void ssl_trusted_cert_storage_destroy(struct ssl_trusted_cert_storage* storage)
{
	return;
}


int ssl_trusted_cert_storage_add(struct ssl_trusted_cert_storage* storage, enum ssl_X509_obj_type type, const char* filename)
{

	int ret=0;	
	struct ssl_X509_object* obj=NULL;
	void* data=NULL;
    MESA_htable_handle hash_table = NULL;
    X509_STORE *effective_store = NULL;

    pthread_rwlock_wrlock(&(storage->rwlock));
    if (storage->status == UPDATING)
	{
        hash_table = storage->temp_table;
        effective_store = storage->temp_store;
    }
	else
	{
        hash_table = storage->hash_table;
        effective_store = storage->effective_store;
	}

    data = MESA_htable_search(hash_table, (const unsigned char *)filename, strlen(filename));
	if(data!=NULL)//duplicated
	{
		ret=-1;
		goto error_out;
	}
	ret=_X509_add_cert_or_crl_add(effective_store, type, filename);
	if(ret<0)
	{
		ret=-1;
		goto error_out;
	}

	
	obj=ALLOC(struct ssl_X509_object, 1);
	obj->type=SSL_X509_OBJ_CERT;
	obj->filename=tfe_strdup(filename);
	ret=MESA_htable_add(hash_table,  (const unsigned char*)obj->filename, strlen(obj->filename), obj);
	assert(ret>0);
	ret=1;
    TFE_LOG_DEBUG(g_default_logger, "%s %p add %s", storage->status == UPDATING ? "temp store" : "effective store", effective_store, filename);

error_out:	
	pthread_rwlock_unlock(&(storage->rwlock));
	return ret;
}

static void cert_storage_htable_traverse_cb(const uchar * key, uint size, void * data, void * user)
{
	X509_STORE* store=(X509_STORE* )user;
	struct ssl_X509_object* obj=(struct ssl_X509_object*)data;
	_X509_add_cert_or_crl_add(store, obj->type, obj->filename);
	return;
}

int ssl_trusted_cert_storage_del(struct ssl_trusted_cert_storage* storage, enum ssl_X509_obj_type type, const char* filename)
{
	int ret=0;
	X509_STORE* temp_store=NULL;
    MESA_htable_handle hash_table = NULL;
    X509_STORE *effective_store = NULL;
	
    pthread_rwlock_wrlock(&(storage->rwlock));
    if (storage->status == UPDATING)
    {
        hash_table = storage->temp_table;
        effective_store = storage->temp_store;
    }
    else
    {
        hash_table = storage->hash_table;
        effective_store = storage->effective_store;
    }

    ret=MESA_htable_del(hash_table,  (const unsigned char*)filename,  strlen(filename), NULL);
	if(ret<0)
	{
		ret=-1;
		goto error_out;
	}
	temp_store=_X509_store_create(storage->pem_bundle, storage->pem_dir, &(storage->param));
	MESA_htable_iterate(hash_table, cert_storage_htable_traverse_cb, temp_store);
	X509_STORE_free(effective_store);
    TFE_LOG_DEBUG(g_default_logger, "%s %p->%p del %s", storage->status == UPDATING ? "temp store" : "effective store", effective_store, temp_store, filename);

    if (storage->status == UPDATING)
    {
        storage->temp_store = temp_store;
    }
    else
    {
        storage->effective_store = temp_store;
    }

    ret=1;
	
error_out:
	pthread_rwlock_unlock(&(storage->rwlock));
	return ret;
}
void ssl_trusted_cert_storage_reset(struct ssl_trusted_cert_storage* storage)
{
	pthread_rwlock_wrlock(&(storage->rwlock));
    storage->temp_table = _create_mesa_htable();
    storage->temp_store = _X509_store_create(storage->pem_bundle, storage->pem_dir, &(storage->param));
    storage->status = UPDATING;
    TFE_LOG_DEBUG(g_default_logger, "reset effective store %p, create temp store %p", storage->effective_store, storage->temp_store);
    pthread_rwlock_unlock(&(storage->rwlock));
}

void ssl_trusted_cert_storage_reset_finish(struct ssl_trusted_cert_storage *storage)
{
    pthread_rwlock_wrlock(&(storage->rwlock));
    MESA_htable_destroy(storage->hash_table, NULL);
    X509_STORE_free(storage->effective_store);
    storage->effective_store = storage->temp_store;
    storage->hash_table = storage->temp_table;
    storage->temp_table = NULL;
    storage->temp_store = NULL;
    storage->status = STABLE;
    TFE_LOG_DEBUG(g_default_logger, "change temp store to effective store %p", storage->effective_store);
    pthread_rwlock_unlock(&(storage->rwlock));
}

static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
    int err=0, ret=0;
	struct cert_verify_param* param=NULL;

	if(preverify_ok)
	{
		return 1;
	}
    err = X509_STORE_CTX_get_error(ctx);
    /*
     * Retrieve the pointer to the SSL of the connection currently treated
     * and the application specific data stored into the SSL object.
     */
	param = (struct cert_verify_param*)X509_STORE_CTX_get_ex_data(ctx, SSL_EX_DATA_IDX_VERIFY_PARAM);
	switch(err)
	{
		case	X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
		case	X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
		case	X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
			if(param->no_verify_issuer)
			{
				ret=1;
			}
			param->real_untrust |= 0x02;
			break;
		case	X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
			if(param->no_verify_self_signed)
			{
				ret=1;
			}
			param->real_untrust |= 0x04;
			break;
		case	X509_V_ERR_CERT_NOT_YET_VALID:
		case	X509_V_ERR_CERT_HAS_EXPIRED:
			if(param->no_verify_expiry_date)
			{
				ret=1;
			}
			param->real_untrust |= 0x08;
			break;
		case	X509_V_ERR_UNABLE_TO_GET_CRL:
		case	X509_V_ERR_DIFFERENT_CRL_SCOPE:
		case	X509_V_ERR_CRL_HAS_EXPIRED:
			ret=1;
			break;
		case	X509_V_ERR_HOSTNAME_MISMATCH:
		case	X509_V_ERR_EMAIL_MISMATCH:
		case	X509_V_ERR_IP_ADDRESS_MISMATCH:
			ret=1;	//host match is verfied via X509_check_host
			break;
		default:
			param->real_untrust |= 0x10;
			ret=0;
			break;
	}
	return ret;
 }

int ssl_trusted_cert_storage_verify_conn(struct ssl_trusted_cert_storage* storage, SSL * ssl, const char* hostname, struct cert_verify_param* param, char* reason, size_t n_reason, struct cert_verify_result* result)
{
	int ret = 0, err_code=0;
	char *subj=NULL, *issuer=NULL;
	STACK_OF(X509) * cert_chain = SSL_get_peer_cert_chain(ssl);
	if (cert_chain == NULL)
	{
		// The peer certificate chain is not necessarily available after reusing a session, in which case a NULL pointer is returned.
		return 1;
	}
	param->real_untrust = 0;
	X509 * cert = sk_X509_value(cert_chain, 0);

	int hostmatched;
	if (hostname)
	{
		hostmatched = X509_check_host(cert, hostname, strlen(hostname), 0, NULL);
		if (hostmatched != 1)
			param->real_untrust |= 0x01;
	}

	if(!param->no_verify_cn&&hostname)
	{
		result->is_hostmatched=hostmatched;
	}
	else
	{
		result->is_hostmatched=1;
	}
	
	char* oid=ssl_x509_get_extension(cert, NID_certificate_policies);
	if(oid)
	{
		result->is_ev=ssl_x509_is_ev(oid);
	}
	free(oid);
	oid=NULL;
	
	int crit = 0;
    STACK_OF(SCT) *sct = (STACK_OF(SCT) *)X509_get_ext_d2i(cert, NID_ct_precert_scts, &crit, NULL);
    if(sct)
	{
		result->is_ct=1;
	}
    SCT_LIST_free(sct);

    X509_STORE_CTX * ctx = X509_STORE_CTX_new();
	pthread_rwlock_rdlock(&(storage->rwlock));
	ret = X509_STORE_CTX_init(ctx, storage->effective_store, cert, cert_chain);
	assert(ret == 1);

	X509_STORE_CTX_set_verify_cb(ctx, verify_callback);
	X509_STORE_CTX_set_ex_data(ctx, SSL_EX_DATA_IDX_VERIFY_PARAM, param);

	//If a complete chain can be built and validated this function returns 1, otherwise it return zero or negtive code.
	ret = X509_verify_cert(ctx);
	err_code=X509_STORE_CTX_get_error(ctx);
	result->error_code=err_code;
	if(ret!=1)
	{
		subj=ssl_x509_subject(cert);
		issuer=ssl_x509_issuer(cert);

		snprintf(reason, n_reason, "%s : subject - %s issuer - %s",
								X509_verify_cert_error_string(err_code),
								subj,
								issuer);
		free(subj);
		free(issuer);
		ret=0;
	}
	else
	{
		ret=1;
	}

	if (is_ssl_debug())
	{
		ssl_chain_dump(hostname, "server_chain", cert_chain);
	}

    // case cert verify success 
	if (param->real_untrust == 0)
	{
		STACK_OF(X509) *trust_chain = X509_STORE_CTX_get1_chain(ctx);
		if (trust_chain)
		{
			if (is_ssl_debug())
			{
				ssl_chain_dump(hostname, "trust_chain", trust_chain);
			}
			ssl_fetch_trusted_cert_from_chain(trust_chain, storage->effective_store, hostname);
			sk_X509_pop_free(trust_chain, X509_free);
        }
	}

	X509_STORE_CTX_free(ctx);
	pthread_rwlock_unlock(&(storage->rwlock));
	return ret;
}