Do not explicitly disable SSLv3 if GnuTLS does it already.
[claws.git] / src / common / ssl.c
index cbf1d3703835ed6c64d899fc63a176b55577d2ff..e25a42301fb73752a1d71b1cd7b6f1108ab31ce8 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2011 Hiroyuki Yamamoto and the Claws Mail team
+ * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
+#include "claws-features.h"
 #endif
 
 #ifdef USE_GNUTLS
@@ -29,8 +30,6 @@
 #include <glib/gi18n.h>
 #include <errno.h>
 #include <pthread.h>
-#include <gcrypt.h>
-GCRY_THREAD_OPTION_PTHREAD_IMPL;
 
 #include "claws.h"
 #include "utils.h"
@@ -38,6 +37,11 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
 #include "ssl_certificate.h"
 #include "hooks.h"
 
+#if GNUTLS_VERSION_NUMBER <= 0x020b00
+#include <gcrypt.h>
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif
+
 #ifdef HAVE_LIBETPAN
 #include <libetpan/mailstream_ssl.h>
 #endif
@@ -48,21 +52,28 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
 
 #ifdef USE_PTHREAD
 typedef struct _thread_data {
-       gnutls_session ssl;
+       gnutls_session_t ssl;
        gboolean done;
 } thread_data;
 #endif
 
-static int gnutls_client_cert_cb(gnutls_session session,
-                               const gnutls_datum *req_ca_rdn, int nreqs,
-                               const gnutls_pk_algorithm *sign_algos,
+#if GNUTLS_VERSION_NUMBER <= 0x020c00
+static int gnutls_client_cert_cb(gnutls_session_t session,
+                               const gnutls_datum_t *req_ca_rdn, int nreqs,
+                               const gnutls_pk_algorithm_t *sign_algos,
                                int sign_algos_length, gnutls_retr_st *st)
+#else
+static int gnutls_cert_cb(gnutls_session_t session,
+                               const gnutls_datum_t *req_ca_rdn, int nreqs,
+                               const gnutls_pk_algorithm_t *sign_algos,
+                               int sign_algos_length, gnutls_retr2_st *st)
+#endif
 {
        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;
+       gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
+       gnutls_x509_crt_t crt;
+       gnutls_x509_privkey_t key;
 
        st->ncerts = 0;
 
@@ -72,8 +83,10 @@ static int gnutls_client_cert_cb(gnutls_session session,
        hookdata.is_smtp = sockinfo->is_smtp;
        hooks_invoke(SSLCERT_GET_CLIENT_CERT_HOOKLIST, &hookdata);      
 
-       if (hookdata.cert_path == NULL)
+       if (hookdata.cert_path == NULL) {
+               g_free(hookdata.password);
                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);
@@ -87,20 +100,28 @@ static int gnutls_client_cert_cb(gnutls_session session,
 
        if (type == GNUTLS_CRT_X509 && sockinfo->client_crt && sockinfo->client_key) {
                st->ncerts = 1;
+#if GNUTLS_VERSION_NUMBER <= 0x020c00
                st->type = type;
+#else
+               st->key_type = type;
+#endif
                st->cert.x509 = &(sockinfo->client_crt);
                st->key.x509 = sockinfo->client_key;
                st->deinit_all = 0;
+               g_free(hookdata.password);
                return 0;
        }
+       g_free(hookdata.password);
        return 0;
 }
 
 const gchar *claws_ssl_get_cert_file(void)
 {
+#ifndef G_OS_WIN32
        const char *cert_files[]={
                "/etc/pki/tls/certs/ca-bundle.crt",
                "/etc/certs/ca-bundle.crt",
+               "/etc/ssl/ca-bundle.pem",
                "/usr/share/ssl/certs/ca-bundle.crt",
                "/etc/ssl/certs/ca-certificates.crt",
                "/usr/local/ssl/certs/ca-bundle.crt",
@@ -110,9 +131,12 @@ const gchar *claws_ssl_get_cert_file(void)
                "/usr/lib/ssl/cert.pem",
                NULL};
        int i;
-       
+#endif
+
+       /* We honor this environment variable on all platforms. */
        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]))
@@ -120,7 +144,7 @@ const gchar *claws_ssl_get_cert_file(void)
        }
        return NULL;
 #else
