/**********************************************************************
 
	Copyright (C) 2003-2005
	Hirohisa MORI <joshua@nichibun.ac.jp>
	Tomohito Nakajima <nakajima@zeta.co.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/

#include "ossl.h"

#include "openssl/err.h"
#include "openssl/rand.h"

SEM *oss_lock_ary;
int NID_gb_comment;

oSSL_method *oSSL_types[MAX_OSSL_OBJECT_TYPE];

oSSL_object *oSSL_object_new(int type)
{
	oSSL_object *obj;
	obj = d_alloc(sizeof(oSSL_object));
	obj->type = type;
	obj->method = oSSL_types[type];
	obj->refcnt = 1;
	return obj;
}

void oSSL_object_free(oSSL_object * obj)
{
	if(obj){
		--(obj->refcnt);
		if(obj->refcnt<=0){
			if(obj->method){
				obj->method->free(obj);
			}
		}
	}
}

oSSL_data *oSSL_data_new(int type, unsigned char *data, int len, int own_data, int buff_size)
{
	oSSL_data *ret;
	ret = d_alloc(sizeof(oSSL_data));
	ret->type = type;
	ret->data = data;
	ret->len = len;
	ret->own_data = own_data;
	ret->buff_size = buff_size;
	return ret;
}

void
oSSL_data_free(oSSL_data * d)
{
	if(d->len){
		if(d->own_data){
			if(d->data)
				d_f_ree(d->data);
		}
		d->len = 0;
	}
	if(d)
		d_f_ree(d);
}

void oSSL_data_ensure_buff_size(oSSL_data *data, int size){
	if(data->data == NULL || data->buff_size < size){
		if(data->own_data && data->data){
			d_f_ree(data->data);
		}
		data->buff_size = size;
		data->data = d_alloc(size);
		data->len = size;
		data->own_data = TRUE;
	}
}

static void oSSL_lock_func(int mode, int index, const char* file, int line)
{
	if(mode & CRYPTO_LOCK){
		lock_task(oss_lock_ary[index]);
	}
	else{
		unlock_task(oss_lock_ary[index], "oSSL_lock_func");
	}
}

static unsigned long oSSL_get_thread_id(void)
{
	return (unsigned long)get_tid();
}

void oSSL_init()
{
	int i;
	
	SSL_library_init();
	OpenSSL_add_all_algorithms();
	
	ERR_load_crypto_strings();
	ERR_load_SSL_strings();

	if(!RAND_load_file("/dev/random",1024)){
#ifdef OPENSSL_SYS_WINDOWS
		RAND_screen();
#else
		ossl_error("rand file error.");
#endif
	}
	
	oss_lock_ary = d_alloc(CRYPTO_NUM_LOCKS*sizeof(SEM));
	
	for(i=0; i<CRYPTO_NUM_LOCKS; ++i){
		oss_lock_ary[i] = new_lock(LL_OSSL);
	}
	CRYPTO_set_id_callback(oSSL_get_thread_id);
	CRYPTO_set_locking_callback(oSSL_lock_func);
	
	
	oSSL_RSA_init();
	oSSL_RC4_init();
	oSSL_cert_init();
	oSSL_csr_init();

	/* prepare extension */
	NID_gb_comment = OBJ_create(OID_gb_comment, SN_gb_comment, LN_gb_comment);
	X509V3_EXT_add_alias(NID_gb_comment, NID_netscape_comment);
	/*
	add_ext(x, NID_gbs_comment, "ossl test example comment");
	*/
}	

void oSSL_close()
{
	int i;
	CRYPTO_set_id_callback(0);
	CRYPTO_set_locking_callback(0);

	if(oss_lock_ary){
		for(i=0; i<CRYPTO_NUM_LOCKS; ++i){
			close_lock(oss_lock_ary[i]);
		}
		d_f_ree(oss_lock_ary);
		oss_lock_ary = NULL;
	}
}

oSSL_data *oSSL_object2data(oSSL_object * obj)
{
	return oSSL_types[obj->type]->object2data(obj);
}

