//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002 Vincent Richard <vincent@vmime.org>
//
// This program 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 3 of
// the License, or (at your option) any later version.
//
// 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. 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.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library.  Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//

#include "vmime/config.hpp"


#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL


#include "vmime/net/tls/openssl/OpenSSLInitializer.hpp"

#include "vmime/platform.hpp"

#include <openssl/opensslv.h>

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
#	define OPENSSL_API_COMPAT 0x10100000L
#endif

#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/crypto.h>
#include <openssl/err.h>

#if OPENSSL_VERSION_NUMBER >= 0x0907000L
#	include <openssl/conf.h>
#endif

#if OPENSSL_VERSION_NUMBER < 0x10100000L
#	include "vmime/utility/sync/autoLock.hpp"
#	include "vmime/utility/sync/criticalSection.hpp"
#endif


// OpenSSL locking callbacks for multithreading support (< v1.1 only)
#if OPENSSL_VERSION_NUMBER < 0x10100000L

namespace {

vmime::shared_ptr <vmime::utility::sync::criticalSection >* g_openSSLMutexes = NULL;

extern "C" void VMime_OpenSSLCallback_lock(int mode, int n, const char* /* file */, int /* line */) {

	if (mode & CRYPTO_LOCK) {
		g_openSSLMutexes[n]->lock();
	} else {
		g_openSSLMutexes[n]->unlock();
	}
}

extern "C" unsigned long VMime_OpenSSLCallback_id() {

	return vmime::platform::getHandler()->getThreadId();
}

}

#endif


namespace vmime {
namespace net {
namespace tls {


OpenSSLInitializer::autoInitializer::autoInitializer() {

	// The construction of this unique 'oneTimeInitializer' object will be triggered
	// by the 'autoInitializer' objects from the other translation units
	static OpenSSLInitializer::oneTimeInitializer oneTimeInitializer;
}


OpenSSLInitializer::autoInitializer::~autoInitializer() {

}


OpenSSLInitializer::oneTimeInitializer::oneTimeInitializer() {

	initialize();
}


OpenSSLInitializer::oneTimeInitializer::~oneTimeInitializer() {

	uninitialize();
}


// static
void OpenSSLInitializer::initialize() {

#if OPENSSL_VERSION_NUMBER >= 0x0907000L && OPENSSL_VERSION_NUMBER < 0x10100000L
	OPENSSL_config(NULL);
#endif

#if OPENSSL_VERSION_NUMBER >=0x10100000L
	OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
		OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_ENGINE_OPENSSL | OPENSSL_INIT_ENGINE_ALL_BUILTIN , NULL);
	OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
#endif

#if OPENSSL_VERSION_NUMBER < 0x10100000L
	SSL_load_error_strings();
	SSL_library_init();
	OpenSSL_add_all_algorithms();

	int numMutexes = CRYPTO_num_locks();
	g_openSSLMutexes = new shared_ptr <vmime::utility::sync::criticalSection>[numMutexes];

	for (int i = 0 ; i < numMutexes ; ++i) {
		g_openSSLMutexes[i] = vmime::platform::getHandler()->createCriticalSection();
	}

	CRYPTO_set_locking_callback(VMime_OpenSSLCallback_lock);
	CRYPTO_set_id_callback(VMime_OpenSSLCallback_id);
#endif

	// Seed the RNG, in case /dev/urandom is not available. Explicitely calling
	// RAND_seed() even though /dev/urandom is available is harmless.
	enum {
		SEEDSIZE = 256
	};

	unsigned char seed[SEEDSIZE];
	vmime::platform::getHandler()->generateRandomBytes(seed, SEEDSIZE);
	RAND_seed(seed, SEEDSIZE);
}


// static
void OpenSSLInitializer::uninitialize() {

#if OPENSSL_VERSION_NUMBER < 0x10100000L
	EVP_cleanup();
	ERR_free_strings();

	CRYPTO_set_locking_callback(NULL);
	CRYPTO_set_id_callback(NULL);

	delete [] g_openSSLMutexes;
	g_openSSLMutexes = NULL;
#endif

}


} // tls
} // net
} // vmime


#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL

