diff options
| -rw-r--r-- | conf.c | 6 | ||||
| -rw-r--r-- | conf.h | 1 | ||||
| -rw-r--r-- | lib/http_client.c | 5 | ||||
| -rw-r--r-- | lib/ssl_bogus.c | 2 | ||||
| -rw-r--r-- | lib/ssl_client.h | 15 | ||||
| -rw-r--r-- | lib/ssl_gnutls.c | 110 | ||||
| -rw-r--r-- | lib/ssl_nss.c | 20 | ||||
| -rw-r--r-- | lib/ssl_openssl.c | 22 | ||||
| -rw-r--r-- | protocols/jabber/io.c | 53 | ||||
| -rw-r--r-- | protocols/jabber/jabber.c | 3 | ||||
| -rw-r--r-- | protocols/jabber/jabber.h | 2 | ||||
| -rw-r--r-- | protocols/skype/skype.c | 2 | 
12 files changed, 216 insertions, 25 deletions
@@ -66,6 +66,7 @@ conf_t *conf_load( int argc, char *argv[] )  	conf->ft_max_kbps = G_MAXUINT;  	conf->ft_listen = NULL;  	conf->protocols = NULL; +	conf->cafile = NULL;  	proxytype = 0;  	i = conf_loadini( conf, global.conf_file ); @@ -339,6 +340,11 @@ static int conf_loadini( conf_t *conf, char *file )  				g_strfreev( conf->protocols );  				conf->protocols = g_strsplit_set( ini->value, " \t,;", -1 );  			} +			else if( g_strcasecmp( ini->key, "cafile" ) == 0 ) +			{ +				g_free( conf->cafile ); +				conf->cafile = g_strdup( ini->value ); +			}  			else  			{  				fprintf( stderr, "Error: Unknown setting `%s` in configuration file (line %d).\n", ini->key, ini->line ); @@ -53,6 +53,7 @@ typedef struct conf  	int ft_max_kbps;  	char *ft_listen;  	char **protocols; +	char *cafile;  } conf_t;  G_GNUC_MALLOC conf_t *conf_load( int argc, char *argv[] ); diff --git a/lib/http_client.c b/lib/http_client.c index 9d986412..02e5ebbe 100644 --- a/lib/http_client.c +++ b/lib/http_client.c @@ -32,7 +32,7 @@  static gboolean http_connected( gpointer data, int source, b_input_condition cond ); -static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond ); +static gboolean http_ssl_connected( gpointer data, int returncode, void *source, b_input_condition cond );  static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond );  static void http_free( struct http_request *req ); @@ -169,8 +169,9 @@ error:  	return FALSE;  } -static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond ) +static gboolean http_ssl_connected( gpointer data, int returncode, void *source, b_input_condition cond )  { +	//The returncode is not used at the moment.  	struct http_request *req = data;  	if( source == NULL ) diff --git a/lib/ssl_bogus.c b/lib/ssl_bogus.c index f4ce5d4d..e2466c19 100644 --- a/lib/ssl_bogus.c +++ b/lib/ssl_bogus.c @@ -55,7 +55,7 @@ int ssl_getfd( void *conn )  	return( -1 );  } -void *ssl_starttls( int fd, ssl_input_function func, gpointer data )  +void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data )   {  	return NULL;  } diff --git a/lib/ssl_client.h b/lib/ssl_client.h index 091335c5..03355297 100644 --- a/lib/ssl_client.h +++ b/lib/ssl_client.h @@ -36,14 +36,25 @@  /* Some generic error codes. Especially SSL_AGAIN is important if you     want to do asynchronous I/O. */ +#define NSS_VERIFY_ERROR -2 +#define OPENSSL_VERIFY_ERROR -1  #define SSL_OK            0  #define SSL_NOHANDSHAKE   1  #define SSL_AGAIN         2 +#define VERIFY_CERT_ERROR 2 +#define VERIFY_CERT_INVALID 4 +#define VERIFY_CERT_REVOKED 8 +#define VERIFY_CERT_SIGNER_NOT_FOUND 16 +#define VERIFY_CERT_SIGNER_NOT_CA 32 +#define VERIFY_CERT_INSECURE_ALGORITHM 64 +#define VERIFY_CERT_NOT_ACTIVATED 128 +#define VERIFY_CERT_EXPIRED 256 +#define VERIFY_CERT_WRONG_HOSTNAME 512  extern int ssl_errno;  /* This is what your callback function should look like. */ -typedef gboolean (*ssl_input_function)(gpointer, void*, b_input_condition); +typedef gboolean (*ssl_input_function)(gpointer, int, void*, b_input_condition);  /* Perform any global initialization the SSL library might need. */ @@ -56,7 +67,7 @@ G_MODULE_EXPORT void *ssl_connect( char *host, int port, ssl_input_function func  /* Start an SSL session on an existing fd. Useful for STARTTLS functionality,     for example in Jabber. */ -G_MODULE_EXPORT void *ssl_starttls( int fd, ssl_input_function func, gpointer data ); +G_MODULE_EXPORT void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data );  /* Obviously you need special read/write functions to read data. */  G_MODULE_EXPORT int ssl_read( void *conn, char *buf, int len ); diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c index ccab8aca..41f71f63 100644 --- a/lib/ssl_gnutls.c +++ b/lib/ssl_gnutls.c @@ -24,6 +24,7 @@  */  #include <gnutls/gnutls.h> +#include <gnutls/x509.h>  #include <gcrypt.h>  #include <fcntl.h>  #include <unistd.h> @@ -31,6 +32,7 @@  #include "ssl_client.h"  #include "sock.h"  #include "stdlib.h" +#include "bitlbee.h"  int ssl_errno = 0; @@ -53,6 +55,8 @@ struct scd  	int fd;  	gboolean established;  	int inpa; +	char *hostname; +	gboolean verify;  	gnutls_session session;  	gnutls_certificate_credentials xcred; @@ -91,7 +95,7 @@ void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data  	return conn;  } -void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) +void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data )  {  	struct scd *conn = g_new0( struct scd, 1 ); @@ -99,6 +103,13 @@ void *ssl_starttls( int fd, ssl_input_function func, gpointer data )  	conn->func = func;  	conn->data = data;  	conn->inpa = -1; +	conn->hostname = hostname; +	 +	/* For now, SSL verification is globally enabled by setting the cafile +	   setting in bitlbee.conf. Commented out by default because probably +	   not everyone has this file in the same place and plenty of folks +	   may not have the cert of their private Jabber server in it. */ +	conn->verify = verify && global.conf->cafile;  	/* This function should be called via a (short) timeout instead of  	   directly from here, because these SSL calls are *supposed* to be @@ -121,13 +132,75 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition  	return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );  } +static int verify_certificate_callback( gnutls_session_t session ) +{ +	unsigned int status; +	const gnutls_datum_t *cert_list; +	unsigned int cert_list_size; +	int gnutlsret; +	int verifyret = 0; +	gnutls_x509_crt_t cert; +	const char *hostname; +	 +	hostname = gnutls_session_get_ptr(session ); + +	gnutlsret = gnutls_certificate_verify_peers2( session, &status ); +	if( gnutlsret < 0 ) +		return VERIFY_CERT_ERROR; + +	if( status & GNUTLS_CERT_INVALID ) +		verifyret |= VERIFY_CERT_INVALID; + +	if( status & GNUTLS_CERT_REVOKED ) +		verifyret |= VERIFY_CERT_REVOKED; + +	if( status & GNUTLS_CERT_SIGNER_NOT_FOUND ) +		verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND; + +	if( status & GNUTLS_CERT_SIGNER_NOT_CA ) +		verifyret |= VERIFY_CERT_SIGNER_NOT_CA; + +	if( status & GNUTLS_CERT_INSECURE_ALGORITHM ) +		verifyret |= VERIFY_CERT_INSECURE_ALGORITHM; + +	if( status & GNUTLS_CERT_NOT_ACTIVATED ) +		verifyret |= VERIFY_CERT_NOT_ACTIVATED; + +	if( status & GNUTLS_CERT_EXPIRED ) +		verifyret |= VERIFY_CERT_EXPIRED; + +	/* The following check is already performed inside  +	 * gnutls_certificate_verify_peers2, so we don't need it. + +	 * if( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 ) +	 * return GNUTLS_E_CERTIFICATE_ERROR; +	 */ + +	if( gnutls_x509_crt_init( &cert ) < 0 ) +		return VERIFY_CERT_ERROR; + +	cert_list = gnutls_certificate_get_peers( session, &cert_list_size ); +	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 ) ) +	{ +		verifyret |= VERIFY_CERT_INVALID; +		verifyret |= VERIFY_CERT_WRONG_HOSTNAME; +	} + +	gnutls_x509_crt_deinit( cert ); + +	return verifyret; +} +  static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )  {  	struct scd *conn = data;  	if( source == -1 )  	{ -		conn->func( conn->data, NULL, cond ); +		conn->func( conn->data, 0, NULL, cond );  		g_free( conn );  		return FALSE;  	} @@ -135,7 +208,15 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con  	ssl_init();  	gnutls_certificate_allocate_credentials( &conn->xcred ); +	if( conn->verify && global.conf->cafile ) +	{ +		gnutls_certificate_set_x509_trust_file( conn->xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM ); +		gnutls_certificate_set_verify_flags( conn->xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT ); +	} +  	gnutls_init( &conn->session, GNUTLS_CLIENT ); +	if( conn->verify ) +		gnutls_session_set_ptr( conn->session, (void *) conn->hostname );  #if GNUTLS_VERSION_NUMBER < 0x020c00  	gnutls_transport_set_lowat( conn->session, 0 );  #endif @@ -151,7 +232,7 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con  static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond )  {  	struct scd *conn = data; -	int st; +	int st, stver;  	if( ( st = gnutls_handshake( conn->session ) ) < 0 )  	{ @@ -162,7 +243,7 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con  		}  		else  		{ -			conn->func( conn->data, NULL, cond ); +			conn->func( conn->data, 0, NULL, cond );  			gnutls_deinit( conn->session );  			gnutls_certificate_free_credentials( conn->xcred ); @@ -173,11 +254,24 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con  	}  	else  	{ -		/* For now we can't handle non-blocking perfectly everywhere... */ -		sock_make_blocking( conn->fd ); +		if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 ) +		{ +			conn->func( conn->data, stver, NULL, cond ); + +			gnutls_deinit( conn->session ); +			gnutls_certificate_free_credentials( conn->xcred ); +			closesocket( conn->fd ); + +			g_free( conn ); +		} +		else +		{ +			/* For now we can't handle non-blocking perfectly everywhere... */ +			sock_make_blocking( conn->fd ); -		conn->established = TRUE; -		conn->func( conn->data, conn, cond ); +			conn->established = TRUE; +			conn->func( conn->data, 0, conn, cond ); +		}  	}  	return FALSE; diff --git a/lib/ssl_nss.c b/lib/ssl_nss.c index ec524ca6..4dfa063d 100644 --- a/lib/ssl_nss.c +++ b/lib/ssl_nss.c @@ -51,6 +51,7 @@ struct scd  	int fd;  	PRFileDesc *prfd;  	gboolean established; +	gboolean verify;  };  static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ); @@ -131,13 +132,14 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition  	return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );  } -void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) +void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data )  {  	struct scd *conn = g_new0( struct scd, 1 );  	conn->fd = fd;  	conn->func = func;  	conn->data = data; +	conn->verify = verify;  	/* This function should be called via a (short) timeout instead of  	   directly from here, because these SSL calls are *supposed* to be @@ -157,6 +159,18 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con  {  	struct scd *conn = data; +	/* Right now we don't have any verification functionality for nss so we  +	   fail in case verification has been requested by the user. */ + +	if( conn->verify ) +	{ +		conn->func( conn->data, NSS_VERIFY_ERROR, NULL, cond ); +		if( source >= 0 ) closesocket( source ); +		g_free( conn ); + +		return FALSE; +	} +	  	if( source == -1 )  		goto ssl_connected_failure; @@ -176,12 +190,12 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con  	conn->established = TRUE; -	conn->func( conn->data, conn, cond ); +	conn->func( conn->data, 0, conn, cond );  	return FALSE;  	ssl_connected_failure: -	conn->func( conn->data, NULL, cond ); +	conn->func( conn->data, 0, NULL, cond );  	PR_Close( conn -> prfd );  	if( source >= 0 ) closesocket( source ); diff --git a/lib/ssl_openssl.c b/lib/ssl_openssl.c index 5f64042d..7c7f725e 100644 --- a/lib/ssl_openssl.c +++ b/lib/ssl_openssl.c @@ -44,6 +44,7 @@ struct scd  	gpointer data;  	int fd;  	gboolean established; +	gboolean verify;  	int inpa;  	int lasterr;		/* Necessary for SSL_get_error */ @@ -81,7 +82,7 @@ void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data  	return conn;  } -void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) +void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data )  {  	struct scd *conn = g_new0( struct scd, 1 ); @@ -89,6 +90,7 @@ void *ssl_starttls( int fd, ssl_input_function func, gpointer data )  	conn->func = func;  	conn->data = data;  	conn->inpa = -1; +	conn->verify = verify;  	/* This function should be called via a (short) timeout instead of  	   directly from here, because these SSL calls are *supposed* to be @@ -116,6 +118,18 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con  	struct scd *conn = data;  	SSL_METHOD *meth; +	/* Right now we don't have any verification functionality for openssl so we  +	   fail in case verification has been requested by the user. */ + +	if( conn->verify ) +	{ +		conn->func( conn->data, OPENSSL_VERIFY_ERROR, NULL, cond ); +		if( source >= 0 ) closesocket( source ); +		g_free( conn ); + +		return FALSE; +	} +  	if( source == -1 )  		goto ssl_connected_failure; @@ -140,7 +154,7 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con  	return ssl_handshake( data, source, cond );  ssl_connected_failure: -	conn->func( conn->data, NULL, cond ); +	conn->func( conn->data, 0, NULL, cond );  	if( conn->ssl )  	{ @@ -168,7 +182,7 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con  		conn->lasterr = SSL_get_error( conn->ssl, st );  		if( conn->lasterr != SSL_ERROR_WANT_READ && conn->lasterr != SSL_ERROR_WANT_WRITE )  		{ -			conn->func( conn->data, NULL, cond ); +			conn->func( conn->data, 0, NULL, cond );  			SSL_shutdown( conn->ssl );  			SSL_free( conn->ssl ); @@ -186,7 +200,7 @@ static gboolean ssl_handshake( gpointer data, gint source, b_input_condition con  	conn->established = TRUE;  	sock_make_blocking( conn->fd );		/* For now... */ -	conn->func( conn->data, conn, cond ); +	conn->func( conn->data, 0, conn, cond );  	return FALSE;  } diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c index a28eea90..9e55e3f9 100644 --- a/protocols/jabber/io.c +++ b/protocols/jabber/io.c @@ -275,7 +275,7 @@ gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition c  	return jabber_start_stream( ic );  } -gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond ) +gboolean jabber_connected_ssl( gpointer data, int returncode, void *source, b_input_condition cond )  {  	struct im_connection *ic = data;  	struct jabber_data *jd; @@ -292,6 +292,43 @@ gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition co  		jd->ssl = NULL;  		imcb_error( ic, "Could not connect to server" ); +		if (returncode ==  OPENSSL_VERIFY_ERROR ) +		{ +			imcb_error( ic, "This BitlBee server is built agains the OpenSSL library." ); +			imcb_error( ic, "Unfortunately certificate verification is only supported when built against GnuTLS for now." ); +			imc_logout( ic, FALSE ); +		} +		else if (returncode ==  NSS_VERIFY_ERROR ) +		{ +			imcb_error( ic, "This BitlBee server is built agains the NSS library." ); +			imcb_error( ic, "Unfortunately certificate verification is only supported when built against GnuTLS for now." ); +			imc_logout( ic, FALSE ); +		} +		else if (returncode == VERIFY_CERT_ERROR ) +		{ +			imcb_error( ic, "An error occured during the certificate verification." ); +			imc_logout( ic, FALSE ); +		} +		else if (returncode  & VERIFY_CERT_INVALID) +		{ +			imcb_error( ic, "Unable to verify peer's certificate." ); +			if (returncode & VERIFY_CERT_REVOKED) +				imcb_error( ic, "The certificate has been revoked." ); +			if (returncode & VERIFY_CERT_SIGNER_NOT_FOUND) +				imcb_error( ic, "The certificate hasn't got a known issuer." ); +			if (returncode & VERIFY_CERT_SIGNER_NOT_CA) +				imcb_error( ic, "The certificate's issuer is not a CA." ); +			if (returncode & VERIFY_CERT_INSECURE_ALGORITHM) +				imcb_error( ic, "The certificate uses an insecure algorithm." ); +			if (returncode & VERIFY_CERT_NOT_ACTIVATED) +				imcb_error( ic, "The certificate has not been activated." ); +			if (returncode & VERIFY_CERT_EXPIRED) +				imcb_error( ic, "The certificate has expired." ); +			if (returncode & VERIFY_CERT_WRONG_HOSTNAME) +				imcb_error( ic, "The hostname specified in the certificate doesn't match the server name." ); +			imc_logout( ic, FALSE ); +		} +		else  		imc_logout( ic, TRUE );  		return FALSE;  	} @@ -396,7 +433,7 @@ static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )  {  	struct im_connection *ic = data;  	struct jabber_data *jd = ic->proto_data; -	char *xmlns; +	char *xmlns, *tlsname;  	xmlns = xt_find_attr( node, "xmlns" ); @@ -422,7 +459,17 @@ static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )  	imcb_log( ic, "Converting stream to TLS" );  	jd->flags |= JFLAG_STARTTLS_DONE; -	jd->ssl = ssl_starttls( jd->fd, jabber_connected_ssl, ic ); + +	/* If the user specified a server for the account, use this server as the  +	 * hostname in the certificate verification. Else we use the domain from  +	 * the username. */ +	if( ic->acc->server && *ic->acc->server ) +		tlsname = ic->acc->server; +	else +		tlsname = jd->server; +	 +	jd->ssl = ssl_starttls( jd->fd, tlsname, set_getbool( &ic->acc->set, "tls_verify" ), +	                        jabber_connected_ssl, ic );  	return XT_HANDLED;  } diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 7d9547ab..dd2f0866 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -81,6 +81,9 @@ static void jabber_init( account_t *acc )  	s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );  	s->flags |= ACC_SET_OFFLINE_ONLY; +	s = set_add( &acc->set, "tls_verify", "true", set_eval_bool, acc ); +	s->flags |= ACC_SET_OFFLINE_ONLY; +	  	s = set_add( &acc->set, "sasl", "true", set_eval_bool, acc );  	s->flags |= ACC_SET_OFFLINE_ONLY | SET_HIDDEN_DEFAULT; diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index adf9a291..5996c301 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -306,7 +306,7 @@ extern const struct jabber_away_state jabber_away_state_list[];  int jabber_write_packet( struct im_connection *ic, struct xt_node *node );  int jabber_write( struct im_connection *ic, char *buf, int len );  gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond ); -gboolean jabber_connected_ssl( gpointer data, void *source, b_input_condition cond ); +gboolean jabber_connected_ssl( gpointer data, int returncode, void *source, b_input_condition cond );  gboolean jabber_start_stream( struct im_connection *ic );  void jabber_end_stream( struct im_connection *ic ); diff --git a/protocols/skype/skype.c b/protocols/skype/skype.c index 5b1a6c30..10f355a6 100644 --- a/protocols/skype/skype.c +++ b/protocols/skype/skype.c @@ -1156,7 +1156,7 @@ gboolean skype_start_stream(struct im_connection *ic)  	return st;  } -gboolean skype_connected(gpointer data, void *source, b_input_condition cond) +gboolean skype_connected(gpointer data, int returncode, void *source, b_input_condition cond)  {  	struct im_connection *ic = data;  	struct skype_data *sd = ic->proto_data;  | 
