/*
 * $Id: tls_init.c 1827 2007-03-12 15:22:53Z bogdan_iancu $
 *
 * Copyright (C) 2001-2003 FhG Fokus
 * Copyright (C) 2004,2005 Free Software Foundation, Inc.
 * Copyright (C) 2006 enum.at
 *
 * This file is part of openser, a free SIP server.
 *
 * openser 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
 *
 * openser 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.
 *
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
 
#include "tls_init.h"
#include "tls_config.h"
#include "../dprint.h"
#include "../mem/shm_mem.h"
#include "../tcp_init.h"
#include "../ut.h"
#include "tls_domain.h"

#include <openssl/ui.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>
#include <openssl/err.h>

#include <netinet/in_systm.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <unistd.h>

#define SER_SSL_SESS_ID ((unsigned char*)"openser-tls-1.2.0")
#define SER_SSL_SESS_ID_LEN (sizeof(SER_SSL_SESS_ID)-1)


#if OPENSSL_VERSION_NUMBER < 0x00907000L
	#warning ""
	#warning "=============================================================="
	#warning "Your version of OpenSSL is < 0.9.7."
	#warning " Upgrade for better compatibility, features and security fixes!"
	#warning "============================================================="
	#warning ""
#endif

SSL_METHOD     *ssl_methods[TLS_USE_SSLv23 + 1];

#define VERIFY_DEPTH_S 3

/* This callback is called during each verification process, 
at each step during the chain of certificates (this function
is not the certificate_verification one!). */
int verify_callback(int pre_verify_ok, X509_STORE_CTX *ctx) {
	char buf[256];
	X509 *err_cert;
	int err, depth;

	depth = X509_STORE_CTX_get_error_depth(ctx);
	LOG( 2, "tls_init: verify_callback: depth = %d\n",depth);
	if ( depth > VERIFY_DEPTH_S ) {
		LOG( 2, "tls_init: verify_callback: cert chain too long "
			"( depth > VERIFY_DEPTH_S)\n");
		pre_verify_ok=0;
	}
	
	if( pre_verify_ok ) {
		LOG( 2, "tls_init: verify_callback: preverify is good: "
			"verify return: %d\n", pre_verify_ok);
		return pre_verify_ok;
	}
	
	err_cert = X509_STORE_CTX_get_current_cert(ctx);
	err = X509_STORE_CTX_get_error(ctx);	
	X509_NAME_oneline(X509_get_subject_name(err_cert),buf,sizeof buf);
	
	LOG( 2, "tls_init: verify_callback: subject = %s\n", buf);
	LOG( 2, "tls_init: verify_callback: verify error:num=%d:%s\n",
		err, X509_verify_cert_error_string(err));
	LOG( 2, "tls_init: verify_callback: error code is %d\n", ctx->error);
	
	switch (ctx->error) {
		case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
			X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert),
				buf,sizeof buf);
			LOG( 2, "tls_init: verify_callback: issuer= %s\n",buf);
			break;
		case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
		case X509_V_ERR_CERT_NOT_YET_VALID:
			LOG( 2, "tls_init: verify_callback: notBefore\n");
			break;
		case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
		case X509_V_ERR_CERT_HAS_EXPIRED:
			LOG( 2, "tls_init: verify_callback: notAfter\n");
			break;
		case X509_V_ERR_CERT_SIGNATURE_FAILURE:
		case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
			LOG( 2, "tls_init: verify_callback: unable to decrypt cert "
				"signature\n");
			break;
		case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
			LOG( 2, "tls_init: verify_callback: unable to decode issuer "
				"public key\n");
			break;
		case X509_V_ERR_OUT_OF_MEM:
			LOG( 2, "tls_init: verify_callback: Out of memory \n");
			break;
		case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
		case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
			LOG( 2, "tls_init: verify_callback: Self signed certificate "
				"issue\n");
			break;
		case X509_V_ERR_CERT_CHAIN_TOO_LONG:
			LOG( 2, "tls_init: verify_callback: certificate chain too long\n");
			break;
		case X509_V_ERR_INVALID_CA:
			LOG( 2, "tls_init: verify_callback: invalid CA\n");
			break;
		case X509_V_ERR_PATH_LENGTH_EXCEEDED:
			LOG( 2, "tls_init: verify_callback: path length exceeded\n");
			break;
		case X509_V_ERR_INVALID_PURPOSE:
			LOG( 2, "tls_init: verify_callback: invalid purpose\n");
			break;
		case X509_V_ERR_CERT_UNTRUSTED:
			LOG( 2, "tls_init: verify_callback: certificate untrusted\n");
			break;
		case X509_V_ERR_CERT_REJECTED:
			LOG( 2, "tls_init: verify_callback: certificate rejected\n");
			break;
		
		default:
			LOG( 2, "tls_init: verify_callback: something wrong with the cert"
				" ... error code is %d (check x509_vfy.h)\n", ctx->error);
			break;
	}
	
	LOG( 2, "tls_init: verify_callback: verify return:%d\n", pre_verify_ok);
	return(pre_verify_ok);
}


