2008-08-24 [colin] 3.5.0cvs70
[claws.git] / src / common / ssl.c
index bdf2b3e9a893248296e4ef54f747b5a1bdf2e66f..13ea119982bc255a085000bacc0c7ba509438949 100644 (file)
@@ -31,6 +31,7 @@
 #include "utils.h"
 #include "ssl.h"
 #include "ssl_certificate.h"
+#include "hooks.h"
 
 #ifdef HAVE_LIBETPAN
 #include <libetpan/mailstream_ssl.h>
@@ -56,6 +57,147 @@ typedef struct _thread_data {
 static SSL_CTX *ssl_ctx;
 #endif
 
+#ifdef USE_OPENSSL
+static int openssl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey)
+{
+       SSLClientCertHookData hookdata;
+       SockInfo *sockinfo = (SockInfo *)SSL_CTX_get_app_data(ssl->ctx);
+       
+       if (x509 == NULL || pkey == NULL) {
+               return 0;
+       }
+
+       if (sockinfo == NULL)
+               return 0;
+
+       hookdata.account = sockinfo->account;
+       hookdata.cert_path = NULL;
+       hookdata.password = NULL;
+       hookdata.is_smtp = sockinfo->is_smtp;
+       hooks_invoke(SSLCERT_GET_CLIENT_CERT_HOOKLIST, &hookdata);      
+
+       if (hookdata.cert_path == NULL)
+               return 0;
+
+       *x509 = ssl_certificate_get_x509_from_pem_file(hookdata.cert_path);
+       *pkey = ssl_certificate_get_pkey_from_pem_file(hookdata.cert_path);
+       if (!(*x509 && *pkey)) {
+               /* try pkcs12 format */
+               ssl_certificate_get_x509_and_pkey_from_p12_file(hookdata.cert_path, hookdata.password, x509, pkey);
+       }
+       if (*x509 && *pkey)
+               return 1;
+       else
+               return 0;
+}
+#endif
+#ifdef USE_GNUTLS
+static int gnutls_client_cert_cb(gnutls_session session,
+                               const gnutls_datum *req_ca_rdn, int nreqs,
+                               const gnutls_pk_algorithm *sign_algos,
+                               int sign_algos_length, gnutls_retr_st *st)
+{
+       SSLClientCertHookData hookdata;
+       SockInfo *sockinfo = (SockInfo *)gnutls_session_get_ptr(session);
+       gnutls_certificate_type type = gnutls_certificate_type_get(session);
+       gnutls_x509_crt crt;
+       gnutls_x509_privkey key;
+
+       st->ncerts = 0;
+
+       hookdata.account = sockinfo->account;
+       hookdata.cert_path = NULL;
+       hookdata.password = NULL;
+       hookdata.is_smtp = sockinfo->is_smtp;
+       hooks_invoke(SSLCERT_GET_CLIENT_CERT_HOOKLIST, &hookdata);      
+
+       if (hookdata.cert_path == NULL)
+               return 0;
+
+       sockinfo->client_crt = ssl_certificate_get_x509_from_pem_file(hookdata.cert_path);
+       sockinfo->client_key = ssl_certificate_get_pkey_from_pem_file(hookdata.cert_path);
+       if (!(sockinfo->client_crt && sockinfo->client_key)) {
+               /* try pkcs12 format */
+               ssl_certificate_get_x509_and_pkey_from_p12_file(hookdata.cert_path, hookdata.password, 
+                       &crt, &key);
+               sockinfo->client_crt = crt;
+               sockinfo->client_key = key;
+       }
+
+       if (type == GNUTLS_CRT_X509 && sockinfo->client_crt && sockinfo->client_key) {
+               st->ncerts = 1;
+               st->type = type;
+               st->cert.x509 = &(sockinfo->client_crt);
+               st->key.x509 = sockinfo->client_key;
+               st->deinit_all = 0;
+               return 0;
+       }
+       return -1;
+}
+#endif
+
+#ifdef USE_OPENSSL
+SSL_CTX *ssl_get_ctx(void)
+{
+       return ssl_ctx;
+}
+#endif
+
+const gchar *claws_ssl_get_cert_file(void)
+{
+       const char *cert_files[]={
+               "/etc/pki/tls/certs/ca-bundle.crt",
+               "/etc/certs/ca-bundle.crt",
+               "/usr/share/ssl/certs/ca-bundle.crt",
+               "/etc/ssl/certs/ca-certificates.crt",
+               "/usr/local/ssl/certs/ca-bundle.crt",
+               "/etc/apache/ssl.crt/ca-bundle.crt",
+               "/usr/share/curl/curl-ca-bundle.crt",
+               "/usr/share/curl/curl-ca-bundle.crt",
+               "/usr/lib/ssl/cert.pem",
+               NULL};
+       int i;
+
+       if (g_getenv("SSL_CERT_FILE"))
+               return g_getenv("SSL_CERT_FILE");
+#ifndef G_OS_WIN32
+       for (i = 0; cert_files[i]; i++) {
+               if (is_file_exist(cert_files[i]))
+                       return cert_files[i];
+       }
+       return NULL;
+#else
+       return "put_what_s_needed_here";
+#endif
+}
+
+const gchar *claws_ssl_get_cert_dir(void)
+{
+       const char *cert_dirs[]={
+               "/etc/pki/tls/certs",
+               "/etc/certs",
+               "/usr/share/ssl/certs",
+               "/etc/ssl/certs",
+               "/usr/local/ssl/certs",
+               "/etc/apache/ssl.crt",
+               "/usr/share/curl",
+               "/usr/lib/ssl/certs",
+               NULL};
+       int i;
+
+       if (g_getenv("SSL_CERT_DIR"))
+               return g_getenv("SSL_CERT_DIR");
+#ifndef G_OS_WIN32
+       for (i = 0; cert_dirs[i]; i++) {
+               if (is_dir_exist(cert_dirs[i]))
+                       return cert_dirs[i];
+       }
+       return NULL;
+#else
+       return "put_what_s_needed_here";
+#endif
+}
+
 void ssl_init(void)
 {
 #ifdef USE_OPENSSL
@@ -64,6 +206,9 @@ void ssl_init(void)
        /* Global system initialization*/
        SSL_library_init();
        SSL_load_error_strings();
+       OpenSSL_add_all_algorithms();
+       OpenSSL_add_all_ciphers();
+       OpenSSL_add_all_digests();
 
 #ifdef HAVE_LIBETPAN
        mailstream_openssl_init_not_required();
@@ -73,9 +218,21 @@ void ssl_init(void)
        meth = SSLv23_client_method();
        ssl_ctx = SSL_CTX_new(meth);
 
-       /* Set default certificate paths */
-       SSL_CTX_set_default_verify_paths(ssl_ctx);
        
+       SSL_CTX_set_client_cert_cb(ssl_ctx, openssl_client_cert_cb);
+
+       /* Set default certificate paths */
+       if (claws_ssl_get_cert_file() || claws_ssl_get_cert_dir()) {
+               int r = SSL_CTX_load_verify_locations(ssl_ctx, claws_ssl_get_cert_file(), claws_ssl_get_cert_dir());
+               if (r != 1) {
+                       g_warning("can't set cert file %s dir %s: %s\n",
+                                       claws_ssl_get_cert_file(), claws_ssl_get_cert_dir(), ERR_error_string(ERR_get_error(), NULL));
+                       SSL_CTX_set_default_verify_paths(ssl_ctx);
+               }
+       } else {
+               g_warning("cant");
+               SSL_CTX_set_default_verify_paths(ssl_ctx);
+       }
 #if (OPENSSL_VERSION_NUMBER < 0x0090600fL)
        SSL_CTX_set_verify_depth(ssl_ctx,1);
 #endif
@@ -123,13 +280,14 @@ static gint SSL_connect_nb(SSL *ssl)
 static gint SSL_connect_nb(gnutls_session ssl)
 #endif
 {
-#if (defined USE_PTHREAD && ((defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3))) || !defined __GLIBC__))
-       thread_data *td = g_new0(thread_data, 1);
-       pthread_t pt;
-       void *res = NULL;
 #ifdef USE_GNUTLS
        int result;
 #endif
+#ifdef USE_PTHREAD
+       thread_data *td = g_new0(thread_data, 1);
+       pthread_t pt;
+       pthread_attr_t pta;
+       void *res = NULL;
        time_t start_time = time(NULL);
        gboolean killed = FALSE;
        
@@ -139,14 +297,16 @@ static gint SSL_connect_nb(gnutls_session ssl)
        /* try to create a thread to initialize the SSL connection,
         * fallback to blocking method in case of problem 
         */
-       if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, 
-                       SSL_connect_thread, td) != 0) {
+       if (pthread_attr_init(&pta) != 0 ||
+           pthread_attr_setdetachstate(&pta, PTHREAD_CREATE_JOINABLE) != 0 ||
+           pthread_create(&pt, &pta, SSL_connect_thread, td) != 0) {
 #ifdef USE_OPENSSL
                return SSL_connect(ssl);
 #else
                do {
                        result = gnutls_handshake(td->ssl);
                } while (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED);
+               return result;
 #endif
        }
        debug_print("waiting for SSL_connect thread...\n");
@@ -171,12 +331,12 @@ static gint SSL_connect_nb(gnutls_session ssl)
                        GPOINTER_TO_INT(res));
        
        return GPOINTER_TO_INT(res);
