/* * 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 // 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 void cb_openssl_id_function( CRYPTO_THREADID * id ) { CRYPTO_THREADID_set_pointer( id, 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_THREADID_set_callback( cb_openssl_id_function ); CRYPTO_set_locking_callback( cb_openssl_locking_function ); // Init it #if OPENSSL_VERSION_NUMBER < 0x10100000L SSL_library_init(); #else OPENSSL_init_ssl(0, NULL); #endif 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; }