#include <ssl_stream.h>
#include <tfe_utils.h>
#include <MESA/maat.h>
#include <cjson/cJSON.h>
#include <openssl/ssl.h>
#include <assert.h>
#include <tfe_resource.h>
#include <tfe_cmsg.h>

struct ssl_policy_enforcer
{
	struct maat *maat;
	int profile_table_id;
	void* logger;
};

struct decryption_param
{
	int ref_cnt;
	int bypass_ev_cert;
	int bypass_ct_cert;
	int bypass_mutual_auth;
	int bypass_pinning;
	int bypass_uninstall_cert_traffic;
	int bypass_protocol_errors;
	int no_verify_cn;
	int no_verify_issuer;
	int no_verify_self_signed;
	int no_verify_expry_date;
	int block_fake_cert;
	int ssl_min_version;
	int ssl_max_version;
	int allow_http2;
	int mirror_client_version;
};

void profile_param_dup_cb(int table_id, void **to, void **from, long argl, void* argp)
{
	struct decryption_param* param= (struct decryption_param*) *from;
	if(param)
	{
		 __sync_add_and_fetch(&(param->ref_cnt), 1);
		*to = param;
	}
	else
	{
		*to=NULL;
	}
	return;
}
void profile_param_free_cb(int table_id, void **ad, long argl, void* argp)
{
	struct decryption_param* param= (struct decryption_param*) *ad;
	if(param==NULL)
	{
		return;
	}

    if ((__sync_sub_and_fetch(&param->ref_cnt, 1) == 0))
	{
		free(param);
		*ad=NULL;
	}
}
void profile_param_free(struct decryption_param* param)
{
	profile_param_free_cb(0, (void**)&param, 0, NULL);
	return;
}
void profile_param_new_cb(const char *table_name, int table_id, const char* key, const char* table_line, void **ad, long argl, void* argp)
{
	int ret=0;
	size_t offset=0, len=0;
	char* json_str=NULL;
	cJSON *json=NULL, *exclusions=NULL, *cert_verify=NULL, *approach=NULL, *ssl_ver=NULL, *item=NULL;
	struct decryption_param* param=NULL;
	struct ssl_policy_enforcer* enforcer=(struct ssl_policy_enforcer*)argp;

	ret=maat_helper_read_column(table_line, 3, &offset, &len);
	if(ret<0)
	{
		TFE_LOG_ERROR(enforcer->logger, "Get decryption param: %s", table_line);
		goto error_out;
	}
	json_str=ALLOC(char, len+1);
	memcpy(json_str, table_line+offset, len);
	json=cJSON_Parse(json_str);
	if(json==NULL)
	{
		TFE_LOG_ERROR(enforcer->logger, "Invalid decryption parameter: %s", table_line);
		goto error_out;
	}

	param=ALLOC(struct decryption_param, 1);
	param->ref_cnt=1;
	param->bypass_mutual_auth=1;
	param->bypass_pinning=1;
	param->mirror_client_version=1;

	exclusions=cJSON_GetObjectItem(json, "dynamic_bypass");
	if(exclusions)
	{
		item=cJSON_GetObjectItem(exclusions, "ev_cert");
		if(item && item->type==cJSON_Number) param->bypass_ev_cert=item->valueint;
		item=cJSON_GetObjectItem(exclusions, "cert_transparency");
		if(item && item->type==cJSON_Number) param->bypass_ct_cert=item->valueint;
		item=cJSON_GetObjectItem(exclusions, "mutual_authentication");
		if(item && item->type==cJSON_Number) param->bypass_mutual_auth=item->valueint;
		item=cJSON_GetObjectItem(exclusions, "cert_pinning");
		if(item && item->type==cJSON_Number) param->bypass_pinning=item->valueint;
		item=cJSON_GetObjectItem(exclusions, "protocol_errors");
		if(item && item->type==cJSON_Number) param->bypass_protocol_errors=item->valueint;
		item=cJSON_GetObjectItem(exclusions, "trusted_root_cert_is_not_installed_on_client");
		if(item && item->type==cJSON_Number) param->bypass_uninstall_cert_traffic=item->valueint;
	}
	ssl_ver=cJSON_GetObjectItem(json, "protocol_version");
	if(ssl_ver)
	{
		item=cJSON_GetObjectItem(ssl_ver, "mirror_client");
		if(item && item->type==cJSON_Number) param->mirror_client_version=item->valueint;
		if(!param->mirror_client_version)
		{
			item=cJSON_GetObjectItem(ssl_ver, "min");
			if(item && item->type==cJSON_String) param->ssl_min_version=sslver_str2num(item->valuestring);
			item=cJSON_GetObjectItem(ssl_ver, "max");
			if(item && item->type==cJSON_String) param->ssl_max_version=sslver_str2num(item->valuestring);
			if(param->ssl_min_version<0||param->ssl_max_version<0)
			{
				param->mirror_client_version=1;
				TFE_LOG_ERROR(enforcer->logger, "Invalid intercept parameter: ssl version = %s", item->valuestring);
			}
		}
		item=cJSON_GetObjectItem(ssl_ver, "allow_http2");
		if(item && item->type==cJSON_Number) param->allow_http2=item->valueint;
	}
	cert_verify=cJSON_GetObjectItem(json, "certificate_checks");
	if(cert_verify)
	{
		approach=cJSON_GetObjectItem(cert_verify, "approach");
		if(approach)
		{
			item=cJSON_GetObjectItem(approach, "cn");
			if(item && item->type==cJSON_Number && item->valueint==0) param->no_verify_cn=1;
			item=cJSON_GetObjectItem(approach, "issuer");
			if(item && item->type==cJSON_Number && item->valueint==0) param->no_verify_issuer=1;
			item=cJSON_GetObjectItem(approach, "self-signed");
			if(item && item->type==cJSON_Number && item->valueint==0) param->no_verify_self_signed=1;
			item=cJSON_GetObjectItem(approach, "expiration");
			if(item && item->type==cJSON_Number && item->valueint==0) param->no_verify_expry_date=1;
		}
		item=cJSON_GetObjectItem(cert_verify, "fail_action");
		if(item && item->type==cJSON_String)
		{
			if(0==strcasecmp(item->valuestring, "Fail-Close"))
			{
				param->block_fake_cert=1;
			}
		}
	}

	*ad=param;
	TFE_LOG_INFO(enforcer->logger, "Add decryption profile: %s", key);
error_out:
	if (json)
		cJSON_Delete(json);
	if (json_str)
		free(json_str);
	return;
}
struct ssl_policy_enforcer* ssl_policy_enforcer_create(void* logger)
{
	UNUSED int ret=0;
	struct ssl_policy_enforcer* enforcer=ALLOC(struct ssl_policy_enforcer, 1);
	enforcer->maat=(struct maat*)tfe_bussiness_resouce_get(STATIC_MAAT);;
	enforcer->logger=logger;
	enforcer->profile_table_id=maat_get_table_id(enforcer->maat, "PXY_PROFILE_DECRYPTION");
	assert(enforcer->profile_table_id >= 0);
	ret=maat_plugin_table_ex_schema_register(enforcer->maat,
										"PXY_PROFILE_DECRYPTION",
										profile_param_new_cb,
										profile_param_free_cb,
										profile_param_dup_cb,
										0,
										enforcer);
	assert(ret==0);
	return enforcer;
}
enum ssl_stream_action ssl_policy_enforce(struct ssl_stream *upstream, void* u_para)
{
	UNUSED struct ssl_policy_enforcer* enforcer=(struct ssl_policy_enforcer*)u_para;
	enum ssl_stream_action action=SSL_ACTION_PASSTHROUGH;
	UNUSED int ret=0;
	char sni[512];
	char addr_string[512];
	char profile_id_str[16]={0};