static int
passwd_cb(char *buf, int size, int rwflag, void *filename)
{
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
	UI             *ui;
	const char     *prompt;
	
	ui = UI_new();
	if (ui == NULL)
		goto err;

	prompt = UI_construct_prompt(ui, "passphrase", filename);
	UI_add_input_string(ui, prompt, 0, buf, 0, size - 1);
	UI_process(ui);
	UI_free(ui);
	return strlen(buf);

err:
	LOG(L_ERR, "tls: tls_init: passwd_cb: Error in passwd_cb\n");
	if (ui)
		UI_free(ui);
	return 0;
	
#else
	if( des_read_pw_string(buf, size-1, "Enter Private Key password:", 0) ) {
		LOG(L_ERR, "tls: tls_init: passwd_cb: Error in passwd_cb\n");
		return 0;
	}
	return strlen( buf );
	
#endif
}


/*
 * Wrappers around SER shared memory functions
 * (which can be macros)
 */

static void    *
ser_malloc(size_t size)
{
	return shm_malloc(size);
}

static void    *
ser_realloc(void *ptr, size_t size)
{
	return shm_realloc(ptr, size);
}

static void
ser_free(void *ptr)
{
	shm_free(ptr);
}


int
tls_init(struct socket_info *si)
{
	DBG("tls_init: Entered\n");
	
	/*
	 * reuse tcp initialization 
	 */
	if (tcp_init(si) < 0) {
		LOG(L_ERR, "tls_init: Error while initializing TCP part\n");
		goto error;
	}

	si->proto = PROTO_TLS;
	return 0;

  error:
	if (si->socket != -1) {
		close(si->socket);
		si->socket = -1;
	}
	return -1;
}

/*
 * load a certificate from a file 
 * (certificate file can be a chain, starting by the user cert, 
 * and ending in the root CA; if not all needed certs are in this
 * file, they are looked up in the caFile or caPATH (see verify
 * function).
 */
static int
load_certificate(SSL_CTX * ctx, char *filename)
{
	DBG("load_certificate: Entered\n");
	if (!SSL_CTX_use_certificate_chain_file(ctx, filename)) {
		LOG(L_ERR,
			"load_certificate: Unable to load certificate file '%s'\n",
			filename);
		return -1;
	}

	DBG("load_certificate: '%s' successfuly loaded\n", filename);
	return 0;
}


#define NUM_RETRIES 3
/*
 * load a private key from a file 
 */
