/**********************************************************************
 
	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.

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

#define STREAM_LIB
#include "ossl.h"
#include "stream.h"
#include "openssl/x509v3.h"
#include "machine/include.h"


#define VERIFY_DEPTH (5)

extern SEM stream_lock;

int
s_close_ossl(STREAM * s)
{
	if(SSL_get_shutdown(s->ossl.ssl) & SSL_RECEIVED_SHUTDOWN){
		SSL_shutdown(s->ossl.ssl);
	}
	else{
		if(s->ossl.is_error){
			SSL_clear(s->ossl.ssl);
		}
		else{
			SSL_shutdown(s->ossl.ssl);
		}
	}
	SSL_free(s->ossl.ssl);
	return 0;
}

int
s_write_ossl(STREAM * s, void * data, int len)
{
	int ret;
	ret = SSL_write(s->ossl.ssl, data, len);
	if(ret < 0){
		s->ossl.is_error = 1;
	}
	return ret;
}

int
s_read_ossl(STREAM * s, void * data, int len)
{
	int ret;
	ret = SSL_read(s->ossl.ssl, data, len);
	if(ret < 0){
		s->ossl.is_error = 1;
	}
	return ret;
}

int
s_flush_ossl(STREAM * s)
{
	return 0;
}

int	s_proc_send_ossl()
{
	er_panic("oss proc send not supported");
	return 0;
}

STREAM *s_proc_recv_ossl()
{
	er_panic("oss proc recv not supported");
	return 0;
}

S_TABLE s_ossl_table = {
	'o',
	0,
	{0,0},
	NULL,
	NULL,
	s_close_ossl,
	s_write_ossl,
	s_read_ossl,
	s_flush_ossl,
	s_proc_send_ossl,
	s_proc_recv_ossl
};


int verify_host_name_in_certificate(X509 *cert, char *peer_host_name_for_verify){
	int ext_count;
	int is_ok;

	is_ok = 0;
	
	ext_count = X509_get_ext_count(cert);

	if(ext_count > 0){
		/* search name from extension area */
		int i;
		for(i=0; i<ext_count; ++i){
			const char * ext_str;
			X509_EXTENSION *ext;
			
			ext = X509_get_ext(cert, i);
			ext_str = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
			if(strcmp(ext_str, "subjectAltName") == 0){
				int j;
				unsigned char *data;
				STACK_OF(CONF_VALUE) *val;
				CONF_VALUE *nval;
				X509V3_EXT_METHOD *method;
				void *ext_i;
				
				method = X509V3_EXT_get(ext);
				if(!method)
					break;
				
				data = ext->value->data;
				if(method->it){
					ext_i = ASN1_item_d2i(NULL, &data, ext->value->length, ASN1_ITEM_ptr(method->it));
				}
				else{
					ext_i = method->d2i(NULL, &data, ext->value->length);
				}
				val = method->i2v(method,ext_i,NULL);
				
				for(j = 0; j<sk_CONF_VALUE_num(val); ++j){
					nval = sk_CONF_VALUE_value(val, j);
					if( (strcmp(nval->name, "DNS")==0) &&
						(strcmp(nval->value, peer_host_name_for_verify)==0) ){
						return X509_V_OK;
					}
				}
			}
		}
	}

	{
		/* compare hostname with commonName */
		X509_NAME *subj;
		char data[256];
		
		subj = X509_get_subject_name(cert);
		if(subj){
			memset(data, 0, sizeof(data));
			if(X509_NAME_get_text_by_NID(subj, NID_commonName, data, sizeof(data)) > 0)
			{
				if(strcasecmp(data, peer_host_name_for_verify) == 0){
					return X509_V_OK;
				}
			}
		}
	}

	return X509_V_ERR_APPLICATION_VERIFICATION;
}

int ossl_verify_peer(SSL *ssl, char *peer_host_name_for_verify)
{
	int verify_result;

	verify_result = SSL_get_verify_result(ssl);
	if(verify_result != X509_V_OK){
		return verify_result;
	}
	
	if(peer_host_name_for_verify){
		X509 *cert;
		
		cert = SSL_get_peer_certificate(ssl);
		if(!cert){
			return X509_V_ERR_APPLICATION_VERIFICATION;
		}
		verify_result =  verify_host_name_in_certificate(cert, peer_host_name_for_verify);
		X509_free(cert);
	}
	
	return verify_result;
}

static int verify_callback(int ok, X509_STORE_CTX *store)
{
	char data[256];
	if(!ok){
		X509 *cert;
		int depth,err;

		cert = X509_STORE_CTX_get_current_cert(store);
		depth = X509_STORE_CTX_get_error_depth(store);
		err = X509_STORE_CTX_get_error(store);
		
		printf("-Error with certificate at depth: %i\n", depth);
		X509_NAME_oneline(X509_get_issuer_name(cert), data, 256);
		printf("  issuer  = %s\n", data);
		X509_NAME_oneline(X509_get_subject_name(cert), data, 256);
		printf("  subject  = %s\n", data);
		printf("  err %i:%s\n", err, X509_verify_cert_error_string(err));
	}
	return ok;
}


STREAM * s_open_oSSL(STREAM * st, oSSL_context *ctx, int flags, L_CHAR *peer_host_name_for_verify)
{
	STREAM * ret;
	BIO *bio;
	SSL *ssl;
	int mode;
	
	ret = NULL;
	if(flags & OSSL_FLAG_VERIFY){
		mode = SSL_CTX_get_verify_mode(ctx->ctx);
		if(!(mode & SSL_VERIFY_PEER)){
			mode |= SSL_VERIFY_PEER;
			mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
			SSL_CTX_set_verify(ctx->ctx, mode, verify_callback);
			SSL_CTX_set_verify_depth(ctx->ctx, VERIFY_DEPTH);
		}
	}
	else{
		mode = SSL_CTX_get_verify_mode(ctx->ctx);
		if(mode != SSL_VERIFY_NONE){
			SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, 0); 
		}
	}
	
	bio = s_bio_new(st);
	
	ssl = SSL_new(ctx->ctx);
	if(!ssl){
		ossl_error("error create ssl");
	}
	SSL_set_bio(ssl, bio, bio);
	
	if(flags & OSSL_FLAG_CONNECT){
		if(SSL_connect(ssl) <= 0){
			ossl_error("error connect ssl");
			return 0;
		}
	}
	else{
		if(SSL_accept(ssl) <= 0){
			ossl_error("error accept ssl");
			oSSL_print_all_error();
			return 0;
		}
	}
	
	ret = (STREAM *)d_alloc(sizeof(S_OSSL));
	ret->h.tbl = &s_ossl_table;
	ret->h.thread = 0;
	ret->ossl.ssl = ssl;
	ret->ossl.is_error = 0;
	
	lock_task(stream_lock);
	_s_open(ret, st->h.mode);
	unlock_task(stream_lock,"s_open_oSSL");
	
	if(flags & OSSL_FLAG_VERIFY){
		int result;
		if( peer_host_name_for_verify == NULL ){
			ossl_error("verify peer name is needed");
			goto err;
		}
		result = ossl_verify_peer(ssl, n_string(std_cm, peer_host_name_for_verify) );
		if(result != X509_V_OK){
			ossl_error("error ssl verify");
			goto err;
		}
	}
	return ret;
err:
s_close(ret);
	return NULL;
}