-       return get_cert_file();
+       return w32_get_cert_file();
 #endif
 }
 
@@ -147,13 +171,15 @@ const gchar *claws_ssl_get_cert_dir(void)
        }
        return NULL;
 #else
-       return "put_what_s_needed_here";
+       return NULL;
 #endif
 }
 
 void ssl_init(void)
 {
+#if GNUTLS_VERSION_NUMBER <= 0x020b00
        gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
+#endif
 #ifdef HAVE_LIBETPAN
        mailstream_gnutls_init_not_required();
 #endif 
@@ -183,7 +209,7 @@ static void *SSL_connect_thread(void *data)
 }
 #endif
 
-static gint SSL_connect_nb(gnutls_session ssl)
+static gint SSL_connect_nb(gnutls_session_t ssl)
 {
        int result;
 #ifdef USE_PTHREAD
@@ -237,30 +263,59 @@ static gint SSL_connect_nb(gnutls_session ssl)
 #endif
 }
 
-gboolean ssl_init_socket(SockInfo *sockinfo)
+gnutls_x509_crt_t *ssl_get_certificate_chain(gnutls_session_t session, gint *list_len)
 {
-       return ssl_init_socket_with_method(sockinfo, SSL_METHOD_SSLv23);
+       const gnutls_datum_t *raw_cert_list;
+       gnutls_x509_crt_t *certs = NULL;
+       gboolean result = TRUE;
+
+       *list_len = -1;
+       if (!session)
+               return NULL;
+
+       raw_cert_list = gnutls_certificate_get_peers(session, list_len);
+
+       if (raw_cert_list && gnutls_certificate_type_get(session) == GNUTLS_CRT_X509) {
+               int i = 0;
+
+               if (*list_len > 128)
+                       *list_len = 128;
+
+               certs = g_malloc(sizeof(gnutls_x509_crt_t) * (*list_len));
+
+               for(i = 0 ; i < (*list_len) ; i++) {
+                       int r;
+
+                       gnutls_x509_crt_init(&certs[i]);
+                       r = gnutls_x509_crt_import(certs[i], &raw_cert_list[i], GNUTLS_X509_FMT_DER);
+                       if (r < 0) {
+                               g_warning("cert get failure: %d %s", r, gnutls_strerror(r));
+
+                               result = FALSE;
+                               i--;
+                               break;
+                       }
+               }
+               if (!result) {
+                       for (; i >= 0; i--)
+                               gnutls_x509_crt_deinit(certs[i]);
+
+                       g_free(certs);
+                       *list_len = -1;
+
+                       return NULL;
+               }
+       }
+
+       return certs;
 }
 
-gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
+gboolean ssl_init_socket(SockInfo *sockinfo)
 {
-       gnutls_session session;
-       int r;
-       const int cipher_prio[] = { GNUTLS_CIPHER_AES_128_CBC,
-                               GNUTLS_CIPHER_3DES_CBC,
-                               GNUTLS_CIPHER_AES_256_CBC,
-                               GNUTLS_CIPHER_ARCFOUR_128, 0 };
-       const int kx_prio[] = { GNUTLS_KX_DHE_RSA,
-                          GNUTLS_KX_RSA, 
-                          GNUTLS_KX_DHE_DSS, 0 };
-       const int mac_prio[] = { GNUTLS_MAC_SHA1,
-                               GNUTLS_MAC_MD5, 0 };
-       const int proto_prio[] = { GNUTLS_TLS1,
-                                 GNUTLS_SSL3, 0 };
-       const gnutls_datum *raw_cert_list;
-       unsigned int raw_cert_list_length;
-       gnutls_x509_crt cert = NULL;
-       guint status;
+       gnutls_session_t session;
+       int r, i;
+       unsigned int cert_list_length;
+       gnutls_x509_crt_t *certs = NULL;
        gnutls_certificate_credentials_t xcred;
 
        if (gnutls_certificate_allocate_credentials (&xcred) != 0)
@@ -269,13 +324,17 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        r = gnutls_init(&session, GNUTLS_CLIENT);
        if (session == NULL || r != 0)
                return FALSE;
-  
-       gnutls_transport_set_lowat (session, 1); 
-       gnutls_set_default_priority(session);
-       gnutls_protocol_set_priority (session, proto_prio);
-       gnutls_cipher_set_priority (session, cipher_prio);
-       gnutls_kx_set_priority (session, kx_prio);
-       gnutls_mac_set_priority (session, mac_prio);
+
+       if (sockinfo->gnutls_priority && strlen(sockinfo->gnutls_priority)) {
+               r = gnutls_priority_set_direct(session, sockinfo->gnutls_priority, NULL);
+               debug_print("Setting GnuTLS priority to %s, status = %d\n",
+                           sockinfo->gnutls_priority, r);
+       }
+#ifdef GNUTLS_VERSION_NUMBER < 0x030400
+       else {
+               gnutls_priority_set_direct(session, "NORMAL:-VERS-SSL3.0", NULL);
+       }
+#endif
        gnutls_record_disable_padding(session);
 
        gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
@@ -283,7 +342,7 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        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",
+                       g_warning("Can't read SSL_CERT_FILE '%s': %s",
                                claws_ssl_get_cert_file(), 
                                gnutls_strerror(r));
        } else {
@@ -291,9 +350,13 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        }
        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_t) GINT_TO_POINTER(sockinfo->sock));
        gnutls_session_set_ptr(session, sockinfo);