	uint64_t policy_id = ssl_stream_get_policy_id(upstream);
	int decryption_profile_id = ssl_stream_get_decrypted_profile_id(upstream);
	int keyring_for_trusted =  ssl_stream_get_trusted_keyring_profile_id(upstream);
	int keyring_for_untrusted = ssl_stream_get_untrusted_keyring_profile_id(upstream);


	ssl_stream_get_string_opt(upstream, SSL_STREAM_OPT_SNI, sni, sizeof(sni));
	ssl_stream_get_string_opt(upstream, SSL_STREAM_OPT_ADDR, addr_string, sizeof(addr_string));
	TFE_LOG_DEBUG(enforcer->logger, "%s %s enforce policy %lu", addr_string, sni, policy_id);

	snprintf(profile_id_str, sizeof(profile_id_str), "%u", decryption_profile_id);
	struct decryption_param *profile_param=(struct decryption_param *)maat_plugin_table_get_ex_data(enforcer->maat, enforcer->profile_table_id, profile_id_str);
	if (profile_param==NULL)
	{
		TFE_LOG_INFO(enforcer->logger, "Failed to get decryption parameter of profile %s.", profile_id_str);
		ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Invalid Decryption Param");
		return SSL_ACTION_PASSTHROUGH;
	}
	int pinning_staus=0, is_ev=0, is_ct=0, is_mauth=0, has_error=0, ja3_pinning_status=0;
 	if(!profile_param->mirror_client_version)
	{
		ret=ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_PROTOCOL_MIN_VERSION, profile_param->ssl_min_version);
		ret=ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_PROTOCOL_MAX_VERSION, profile_param->ssl_max_version);
	}
	if(profile_param->allow_http2)
	{
		ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_ENABLE_ALPN, 1);
	}
	ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_NO_VERIFY_COMMON_NAME, profile_param->no_verify_cn);
	ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_NO_VERIFY_ISSUER, profile_param->no_verify_issuer);
	ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_NO_VERIFY_SELF_SIGNED, profile_param->no_verify_self_signed);
	ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_NO_VERIFY_EXPIRY_DATE, profile_param->no_verify_expry_date);
	if(profile_param->block_fake_cert)
	{
		ret=ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_BLOCK_FAKE_CERT, 1);
	}
	ret=ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_KEYRING_FOR_TRUSTED, keyring_for_trusted);
	ret=ssl_stream_set_integer_opt(upstream, SSL_STREAM_OPT_KEYRING_FOR_UNTRUSTED, keyring_for_untrusted);

	ret=ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_PINNING_STATUS, &pinning_staus);
	assert(ret==0);
	ret=ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_JA3_PINNING_STATUS, &ja3_pinning_status);
	assert(ret==0);
	ret=ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_IS_EV_CERT, &is_ev);
	assert(ret==0);
	ret=ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_IS_MUTUAL_AUTH, &is_mauth);
	ret=ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_IS_CT_CERT, &is_ct);
	ret=ssl_stream_get_integer_opt(upstream, SSL_STREAM_OPT_HAS_PROTOCOL_ERRORS, &has_error);
	assert(ret==0);

	if (pinning_staus == 1 && ja3_pinning_status == JA3_PINNING_STATUS_NOT_PINNING && profile_param->bypass_uninstall_cert_traffic)
	{
		action = SSL_ACTION_PASSTHROUGH;
		ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Certificate Not Installed");
		TFE_LOG_DEBUG(enforcer->logger, "%s %s enforce policy_id %lu, action PASSTHROUGH due to Certificate Not Installed", addr_string, sni, policy_id);
	}
	else if ((pinning_staus == 1 || ja3_pinning_status == JA3_PINNING_STATUS_IS_PINNING) && ja3_pinning_status != JA3_PINNING_STATUS_NOT_PINNING && profile_param->bypass_pinning)
	{
		action = SSL_ACTION_PASSTHROUGH;
		ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Certificate Pinning");
		TFE_LOG_DEBUG(enforcer->logger, "%s %s enforce policy_id %lu, action PASSTHROUGH due to Certificate Pinning", addr_string, sni, policy_id);
	}
	else if (is_mauth && profile_param->bypass_mutual_auth)
	{
		action = SSL_ACTION_PASSTHROUGH;
		ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Mutual Authentication");
		TFE_LOG_DEBUG(enforcer->logger, "%s %s enforce policy_id %lu, action PASSTHROUGH due to Mutual Authentication", addr_string, sni, policy_id);
	}
	else if (is_ev && profile_param->bypass_ev_cert)
	{
		action = SSL_ACTION_PASSTHROUGH;
		ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "EV Certificate");
		TFE_LOG_DEBUG(enforcer->logger, "%s %s enforce policy_id %lu, action PASSTHROUGH due to EV Certificate", addr_string, sni, policy_id);
	}
	else if (is_ct && profile_param->bypass_ct_cert)
	{
		action = SSL_ACTION_PASSTHROUGH;
		ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Certificate Transparency");
		TFE_LOG_DEBUG(enforcer->logger, "%s %s enforce policy_id %lu, action PASSTHROUGH due to Certificate Transparency", addr_string, sni, policy_id);
	}
	else if (has_error && profile_param->bypass_protocol_errors)
	{
		action = SSL_ACTION_PASSTHROUGH;
		ssl_stream_set_cmsg_string(upstream, TFE_CMSG_SSL_PASSTHROUGH_REASON, "Protocol Errors");
		TFE_LOG_DEBUG(enforcer->logger, "%s %s enforce policy_id %lu, action PASSTHROUGH due to Protocol Errors", addr_string, sni, policy_id);
	}
	else
	{
		action = SSL_ACTION_INTERCEPT;
	}

	profile_param_free(profile_param);
	profile_param=NULL;
	return action;
}

