diff options
Diffstat (limited to 'lib/ssl_gnutls.c')
-rw-r--r-- | lib/ssl_gnutls.c | 81 |
1 files changed, 67 insertions, 14 deletions
diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c index 93601ba6..b698e630 100644 --- a/lib/ssl_gnutls.c +++ b/lib/ssl_gnutls.c @@ -1,7 +1,7 @@ /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * - * Copyright 2002-2011 Wilmer van der Gaast and others * + * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* SSL module - GnuTLS version */ @@ -37,7 +37,7 @@ int ssl_errno = 0; static gboolean initialized = FALSE; -gnutls_certificate_credentials xcred; +gnutls_certificate_credentials_t xcred; #include <limits.h> @@ -59,9 +59,11 @@ struct scd char *hostname; gboolean verify; - gnutls_session session; + gnutls_session_t session; }; +static GHashTable *session_cache; + static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ); static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ); static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ); @@ -84,8 +86,10 @@ void ssl_init( void ) { gnutls_certificate_set_x509_trust_file( xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM ); - /* Not needed in GnuTLS 2.11+ but we support older versions for now. */ - gnutls_certificate_set_verify_flags( xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT ); + /* Not needed in GnuTLS 2.11+ (enabled by default there) so + don't do it (resets possible other defaults). */ + if( !gnutls_check_version( "2.11" ) ) + gnutls_certificate_set_verify_flags( xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT ); } initialized = TRUE; @@ -94,6 +98,8 @@ void ssl_init( void ) gnutls_global_set_log_level( 3 ); */ + session_cache = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free ); + atexit( ssl_deinit ); } @@ -101,18 +107,20 @@ static void ssl_deinit( void ) { gnutls_global_deinit(); gnutls_certificate_free_credentials( xcred ); + g_hash_table_destroy( session_cache ); + session_cache = NULL; } void *ssl_connect( char *host, int port, gboolean verify, ssl_input_function func, gpointer data ) { struct scd *conn = g_new0( struct scd, 1 ); - conn->fd = proxy_connect( host, port, ssl_connected, conn ); conn->func = func; conn->data = data; conn->inpa = -1; conn->hostname = g_strdup( host ); conn->verify = verify && global.conf->cafile; + conn->fd = proxy_connect( host, port, ssl_connected, conn ); if( conn->fd < 0 ) { @@ -131,7 +139,7 @@ void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function conn->func = func; conn->data = data; conn->inpa = -1; - conn->hostname = hostname; + conn->hostname = g_strdup( hostname ); /* For now, SSL verification is globally enabled by setting the cafile setting in bitlbee.conf. Commented out by default because probably @@ -168,9 +176,9 @@ static int verify_certificate_callback( gnutls_session_t session ) int gnutlsret; int verifyret = 0; gnutls_x509_crt_t cert; - const char *hostname; + struct scd *conn; - hostname = gnutls_session_get_ptr( session ); + conn = gnutls_session_get_ptr( session ); gnutlsret = gnutls_certificate_verify_peers2( session, &status ); if( gnutlsret < 0 ) @@ -208,7 +216,7 @@ static int verify_certificate_callback( gnutls_session_t session ) if( cert_list == NULL || gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER ) < 0 ) return VERIFY_CERT_ERROR; - if( !gnutls_x509_crt_check_hostname( cert, hostname ) ) + if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) ) { verifyret |= VERIFY_CERT_INVALID; verifyret |= VERIFY_CERT_WRONG_HOSTNAME; @@ -219,6 +227,45 @@ static int verify_certificate_callback( gnutls_session_t session ) return verifyret; } +struct ssl_session +{ + size_t size; + char data[]; +}; + +static void ssl_cache_add( struct scd *conn ) +{ + size_t data_size; + struct ssl_session *data; + char *hostname; + + if( !conn->hostname || + gnutls_session_get_data( conn->session, NULL, &data_size ) != 0 ) + return; + + data = g_malloc( sizeof( struct ssl_session ) + data_size ); + if( gnutls_session_get_data( conn->session, data->data, &data->size ) != 0 ) + { + g_free( data ); + return; + } + + hostname = g_strdup( conn->hostname ); + g_hash_table_insert( session_cache, hostname, data ); +} + +static void ssl_cache_resume( struct scd *conn ) +{ + struct ssl_session *data; + + if( conn->hostname && + ( data = g_hash_table_lookup( session_cache, conn->hostname ) ) ) + { + gnutls_session_set_data( conn->session, data->data, data->size ); + g_hash_table_remove( session_cache, conn->hostname ); + } +} + char *ssl_verify_strerror( int code ) { GString *ret = g_string_new( "" ); @@ -264,16 +311,20 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con ssl_init(); gnutls_init( &conn->session, GNUTLS_CLIENT ); - if( conn->verify ) - gnutls_session_set_ptr( conn->session, (void *) conn->hostname ); + gnutls_session_set_ptr( conn->session, (void *) conn ); #if GNUTLS_VERSION_NUMBER < 0x020c00 gnutls_transport_set_lowat( conn->session, 0 ); #endif gnutls_set_default_priority( conn->session ); gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, xcred ); + if( conn->hostname && !isdigit( conn->hostname[0] ) ) + gnutls_server_name_set( conn->session, GNUTLS_NAME_DNS, + conn->hostname, strlen( conn->hostname ) ); sock_make_nonblocking( conn->fd ); - gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd ); + gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd ); + + ssl_cache_resume( conn ); return ssl_handshake( data, source, cond ); } @@ -315,7 +366,8 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con { /* For now we can't handle non-blocking perfectly everywhere... */ sock_make_blocking( conn->fd ); - + + ssl_cache_add( conn ); conn->established = TRUE; conn->func( conn->data, 0, conn, cond ); } @@ -399,6 +451,7 @@ void ssl_disconnect( void *conn_ ) if( conn->session ) gnutls_deinit( conn->session ); + g_free( conn->hostname ); g_free( conn ); } |