static int
load_private_key(SSL_CTX * ctx, char *filename)
{
	int idx, ret_pwd;
	DBG("load_private_key: Entered\n");
	
	SSL_CTX_set_default_passwd_cb(ctx, passwd_cb);
	SSL_CTX_set_default_passwd_cb_userdata(ctx, filename);

	for(idx = 0, ret_pwd = 0; idx < NUM_RETRIES; idx++ ) {
		ret_pwd = SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM);
		if ( ret_pwd ) {
			break;
		} else {
			LOG( L_ERR,
				"load_private_key: Unable to load private key file '%s'. \n"
				"Retry (%d left) (check password case)\n",
				filename, (NUM_RETRIES - idx -1) );
			continue;
		}
	}
	
	if( ! ret_pwd ) {
		LOG(L_ERR,
			"load_private_key: Unable to load private key file '%s'\n",
			filename);
		return -1;
	}
	
	if (!SSL_CTX_check_private_key(ctx)) {
		LOG(L_ERR,
			"load_private_key: Key '%s' does not match the public key of the certificate\n",
			filename);
		return -1;
	}
	
	DBG("load_private_key: Key '%s' successfuly loaded\n", filename);
	return 0;
}

/*  
 * Load a caList, to be used to verify the client's certificate.
 * The list is to be stored in a single file, containing all
 * the acceptable root certificates.
 */
static int
load_ca(SSL_CTX * ctx, char *filename)
{
	DBG("load_ca: Entered\n");
	if (!SSL_CTX_load_verify_locations(ctx, filename, 0)) {
		LOG(L_ERR, "load_ca: Unable to load ca '%s'\n", filename);
		return -1;
	}
	
	DBG("load_ca: CA '%s' successfuly loaded\n", filename);
	return 0;
}


/*
 * initialize ssl methods 
 */
static void
init_ssl_methods(void)
{
	DBG("init_methods: Entered\n");
	ssl_methods[TLS_USE_SSLv2_cli - 1] = SSLv2_client_method();
	ssl_methods[TLS_USE_SSLv2_srv - 1] = SSLv2_server_method();
	ssl_methods[TLS_USE_SSLv2 - 1] = SSLv2_method();
	
	ssl_methods[TLS_USE_SSLv3_cli - 1] = SSLv3_client_method();
	ssl_methods[TLS_USE_SSLv3_srv - 1] = SSLv3_server_method();
	ssl_methods[TLS_USE_SSLv3 - 1] = SSLv3_method();
	
	ssl_methods[TLS_USE_TLSv1_cli - 1] = TLSv1_client_method();
	ssl_methods[TLS_USE_TLSv1_srv - 1] = TLSv1_server_method();
	ssl_methods[TLS_USE_TLSv1 - 1] = TLSv1_method();
	
	ssl_methods[TLS_USE_SSLv23_cli - 1] = SSLv23_client_method();
	ssl_methods[TLS_USE_SSLv23_srv - 1] = SSLv23_server_method();
	ssl_methods[TLS_USE_SSLv23 - 1] = SSLv23_method();
}


/*
 * Setup default SSL_CTX (and SSL * ) behavior:
 *     verification, cipherlist, acceptable versions, ...
 */