+#if GNUTLS_VERSION_NUMBER <= 0x020c00
        gnutls_certificate_client_set_retrieve_function(xcred, gnutls_client_cert_cb);
+#else
+       gnutls_certificate_set_retrieve_function(xcred, gnutls_cert_cb);
+#endif
 
        gnutls_dh_set_prime_bits(session, 512);
 
@@ -305,28 +368,27 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
        }
 
        /* Get server's certificate (note: beware of dynamic allocation) */
-       raw_cert_list = gnutls_certificate_get_peers(session, &raw_cert_list_length);
+       certs = ssl_get_certificate_chain(session, &cert_list_length);
 
-       if (!raw_cert_list 
-       ||  gnutls_certificate_type_get(session) != GNUTLS_CRT_X509
-       ||  (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));
+       if (!certs) {
                gnutls_certificate_free_credentials(xcred);
                gnutls_deinit(session);
                return FALSE;
        }
 
-       r = gnutls_certificate_verify_peers2(session, &status);
-
-       if (!ssl_certificate_check(cert, status, sockinfo->canonical_name, sockinfo->hostname, sockinfo->port)) {
-               gnutls_x509_crt_deinit(cert);
+       if (!ssl_certificate_check_chain(certs, cert_list_length, sockinfo->hostname, sockinfo->port,
+                                        sockinfo->ssl_cert_auto_accept)) {
+               for (i = 0; i < cert_list_length; i++)
+                       gnutls_x509_crt_deinit(certs[i]);
+               g_free(certs);
                gnutls_certificate_free_credentials(xcred);
                gnutls_deinit(session);
                return FALSE;
        }
 
-       gnutls_x509_crt_deinit(cert);
+       for (i = 0; i < cert_list_length; i++)
+               gnutls_x509_crt_deinit(certs[i]);
+       g_free(certs);
 
        sockinfo->ssl = session;
        sockinfo->xcred = xcred;
@@ -336,7 +398,8 @@ gboolean ssl_init_socket_with_method(SockInfo *sockinfo, SSLMethod method)
 void ssl_done_socket(SockInfo *sockinfo)
 {
        if (sockinfo && sockinfo->ssl) {
-               gnutls_certificate_free_credentials(sockinfo->xcred);
+               if (sockinfo->xcred)
+                       gnutls_certificate_free_credentials(sockinfo->xcred);
                gnutls_deinit(sockinfo->ssl);
                if (sockinfo->client_crt)
                        gnutls_x509_crt_deinit(sockinfo->client_crt);
@@ -344,6 +407,7 @@ void ssl_done_socket(SockInfo *sockinfo)
                        gnutls_x509_privkey_deinit(sockinfo->client_key);
                sockinfo->client_key = NULL;
                sockinfo->client_crt = NULL;
+               sockinfo->xcred = NULL;
                sockinfo->ssl = NULL;
        }
 }