-#else
+#else /* USE_PTHREAD */
 #ifdef USE_OPENSSL
        return SSL_connect(ssl);
 #else
        do {
-               result = gnutls_handshake(td->ssl);
+               result = gnutls_handshake(ssl);
        } while (result == GNUTLS_E_AGAIN || result == GNUTLS_E_INTERRUPTED);
 #endif
 #endif
@@ -187,19 +347,6 @@ gboolean ssl_init_socket(SockInfo *sockinfo)
        return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23);
 }
 
-#ifdef USE_GNUTLS
-static const gchar *ssl_get_cert_file(void)
-{
-       if (g_getenv("SSL_CERT_FILE"))
-               return g_getenv("SSL_CERT_FILE");
-#ifndef G_OS_WIN32
-       return "/etc/ssl/certs/ca-certificates.crt";
-#else
-       return "put_what_s_needed_here";
-#endif
-}
-#endif
-
 gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
 {
 #ifdef USE_OPENSSL
@@ -225,6 +372,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
                break;
        }
 
+       SSL_CTX_set_app_data(ssl_ctx, sockinfo);
        SSL_set_fd(ssl, sockinfo->sock);
        if (SSL_connect_nb(ssl) == -1) {
                g_warning(_("SSL connect failed (%s)\n"),
@@ -254,6 +402,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
 
        X509_free(server_cert);
        sockinfo->ssl = ssl;
+       
 #else
        gnutls_session session;
        int r;
@@ -289,19 +438,26 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
 
        gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
 
-       r = gnutls_certificate_set_x509_trust_file(xcred, ssl_get_cert_file(),  GNUTLS_X509_FMT_PEM);
-       if (r < 0)
-               g_warning("Can't read SSL_CERT_FILE %s: %s\n",
-                       ssl_get_cert_file(), 
-                       gnutls_strerror(r));
-
+       if (claws_ssl_get_cert_file()) {
+               r = gnutls_certificate_set_x509_trust_file(xcred, claws_ssl_get_cert_file(),  GNUTLS_X509_FMT_PEM);
+               if (r < 0)
+                       g_warning("Can't read SSL_CERT_FILE %s: %s\n",
+                               claws_ssl_get_cert_file(), 
+                               gnutls_strerror(r));
+       } else {
+               debug_print("Can't find SSL ca-certificates file\n");
+       }
        gnutls_certificate_set_verify_flags (xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
 
-       gnutls_transport_set_ptr(session, (gnutls_transport_ptr) 
-               sockinfo->sock);
+       gnutls_transport_set_ptr(session, (gnutls_transport_ptr) sockinfo->sock);
+       gnutls_session_set_ptr(session, sockinfo);
+       gnutls_certificate_client_set_retrieve_function(xcred, gnutls_client_cert_cb);
+
+       gnutls_dh_set_prime_bits(session, 512);
 
        if ((r = SSL_connect_nb(session)) < 0) {
                g_warning("SSL connection failed (%s)", gnutls_strerror(r));
+               gnutls_certificate_free_credentials(xcred);
                gnutls_deinit(session);
                return FALSE;
        }
@@ -314,6 +470,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        ||  (r = gnutls_x509_crt_init(&cert)) < 0
        ||  (r = gnutls_x509_crt_import(cert, &raw_cert_list[0], GNUTLS_X509_FMT_DER)) < 0) {
                g_warning("cert get failure: %d %s\n", r, gnutls_strerror(r));
+               gnutls_certificate_free_credentials(xcred);
                gnutls_deinit(session);
                return FALSE;
        }
@@ -322,6 +479,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
 
        if (!ssl_certificate_check(cert, status, sockinfo->canonical_name, sockinfo->hostname, sockinfo->port)) {
                gnutls_x509_crt_deinit(cert);
+               gnutls_certificate_free_credentials(xcred);
                gnutls_deinit(session);
                return FALSE;
        }
@@ -329,6 +487,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        gnutls_x509_crt_deinit(cert);
 
        sockinfo->ssl = session;
+       sockinfo->xcred = xcred;
 #endif
        return TRUE;
 }
@@ -339,7 +498,14 @@ void ssl_done_socket(SockInfo *sockinfo)
 #ifdef USE_OPENSSL
                SSL_free(sockinfo->ssl);
 #else
+               gnutls_certificate_free_credentials(sockinfo->xcred);
                gnutls_deinit(sockinfo->ssl);
+               if (sockinfo->client_crt)
+                       gnutls_x509_crt_deinit(sockinfo->client_crt);
+               if (sockinfo->client_key)
+                       gnutls_x509_privkey_deinit(sockinfo->client_key);
+               sockinfo->client_key = NULL;
+               sockinfo->client_crt = NULL;
 #endif
                sockinfo->ssl = NULL;
        }