static int
init_ssl_ctx_behavior( struct tls_domain *d ) {
	int verify_mode;
	if( d->ciphers_list != 0 ) {
		if( SSL_CTX_set_cipher_list(d->ctx, d->ciphers_list) == 0 ) {
			LOG( L_ERR, "init_ssl_ctx_behavior: failure to set SSL context "
				"cipher list '%s'\n", d->ciphers_list);
			return -1;
		} else {
			LOG( L_NOTICE, "init_ssl_ctx_behavior: cipher list set to %s\n",
				d->ciphers_list);
		}
	} else {
		DBG( "init_ssl_ctx_behavior: cipher list null ... setting default\n");
	}

	/* Set a bunch of options: 
	 *     do not accept SSLv2
	 *     no session resumption
	 *     choose cipher according to server's preference's*/

#if OPENSSL_VERSION_NUMBER >= 0x000907000
	SSL_CTX_set_options(d->ctx, 
		SSL_OP_ALL | SSL_OP_NO_SSLv2 |
		SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
		SSL_OP_CIPHER_SERVER_PREFERENCE);
#else
	SSL_CTX_set_options(d->ctx,
		SSL_OP_ALL | SSL_OP_NO_SSLv2 );
#endif

	/* Set verification procedure
	 * The verification can be made null with SSL_VERIFY_NONE, or 
	 * at least easier with SSL_VERIFY_CLIENT_ONCE instead of 
	 * SSL_VERIFY_FAIL_IF_NO_PEER_CERT.
	 * For extra control, instead of 0, we can specify a callback function:
	 *           int (*verify_callback)(int, X509_STORE_CTX *)
	 * Also, depth 2 may be not enough in some scenarios ... though no need
	 * to increase it much further */

	if (d->type & TLS_DOMAIN_SRV) {
		/* Server mode:
		 * SSL_VERIFY_NONE
		 *   the server will not send a client certificate request to the 
		 *   client, so the client  will not send a certificate.
		 * SSL_VERIFY_PEER
		 *   the server sends a client certificate request to the client. 
		 *   The certificate returned (if any) is checked. If the verification 
		 *   process fails, the TLS/SSL handshake is immediately terminated 
		 *   with an alert message containing the reason for the verification 
		 *   failure. The behaviour can be controlled by the additional 
		 *   SSL_VERIFY_FAIL_IF_NO_PEER_CERT and SSL_VERIFY_CLIENT_ONCE flags.
		 * SSL_VERIFY_FAIL_IF_NO_PEER_CERT
		 *   if the client did not return a certificate, the TLS/SSL handshake 
		 *   is immediately terminated with a ``handshake failure'' alert. 
		 *   This flag must be used together with SSL_VERIFY_PEER.
		 * SSL_VERIFY_CLIENT_ONCE
		 *   only request a client certificate on the initial TLS/SSL 
		 *   handshake. Do not ask for a client certificate again in case of 
		 *   a renegotiation. This flag must be used together with 
		 *   SSL_VERIFY_PEER.
		 */

		if( d->verify_cert ) {
			verify_mode = SSL_VERIFY_PEER;
			if( d->require_client_cert ) {
				LOG( L_WARN, "TLS: Client verification activated. Client "
					"certificates are mandatory.\n");
				verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
			} else
				LOG( L_WARN, "TLS: Client verification activated. Client "
					"certificates are NOT mandatory.\n");
		} else {
			verify_mode = SSL_VERIFY_NONE;
			LOG( L_WARN, "TLS: Client verification NOT activated. Weaker "
				"security.\n");
		}
	} else {
		/* Client mode:
		 * SSL_VERIFY_NONE
		 *   if not using an anonymous cipher (by default disabled), the 
		 *   server will send a certificate which will be checked. The result 
		 *   of the certificate verification process can be checked after the 
		 *   TLS/SSL handshake using the SSL_get_verify_result(3) function.
		 *   The handshake will be continued regardless of the verification 
		 *   result.
		 * SSL_VERIFY_PEER
		 *   the server certificate is verified. If the verification process 
		 *   fails, the TLS/SSL handshake is immediately terminated with an 
		 *   alert message containing the reason for the verification failure.
		 *   If no server certificate is sent, because an anonymous cipher is 
		 *   used, SSL_VERIFY_PEER is ignored.
		 * SSL_VERIFY_FAIL_IF_NO_PEER_CERT
		 *   ignored
		 * SSL_VERIFY_CLIENT_ONCE
		 *   ignored
		 */

		if( d->verify_cert ) {
			verify_mode = SSL_VERIFY_PEER;
			LOG( L_WARN, "TLS: Server verification activated.\n");
		} else {
			verify_mode = SSL_VERIFY_NONE;
			LOG( L_WARN, "TLS: Server verification NOT activated. Weaker "
				"security.\n");
		}
	}
	
	SSL_CTX_set_verify( d->ctx, verify_mode, verify_callback);
	SSL_CTX_set_verify_depth( d->ctx, VERIFY_DEPTH_S);

	SSL_CTX_set_session_cache_mode( d->ctx, SSL_SESS_CACHE_SERVER );
	SSL_CTX_set_session_id_context( d->ctx, SER_SSL_SESS_ID, 
		SER_SSL_SESS_ID_LEN );

	return 0;
}


