diff extern/libircclient/src/ssl.c @ 0:1158cffe5a5e

Initial import
author David Demelier <markand@malikania.fr>
date Mon, 08 Feb 2016 16:43:14 +0100
parents
children f67540addf5a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extern/libircclient/src/ssl.c	Mon Feb 08 16:43:14 2016 +0100
@@ -0,0 +1,390 @@
+/* 
+ * Copyright (C) 2004-2012 George Yunaev gyunaev@ulduzsoft.com
+ *
+ * This library is free software; you can redistribute it and/or modify it 
+ * under the terms of the GNU Lesser General Public License as published by 
+ * the Free Software Foundation; either version 3 of the License, or (at your 
+ * option) any later version.
+ *
+ * This library 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 Lesser General Public 
+ * License for more details.
+ */
+
+
+#if defined (ENABLE_SSL)
+
+// Nonzero if OpenSSL has been initialized
+static SSL_CTX * ssl_context = 0;
+
+#if defined (_WIN32)
+#include <windows.h>
+// This array will store all of the mutexes available to OpenSSL
+static CRITICAL_SECTION * mutex_buf = 0;
+
+// OpenSSL callback to utilize static locks
+static void cb_openssl_locking_function( int mode, int n, const char * file, int line )
+{
+    if ( mode & CRYPTO_LOCK)
+        EnterCriticalSection( &mutex_buf[n] );
+    else
+        LeaveCriticalSection( &mutex_buf[n] );
+}
+
+// OpenSSL callback to get the thread ID
+static unsigned long cb_openssl_id_function(void)
+{
+    return ((unsigned long) GetCurrentThreadId() );
+}
+
+static int alloc_mutexes( unsigned int total )
+{
+	unsigned int i;
+	
+	// Enable thread safety in OpenSSL
+	mutex_buf = (CRITICAL_SECTION*) malloc( total * sizeof(CRITICAL_SECTION) );
+
+	if ( !mutex_buf )
+		return -1;
+
+	for ( i = 0;  i < total;  i++)
+		InitializeCriticalSection( &(mutex_buf[i]) );
+	
+	return 0;
+}
+
+
+#else
+
+// This array will store all of the mutexes available to OpenSSL
+static pthread_mutex_t * mutex_buf = 0;
+
+// OpenSSL callback to utilize static locks
+static void cb_openssl_locking_function( int mode, int n, const char * file, int line )
+{
+    (void)file;
+    (void)line;
+
+    if ( mode & CRYPTO_LOCK)
+        pthread_mutex_lock( &mutex_buf[n] );
+    else
+        pthread_mutex_unlock( &mutex_buf[n] );
+}
+
+// OpenSSL callback to get the thread ID
+static unsigned long cb_openssl_id_function()
+{
+    return ((unsigned long) pthread_self() );
+}
+
+static int alloc_mutexes( unsigned int total )
+{
+	unsigned i;
+	
+	// Enable thread safety in OpenSSL
+	mutex_buf = (pthread_mutex_t*) malloc( total * sizeof(pthread_mutex_t) );
+
+	if ( !mutex_buf )
+		return -1;
+
+	for ( i = 0;  i < total;  i++)
+		pthread_mutex_init( &(mutex_buf[i]), 0 );
+	
+	return 0;
+}
+
+#endif
+
+static int ssl_init_context( irc_session_t * session )
+{
+	// Load the strings and init the library
+	SSL_load_error_strings();
+
+	// Enable thread safety in OpenSSL
+	if ( alloc_mutexes( CRYPTO_num_locks() ) )
+		return LIBIRC_ERR_NOMEM;
+
+	// Register our callbacks
+	CRYPTO_set_id_callback( cb_openssl_id_function );
+	CRYPTO_set_locking_callback( cb_openssl_locking_function );
+
+	// Init it
+	if ( !SSL_library_init() )
+		return LIBIRC_ERR_SSL_INIT_FAILED;
+
+	if ( RAND_status() == 0 )
+		return LIBIRC_ERR_SSL_INIT_FAILED;
+
+	// Create an SSL context; currently a single context is used for all connections
+	ssl_context = SSL_CTX_new( SSLv23_method() );
+
+	if ( !ssl_context )
+		return LIBIRC_ERR_SSL_INIT_FAILED;
+
+	// Disable SSLv2 as it is unsecure
+	if ( (SSL_CTX_set_options( ssl_context, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2) == 0 )
+		return LIBIRC_ERR_SSL_INIT_FAILED;
+
+	// Enable only strong ciphers
+	if ( SSL_CTX_set_cipher_list( ssl_context, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" ) != 1 )
+		return LIBIRC_ERR_SSL_INIT_FAILED;
+
+	// Set the verification
+	if ( session->options & LIBIRC_OPTION_SSL_NO_VERIFY )
+		SSL_CTX_set_verify( ssl_context, SSL_VERIFY_NONE, 0 );
+	else
+		SSL_CTX_set_verify( ssl_context, SSL_VERIFY_PEER, 0 );
+	
+	// Disable session caching
+	SSL_CTX_set_session_cache_mode( ssl_context, SSL_SESS_CACHE_OFF );
+
+	// Enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER so we can move the buffer during sending
+	SSL_CTX_set_mode( ssl_context, SSL_CTX_get_mode(ssl_context) | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE );
+	
+	return 0;
+}
+
+
+#if defined (_WIN32)
+	#define SSLINIT_LOCK_MUTEX(a)		WaitForSingleObject( a, INFINITE )
+	#define SSLINIT_UNLOCK_MUTEX(a)		ReleaseMutex( a )
+#else
+	#define SSLINIT_LOCK_MUTEX(a)		pthread_mutex_lock( &a )
+	#define SSLINIT_UNLOCK_MUTEX(a)		pthread_mutex_unlock( &a )
+#endif
+
+// Initializes the SSL context. Must be called after the socket is created.
+static int ssl_init( irc_session_t * session )
+{
+	static int ssl_context_initialized = 0;
+	
+#if defined (_WIN32)
+	static HANDLE initmutex = 0;
+	
+	// First time run? Create the mutex
+	if ( initmutex == 0 )
+	{ 
+		HANDLE m = CreateMutex( 0, FALSE, 0 );
+
+		// Now we check if the mutex has already been created by another thread performing the init concurrently.
+		// If it was, we close our mutex and use the original one. This could be done synchronously by using the
+		// InterlockedCompareExchangePointer function.
+		if ( InterlockedCompareExchangePointer( &m, m, 0 ) != 0 )
+			CloseHandle( m );
+	}
+#else
+	static pthread_mutex_t initmutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+	
+	// This initialization needs to be performed only once. The problem is that it is called from
+	// irc_connect() and this function may be called simultaneously from different threads. So we have
+	// to use mutex on Linux because it allows static mutex initialization. Windows doesn't, so here 
+	// we do the sabre dance around it.
+	SSLINIT_LOCK_MUTEX( initmutex );
+
+	if ( ssl_context_initialized == 0 )
+	{
+		int res = ssl_init_context( session );
+		
+		if ( res )
+		{
+			SSLINIT_UNLOCK_MUTEX( initmutex );
+			return res;
+		}
+		
+		ssl_context_initialized = 1;
+	}
+	
+	SSLINIT_UNLOCK_MUTEX( initmutex );
+	
+	// Get the SSL context
+	session->ssl = SSL_new( ssl_context );
+
+	if ( !session->ssl )
+		return LIBIRC_ERR_SSL_INIT_FAILED;
+
+	// Let OpenSSL use our socket
+	if ( SSL_set_fd( session->ssl, session->sock) != 1 )
+		return LIBIRC_ERR_SSL_INIT_FAILED;
+	
+	// Since we're connecting on our own, tell openssl about it
+	SSL_set_connect_state( session->ssl );
+
+	return 0;
+}
+
+static void ssl_handle_error( irc_session_t * session, int ssl_error )
+{
+	if ( ERR_GET_LIB(ssl_error) == ERR_LIB_SSL )
+	{
+		if ( ERR_GET_REASON(ssl_error) == SSL_R_CERTIFICATE_VERIFY_FAILED )
+		{
+			session->lasterror = LIBIRC_ERR_SSL_CERT_VERIFY_FAILED;
+			return;
+		}
+		
+		if ( ERR_GET_REASON(ssl_error) == SSL_R_UNKNOWN_PROTOCOL )
+		{
+			session->lasterror = LIBIRC_ERR_CONNECT_SSL_FAILED;
+			return;
+		}
+	}
+
+#if defined (ENABLE_DEBUG)
+	if ( IS_DEBUG_ENABLED(session) )
+		fprintf (stderr, "[DEBUG] SSL error: %s\n\t(%d, %d)\n", 
+			 ERR_error_string( ssl_error, NULL),  ERR_GET_LIB( ssl_error), ERR_GET_REASON(ssl_error) );
+#endif
+}
+
+static int ssl_recv( irc_session_t * session )
+{
+	int count;
+	unsigned int amount = (sizeof (session->incoming_buf) - 1) - session->incoming_offset;
+	
+	ERR_clear_error();
+
+	// Read up to m_bufferLength bytes
+	count = SSL_read( session->ssl, session->incoming_buf + session->incoming_offset, amount );
+
+    if ( count > 0 )
+		return count;
+	else if ( count == 0 )
+		return -1; // remote connection closed
+	else
+	{
+		int ssl_error = SSL_get_error( session->ssl, count );
+		
+		// Handle SSL error since not all of them are actually errors
+        switch ( ssl_error )
+        {
+            case SSL_ERROR_WANT_READ:
+                // This is not really an error. We received something, but
+                // OpenSSL gave nothing to us because all it read was
+                // internal data. Repeat the same read.
+				return 0;
+
+            case SSL_ERROR_WANT_WRITE:
+                // This is not really an error. We received something, but
+                // now OpenSSL needs to send the data before returning any
+                // data to us (like negotiations). This means we'd need
+                // to wait for WRITE event, but call SSL_read() again.
+                session->flags |= SESSIONFL_SSL_READ_WANTS_WRITE;
+				return 0;
+		}
+
+		// This is an SSL error, handle it
+		ssl_handle_error( session, ERR_get_error() ); 
+	}
+	
+	return -1;
+}
+
+
+static int ssl_send( irc_session_t * session )
+{
+	int count;
+    ERR_clear_error();
+
+	count = SSL_write( session->ssl, session->outgoing_buf, session->outgoing_offset );
+
+    if ( count > 0 )
+		return count;
+    else if ( count == 0 )
+		return -1;
+    else
+    {
+		int ssl_error = SSL_get_error( session->ssl, count );
+		
+        switch ( ssl_error )
+        {
+            case SSL_ERROR_WANT_READ:
+                // This is not really an error. We sent some internal OpenSSL data,
+                // but now it needs to read more data before it can send anything.
+                // Thus we wait for READ event, but will call SSL_write() again.
+                session->flags |= SESSIONFL_SSL_WRITE_WANTS_READ;
+				return 0;
+
+           case SSL_ERROR_WANT_WRITE:
+                // This is not really an error. We sent some data, but now OpenSSL
+                // wants to send some internal data before sending ours.
+                // Repeat the same write.
+				return 0;
+        }
+        
+		// This is an SSL error, handle it
+		ssl_handle_error( session, ERR_get_error() ); 
+    }
+
+	return -1;
+}
+
+#endif
+
+
+// Handles both SSL and non-SSL reads.
+// Returns -1 in case there is an error and socket should be closed/connection terminated
+// Returns 0 in case there is a temporary error and the call should be retried (SSL_WANTS_WRITE case)
+// Returns a positive number if we actually read something
+static int session_socket_read( irc_session_t * session )
+{
+	int length;
+
+#if defined (ENABLE_SSL)
+	if ( session->ssl )
+	{
+		// Yes, I know this is tricky
+		if ( session->flags & SESSIONFL_SSL_READ_WANTS_WRITE )
+		{
+			session->flags &= ~SESSIONFL_SSL_READ_WANTS_WRITE;
+			ssl_send( session );
+			return 0;
+		}
+		
+		return ssl_recv( session );
+	}
+#endif
+	
+	length = socket_recv( &session->sock, 
+						session->incoming_buf + session->incoming_offset, 
+					    (sizeof (session->incoming_buf) - 1) - session->incoming_offset );
+	
+	// There is no "retry" errors for regular sockets
+	if ( length <= 0 )
+		return -1;
+	
+	return length;
+}
+
+// Handles both SSL and non-SSL writes.
+// Returns -1 in case there is an error and socket should be closed/connection terminated
+// Returns 0 in case there is a temporary error and the call should be retried (SSL_WANTS_WRITE case)
+// Returns a positive number if we actually sent something
+static int session_socket_write( irc_session_t * session )
+{
+	int length;
+
+#if defined (ENABLE_SSL)
+	if ( session->ssl )
+	{
+		// Yep
+		if ( session->flags & SESSIONFL_SSL_WRITE_WANTS_READ )
+		{
+			session->flags &= ~SESSIONFL_SSL_WRITE_WANTS_READ;
+			ssl_recv( session );
+			return 0;
+		}
+		
+		return ssl_send( session );
+	}
+#endif
+	
+	length = socket_send (&session->sock, session->outgoing_buf, session->outgoing_offset);
+	
+	// There is no "retry" errors for regular sockets
+	if ( length <= 0 )
+		return -1;
+	
+	return length;
+}