oSSL_object *oSSL_data2object(oSSL_data * data)
{
	return oSSL_types[data->type]->data2object(data);
}

OSSL_BOOL oSSL_object_save_pem(oSSL_object * obj, const char* file, const char* password/* egnored if obj is not private key */)
{
	if(oSSL_types[obj->type]->save_pem){
		return oSSL_types[obj->type]->save_pem(obj, file, password);
	}
	else{
		ossl_error("save_pem is not implemented for this object");
		return FALSE;
	}

}

OSSL_BOOL oSSL_object_load_pem(oSSL_object * obj, const char* file, const char* password/* egnored if obj is not private key */)
{
	if(oSSL_types[obj->type]->load_pem){
		return oSSL_types[obj->type]->load_pem(obj, file, password);
	}
	else{
		ossl_error("load_pem is not implemented for this object");
		return FALSE;
	}
}


oSSL_object_list *oSSL_object_list_new(oSSL_object_list **list, oSSL_object *obj)
{
	oSSL_object_list *ret;
	
	ret = (oSSL_object_list *)d_alloc(sizeof(oSSL_object_list));
	ret->obj = obj;
	
	if(list){
		ret->next = *list;
		*list = ret;
	}
	else{
		ret->next = NULL;
	}
	
	return ret;
}
void oSSL_object_list_free(oSSL_object_list *lst){
	oSSL_object_list *it,*next;
	for(it=lst; it; it=next){
		next=it->next;
		oSSL_object_free(it->obj);
		d_f_ree(it);
	}
}

OSSL_BOOL oSSL_add_root_certificate(oSSL_context * ctx, oSSL_object *certificate)
{
	if(certificate->type != OSSL_CERTIFICATE){
		er_panic("oSSL_add_root_certificate: type error");
	}
	
	oSSL_object_list_new(&ctx->root_certs, certificate);
	
	return X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx->ctx), certificate->obj.x509);
}

oSSL_object_list *oSSL_get_root_certificates(oSSL_context * ctx)
{
	return ctx->root_certs;
}

void ___ossl_error(const char *msg, const char *file, int line)
{
	printf("%s  file %s:line %d", msg, file, line);
	oSSL_print_all_error();
	printf("\n");
}



void oSSL_context_free(oSSL_context *ctx)
{
	SSL_CTX_free(ctx->ctx);
	oSSL_object_list_free(ctx->root_certs);
	d_f_ree(ctx);
}

oSSL_context *oSSL_context_new(
	oSSL_object *certificate, 
	oSSL_object *privatekey,
	int flags)
{
	oSSL_context *ret;
	
	ret = d_alloc(sizeof(oSSL_context));
	ret->ctx = SSL_CTX_new(SSLv23_method());
	ret->root_certs = NULL;

	if(certificate){
		if(!SSL_CTX_use_certificate(ret->ctx, certificate->obj.x509)){
			ossl_error("oSSL_context_new() use_certfivicate error");
			goto err;
		}
	}
	
	if(privatekey){
		if(!SSL_CTX_use_PrivateKey(ret->ctx, privatekey->obj.pkey.pkey)){
			ossl_error("oSSL_context_new() use_privatekey error");
			goto err;
		}
	}
	
	return ret;

err:
	if(ret->ctx)
		SSL_CTX_free(ret->ctx);
	if(ret)
		d_f_ree(ret);
	return NULL;
}

void oSSL_print_all_error()
{
	int flags,line;
	const char *data,*file;
	unsigned long code;

	while( (code = ERR_get_error_line_data(&file, &line, &data, &flags)) ){
		printf("openssl_lib_error: %lu in %s line %d.\n", code, file, line);
		if(data && (flags & ERR_TXT_STRING)){
			printf("error data: %s\n", data);
		}
	}
}


const EVP_MD *oSSL_get_MD_by_pkey(EVP_PKEY *pkey)
{
	switch(EVP_PKEY_type(pkey->type)){

	case EVP_PKEY_DSA:
		return EVP_dss1();

	case EVP_PKEY_RSA:
		return EVP_sha1();
	
	default:
		return EVP_sha1();
	
	}
}