static int check_for_krb()
{
	SSL_CTX *xx;
	int j;

	xx = SSL_CTX_new(ssl_methods[tls_method - 1]);
	if (xx==NULL)
		return -1;

	for( j=0 ; j<M_sk_num(xx->cipher_list) ; j++) {
		SSL_CIPHER *yy = (SSL_CIPHER*)M_sk_value(xx->cipher_list,j);
		if ( yy->id>=SSL3_CK_KRB5_DES_64_CBC_SHA &&
		 yy->id<=SSL3_CK_KRB5_RC4_40_MD5 ) {
			LOG(L_INFO,"INFO:tls:check_for_krb: KRB5 cipher %s found\n",
				yy->name);
			SSL_CTX_free(xx);
			return 1;
		}
	}

	SSL_CTX_free(xx);
	return 0;
}


/*
 * called once from main.c (main process) 
 */
int
init_tls(void)
{
	int i;
#if (OPENSSL_VERSION_NUMBER >= 0x00908000L) && !defined(OPENSSL_NO_COMP)
	STACK_OF(SSL_COMP)* comp_methods;
#endif

	DBG("init_tls: Entered\n");

#if OPENSSL_VERSION_NUMBER < 0x00907000L
	LOG(L_ERR, "WARNING! You are using an old version of OpenSSL (< 0.9.7). "
		"Upgrade!\n");
#endif

	/*
	* this has to be called before any function calling CRYPTO_malloc,
	* CRYPTO_malloc will set allow_customize in openssl to 0 
	*/
	if (!CRYPTO_set_mem_functions(ser_malloc, ser_realloc, ser_free)) {
		LOG(L_ERR,
			"init_tls: Unable to set the memory allocation functions\n");
		return -1;
	}

#if (OPENSSL_VERSION_NUMBER >= 0x00908000L) && !defined(OPENSSL_NO_COMP)
	/* disabling compression */
	LOG(L_ERR, "WARNING:init_tls: disabling compression due ZLIB problems\n");
	comp_methods = SSL_COMP_get_compression_methods();
	if (comp_methods==0) {
		LOG(L_ERR, "ERRRO:init_tls: null openssl compression methods\n");
		return -1;
	}
	sk_SSL_COMP_zero(comp_methods);
#endif

	SSL_library_init();
	SSL_load_error_strings();
	init_ssl_methods();

	i = check_for_krb();
	if (i==-1) {
		LOG(L_ERR, "ERROR:init_tls: kerberos check failed\n");
		return -1;
	}

	if ( ( i ^ 
#ifndef OPENSSL_NO_KRB5
	1
#else
	0
#endif
	)!=0 ) {
		LOG(L_ERR, "ERROR:init_tls: compiled agaist an openssl with %s"
			"kerberos, but run with one with %skerberos\n",
			(i==1)?"":"no ",(i!=1)?"no ":"");
		return -1;
	}

	/*
	 * now initialize tls default domains 
	 */
	if ( (i=init_tls_domains(tls_default_server_domain)) ) {
		return i;
	}
	if ( (i=init_tls_domains(tls_default_client_domain)) ) {
		return i;
	}
	/*
	 * now initialize tls virtual domains 
	 */
	if ( (i=init_tls_domains(tls_server_domains)) ) {
		return i;
	}
	if ( (i=init_tls_domains(tls_client_domains)) ) {
		return i;
	}
	/*
	 * we are all set 
	 */
	return 0;
}

/*
 * initialize tls virtual domains
 */
