text: /**
   * WinPR: Windows Portable Runtime
   * OpenSSL Library Initialization
   *
   * Copyright 2014 Thincast Technologies GmbH
   * Copyright 2014 Norbert Federa <norbert.federa@thincast.com>
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  #include <winpr/crt.h>
  #include <winpr/synch.h>
  #include <winpr/ssl.h>
  #include <winpr/thread.h>
  
  #include <openssl/ssl.h>
  #include <openssl/err.h>
  
  #include "../log.h"
  #define TAG WINPR_TAG("utils.ssl")
  
  static int g_winpr_openssl_num_locks = 0;
  static HANDLE* g_winpr_openssl_locks = NULL;
  static BOOL g_winpr_openssl_initialized_by_winpr = FALSE;
  
  struct CRYPTO_dynlock_value
  {
  	HANDLE mutex;
  };
  
  
  #if (OPENSSL_VERSION_NUMBER < 0x10000000L)
  static unsigned long _winpr_openssl_id(void)
  {
  	return (unsigned long)GetCurrentThreadId();
  }
  #endif
  
  static void _winpr_openssl_locking(int mode, int type, const char* file, int line)
  {
  	if (mode & CRYPTO_LOCK)
  	{
  		WaitForSingleObject(g_winpr_openssl_locks[type], INFINITE);
  	}
  	else
  	{
  		ReleaseMutex(g_winpr_openssl_locks[type]);
  	}
  }
  
  static struct CRYPTO_dynlock_value* _winpr_openssl_dynlock_create(const char* file, int line)
  {
  	struct CRYPTO_dynlock_value* dynlock = (struct CRYPTO_dynlock_value*)
  										   malloc(sizeof(struct CRYPTO_dynlock_value));
  
  	if (dynlock)
  	{
  		dynlock->mutex = CreateMutex(NULL, FALSE, NULL);
  	}
  
  	return dynlock;
  }
  
  static void _winpr_openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value* dynlock, const char* file, int line)
  {
  	if (mode & CRYPTO_LOCK)
  	{
  		WaitForSingleObject(dynlock->mutex, INFINITE);
  	}
  	else
  	{
  		ReleaseMutex(dynlock->mutex);
  	}
  }
  
  static void _winpr_openssl_dynlock_destroy(struct CRYPTO_dynlock_value* dynlock, const char* file, int line)
  {
  	CloseHandle(dynlock->mutex);
  	free(dynlock);
  }
  
  static BOOL _winpr_openssl_initialize_locking(void)
  {
  	int i, count;
  
  	/* OpenSSL static locking */
  
  	if (CRYPTO_get_locking_callback())
  	{
  		WLog_WARN(TAG, "OpenSSL static locking callback is already set");
  	}
  	else
  	{
  		if ((count = CRYPTO_num_locks()) > 0)
  		{
  			HANDLE* locks;
  
  			if (!(locks = calloc(count, sizeof(HANDLE))))
  			{
  				WLog_ERR(TAG, "error allocating lock table");
  				return FALSE;
  			}
  
  			for (i = 0; i < count; i++)
  			{
  				if (!(locks[i] = CreateMutex(NULL, FALSE, NULL)))
  				{
  					WLog_ERR(TAG, "error creating lock #%d", i);
  
  					while (i--)
  					{
  						if (locks[i])
  							CloseHandle(locks[i]);
  					}
  
  					free(locks);
  					return FALSE;
  				}
  			}
  
  			g_winpr_openssl_locks = locks;
  			g_winpr_openssl_num_locks = count;
  			CRYPTO_set_locking_callback(_winpr_openssl_locking);
  		}
  	}
  
  	/* OpenSSL dynamic locking */
  
  	if (CRYPTO_get_dynlock_create_callback() ||
  			CRYPTO_get_dynlock_lock_callback()   ||
  			CRYPTO_get_dynlock_destroy_callback())
  	{
  		WLog_WARN(TAG, "dynamic locking callbacks are already set");
  	}
  	else
  	{
  		CRYPTO_set_dynlock_create_callback(_winpr_openssl_dynlock_create);
  		CRYPTO_set_dynlock_lock_callback(_winpr_openssl_dynlock_lock);
  		CRYPTO_set_dynlock_destroy_callback(_winpr_openssl_dynlock_destroy);
  	}
  
  	/* Use the deprecated CRYPTO_get_id_callback() if building against OpenSSL < 1.0.0 */
  #if (OPENSSL_VERSION_NUMBER < 0x10000000L)
  
  	if (CRYPTO_get_id_callback())
  	{
  		WLog_WARN(TAG, "OpenSSL id_callback is already set");
  	}
  	else
  	{
  		CRYPTO_set_id_callback(_winpr_openssl_id);
  	}
  
  #endif
  	return TRUE;
  }
  
  static BOOL _winpr_openssl_cleanup_locking(void)
  {
  	/* undo our static locking modifications */
  	if (CRYPTO_get_locking_callback() == _winpr_openssl_locking)
  	{
  		int i;
  		CRYPTO_set_locking_callback(NULL);
  
  		for (i = 0; i < g_winpr_openssl_num_locks; i++)
  		{
  			CloseHandle(g_winpr_openssl_locks[i]);
  		}
  
  		g_winpr_openssl_num_locks = 0;
  		free(g_winpr_openssl_locks);
  		g_winpr_openssl_locks = NULL;
  	}
  
  	/* unset our dynamic locking callbacks */
  
  	if (CRYPTO_get_dynlock_create_callback() == _winpr_openssl_dynlock_create)
  	{
  		CRYPTO_set_dynlock_create_callback(NULL);
  	}
  
  	if (CRYPTO_get_dynlock_lock_callback() == _winpr_openssl_dynlock_lock)
  	{
  		CRYPTO_set_dynlock_lock_callback(NULL);
  	}
  
  	if (CRYPTO_get_dynlock_destroy_callback() == _winpr_openssl_dynlock_destroy)
  	{
  		CRYPTO_set_dynlock_destroy_callback(NULL);
  	}
  
  #if (OPENSSL_VERSION_NUMBER < 0x10000000L)
  
  	if (CRYPTO_get_id_callback() == _winpr_openssl_id)
  	{
  		CRYPTO_set_id_callback(NULL);
  	}
  
  #endif
  	return TRUE;
  }
  
  static BOOL CALLBACK _winpr_openssl_initialize(PINIT_ONCE once, PVOID param, PVOID* context)
  {
  	DWORD flags = param ? *(PDWORD)param : WINPR_SSL_INIT_DEFAULT;
  
  	if (flags & WINPR_SSL_INIT_ALREADY_INITIALIZED)
  	{
  		return TRUE;
  	}
  
  	if (flags & WINPR_SSL_INIT_ENABLE_LOCKING)
  	{
  		if (!_winpr_openssl_initialize_locking())
  		{
  			return FALSE;
  		}
  	}
  
  	/* SSL_load_error_strings() is void */
  	SSL_load_error_strings();
  	/* SSL_library_init() always returns "1" */
  	SSL_library_init();
  	g_winpr_openssl_initialized_by_winpr = TRUE;
  	return TRUE;
  }
  
  
  /* exported functions */
  
  BOOL winpr_InitializeSSL(DWORD flags)
  {
  	static INIT_ONCE once = INIT_ONCE_STATIC_INIT;
  	return InitOnceExecuteOnce(&once, _winpr_openssl_initialize, &flags, NULL);
  }
  
  BOOL winpr_CleanupSSL(DWORD flags)
  {
  	if (flags & WINPR_SSL_CLEANUP_GLOBAL)
  	{
  		if (!g_winpr_openssl_initialized_by_winpr)
  		{
  			WLog_WARN(TAG, "ssl was not initialized by winpr");
  			return FALSE;
  		}
  
  		g_winpr_openssl_initialized_by_winpr = FALSE;
  		_winpr_openssl_cleanup_locking();
  		CRYPTO_cleanup_all_ex_data();
  		ERR_free_strings();
  		EVP_cleanup();
  		flags |= WINPR_SSL_CLEANUP_THREAD;
  	}
  
  	if (flags & WINPR_SSL_CLEANUP_THREAD)
  	{
  #if (OPENSSL_VERSION_NUMBER < 0x10000000L)
  		ERR_remove_state(0);
  #else
  		ERR_remove_thread_state(NULL);
  #endif
  	}
  
  	return TRUE;
  }
  