int
init_tls_domains(struct tls_domain *d)
{
	struct tls_domain *dom;

	dom = d;
	while (d) {
		if (d->name.len) {
			LOG(L_INFO, "init_tls_domains: Processing TLS domain '%.*s'\n",
				d->name.len, ZSW(d->name.s));
		} else {
			LOG(L_INFO, "init_tls_domains: Processing TLS domain [%s:%d]\n",
				ip_addr2a(&d->addr), d->port);
		}

		/*
		* set method 
		*/
		if (d->method == TLS_METHOD_UNSPEC) {
			DBG("init_tls_domains: No method for tls[%s:%d], using default\n",
				ip_addr2a(&d->addr), d->port);
			d->method = tls_method;
		}
	
		/*
		* create context 
		*/
		d->ctx = SSL_CTX_new(ssl_methods[d->method - 1]);
		if (d->ctx == NULL) {
			LOG(L_ERR, "init_tls_domains: Cannot create ssl context for "
				"tls[%s:%d]\n", ip_addr2a(&d->addr), d->port);
			return -1;
		}
		if (init_ssl_ctx_behavior( d ) < 0)
			return -1;

		/*
		* load certificate 
		*/
		if (!d->cert_file) {
			LOG(L_NOTICE, "init_tls_domains: No certificate for tls[%s:%d] "
				"defined, using default '%s'\n", ip_addr2a(&d->addr), d->port,
				tls_cert_file);
			d->cert_file = tls_cert_file;
		}
		if (load_certificate(d->ctx, d->cert_file) < 0)
			return -1;
	
		/*
		* load ca 
		*/
		if (!d->ca_file) {
			LOG(L_NOTICE, "init_tls_domains: No CA for tls[%s:%d] defined, "
				"using default '%s'\n", ip_addr2a(&d->addr), d->port,
				tls_ca_file);
			d->ca_file = tls_ca_file;
		}
		if (d->ca_file && load_ca(d->ctx, d->ca_file) < 0)
			return -1;
		d = d->next;
	}

	/*
	* load all private keys as the last step (may prompt for password) 
	*/
	d = dom;
	while (d) {
		if (!d->pkey_file) {
			LOG(L_NOTICE, "init_tls_domain: No private key for tls[%s:%d] "
				"defined, using default '%s'\n", ip_addr2a(&d->addr),
				d->port, tls_pkey_file);
			d->pkey_file = tls_pkey_file;
		}
		if (load_private_key(d->ctx, d->pkey_file) < 0)
			return -1;
		d = d->next;
	}
	return 0;
}

/*
 * called from main.c when openser exits (main process) 
 */
void
destroy_tls(void)
{
	struct tls_domain *d;
	DBG("destroy_tls: Entered\n");
	
	d = tls_server_domains;
	while (d) {
		if (d->ctx)
			SSL_CTX_free(d->ctx);
		d = d->next;
	}
	d = tls_client_domains;
	while (d) {
		if (d->ctx)
			SSL_CTX_free(d->ctx);
		d = d->next;
	}
	if (tls_default_server_domain && tls_default_server_domain->ctx) {
		SSL_CTX_free(tls_default_server_domain->ctx);
	}
	if (tls_default_client_domain && tls_default_client_domain->ctx) {
		SSL_CTX_free(tls_default_client_domain->ctx);
	}
	tls_free_domains();

	/* library destroy */
	ERR_free_strings();
	/*SSL_free_comp_methods(); - this function is not on std. openssl*/
	EVP_cleanup();
	CRYPTO_cleanup_all_ex_data();
}

/*
 * called once from main.c (main process) before
 * parsing the configuration
 */
int pre_init_tls(void)
{
	DBG("pre_init_tls: Entered\n");

	tls_default_client_domain = tls_new_domain(TLS_DOMAIN_DEF|TLS_DOMAIN_CLI);
	if (tls_default_client_domain==0) {
		LOG(L_ERR, "ERROR:tls:pre_init_tls: failed to initialize "
			"tls_default_client_domain\n");
		return -1;
	}
	tls_default_client_domain->addr.af = AF_INET;

	tls_default_server_domain = tls_new_domain(TLS_DOMAIN_DEF|TLS_DOMAIN_SRV);
	if (tls_default_server_domain==0) {
		LOG(L_ERR, "ERROR:tls:pre_init_tls: failed to initialize "
			"tls_default_server_domain\n");
		return -1;
	}
	tls_default_server_domain->addr.af = AF_INET;

	return 0;
}

