aboutsummaryrefslogtreecommitdiffstats
path: root/src/net
diff options
context:
space:
mode:
authorVincent Richard <[email protected]>2012-11-03 09:27:12 +0100
committerVincent Richard <[email protected]>2012-11-03 09:27:12 +0100
commitbc63892291ecf245dd210e236ab14f12e31baef8 (patch)
tree031bc486b0cafc527f516e6890105521bc7f2afc /src/net
parentMigrated build system to CMake. Conditional file compilation. Automatic selec... (diff)
downloadvmime-bc63892291ecf245dd210e236ab14f12e31baef8.tar.gz
vmime-bc63892291ecf245dd210e236ab14f12e31baef8.zip
OpenSSL support (thanks to Mehmet Bozkurt).
Diffstat (limited to 'src/net')
-rw-r--r--src/net/imap/IMAPConnection.cpp4
-rw-r--r--src/net/pop3/POP3Store.cpp4
-rw-r--r--src/net/smtp/SMTPTransport.cpp4
-rw-r--r--src/net/tls/TLSSession.cpp256
-rw-r--r--src/net/tls/TLSSocket.cpp377
-rw-r--r--src/net/tls/gnutls/TLSSession_GnuTLS.cpp304
-rw-r--r--src/net/tls/gnutls/TLSSocket_GnuTLS.cpp424
-rwxr-xr-xsrc/net/tls/openssl/OpenSSLInitializer.cpp152
-rwxr-xr-xsrc/net/tls/openssl/TLSSession_OpenSSL.cpp138
-rwxr-xr-xsrc/net/tls/openssl/TLSSocket_OpenSSL.cpp416
10 files changed, 1445 insertions, 634 deletions
diff --git a/src/net/imap/IMAPConnection.cpp b/src/net/imap/IMAPConnection.cpp
index 9cb81d8c..6c8b400e 100644
--- a/src/net/imap/IMAPConnection.cpp
+++ b/src/net/imap/IMAPConnection.cpp
@@ -111,7 +111,7 @@ void IMAPConnection::connect()
if (store->isIMAPS()) // dedicated port/IMAPS
{
ref <tls::TLSSession> tlsSession =
- vmime::create <tls::TLSSession>(store->getCertificateVerifier());
+ tls::TLSSession::create(store->getCertificateVerifier());
ref <tls::TLSSocket> tlsSocket =
tlsSession->getSocket(m_socket);
@@ -460,7 +460,7 @@ void IMAPConnection::startTLS()
}
ref <tls::TLSSession> tlsSession =
- vmime::create <tls::TLSSession>(m_store.acquire()->getCertificateVerifier());
+ tls::TLSSession::create(m_store.acquire()->getCertificateVerifier());
ref <tls::TLSSocket> tlsSocket =
tlsSession->getSocket(m_socket);
diff --git a/src/net/pop3/POP3Store.cpp b/src/net/pop3/POP3Store.cpp
index 51181ece..8233cdb2 100644
--- a/src/net/pop3/POP3Store.cpp
+++ b/src/net/pop3/POP3Store.cpp
@@ -151,7 +151,7 @@ void POP3Store::connect()
if (m_isPOP3S) // dedicated port/POP3S
{
ref <tls::TLSSession> tlsSession =
- vmime::create <tls::TLSSession>(getCertificateVerifier());
+ tls::TLSSession::create(getCertificateVerifier());
ref <tls::TLSSocket> tlsSocket =
tlsSession->getSocket(m_socket);
@@ -550,7 +550,7 @@ void POP3Store::startTLS()
throw exceptions::command_error("STLS", response);
ref <tls::TLSSession> tlsSession =
- vmime::create <tls::TLSSession>(getCertificateVerifier());
+ tls::TLSSession::create(getCertificateVerifier());
ref <tls::TLSSocket> tlsSocket =
tlsSession->getSocket(m_socket);
diff --git a/src/net/smtp/SMTPTransport.cpp b/src/net/smtp/SMTPTransport.cpp
index 64028d1c..e658ecb7 100644
--- a/src/net/smtp/SMTPTransport.cpp
+++ b/src/net/smtp/SMTPTransport.cpp
@@ -114,7 +114,7 @@ void SMTPTransport::connect()
if (m_isSMTPS) // dedicated port/SMTPS
{
ref <tls::TLSSession> tlsSession =
- vmime::create <tls::TLSSession>(getCertificateVerifier());
+ tls::TLSSession::create(getCertificateVerifier());
ref <tls::TLSSocket> tlsSocket =
tlsSession->getSocket(m_socket);
@@ -463,7 +463,7 @@ void SMTPTransport::startTLS()
throw exceptions::command_error("STARTTLS", resp->getText());
ref <tls::TLSSession> tlsSession =
- vmime::create <tls::TLSSession>(getCertificateVerifier());
+ tls::TLSSession::create(getCertificateVerifier());
ref <tls::TLSSocket> tlsSocket =
tlsSession->getSocket(m_socket);
diff --git a/src/net/tls/TLSSession.cpp b/src/net/tls/TLSSession.cpp
index 31149396..44e8a0f4 100644
--- a/src/net/tls/TLSSession.cpp
+++ b/src/net/tls/TLSSession.cpp
@@ -24,268 +24,19 @@
#include "vmime/config.hpp"
-#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS
+#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT
-#include <gnutls/gnutls.h>
-#if GNUTLS_VERSION_NUMBER < 0x030000
-#include <gnutls/extra.h>
-#endif
-
-
-// Dependency on gcrypt is not needed since GNU TLS version 2.12.
-// See here: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=638651
-#if GNUTLS_VERSION_NUMBER <= 0x020b00
-# define VMIME_GNUTLS_NEEDS_GCRYPT 1
-#endif
-
-#if VMIME_HAVE_PTHREAD
-# include <pthread.h>
-# if VMIME_GNUTLS_NEEDS_GCRYPT
-# include <gcrypt.h>
-# endif
-# include <errno.h>
-#endif // VMIME_HAVE_PTHREAD
-
#include "vmime/net/tls/TLSSession.hpp"
-#include "vmime/exception.hpp"
-
-
-// Enable GnuTLS debugging by defining GNUTLS_DEBUG
-//#define GNUTLS_DEBUG 1
-
-
-#include <sstream>
-#include <iomanip>
-
-#if VMIME_DEBUG && GNUTLS_DEBUG
- #include <iostream>
-#endif // VMIME_DEBUG && GNUTLS_DEBUG
-
-
-#if VMIME_HAVE_PTHREAD && VMIME_GNUTLS_NEEDS_GCRYPT && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL)
-extern "C"
-{
- GCRY_THREAD_OPTION_PTHREAD_IMPL;
-}
-#endif // VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL
-
namespace vmime {
namespace net {
namespace tls {
-#ifndef VMIME_BUILDING_DOC
-
-// Initialize GNU TLS library
-struct TLSGlobal
-{
- TLSGlobal()
- {
-#if VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL)
- #if VMIME_GNUTLS_NEEDS_GCRYPT
- gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
- #endif // VMIME_GNUTLS_NEEDS_GCRYPT
-#endif // VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL
-
- gnutls_global_init();
- //gnutls_global_init_extra();
-
-#if VMIME_DEBUG && GNUTLS_DEBUG
- gnutls_global_set_log_function(TLSLogFunc);
- gnutls_global_set_log_level(10);
-#endif // VMIME_DEBUG && GNUTLS_DEBUG
-
- gnutls_anon_allocate_client_credentials(&anonCred);
- gnutls_certificate_allocate_credentials(&certCred);
- }
-
- ~TLSGlobal()
- {
- gnutls_anon_free_client_credentials(anonCred);
- gnutls_certificate_free_credentials(certCred);
-
- gnutls_global_deinit();
- }
-
-#if VMIME_DEBUG && GNUTLS_DEBUG
-
- static void TLSLogFunc(int level, const char *str)
- {
- std::cerr << "GNUTLS: [" << level << "] " << str << std::endl;
- }
-
-#endif // VMIME_DEBUG && GNUTLS_DEBUG
-
-
- gnutls_anon_client_credentials anonCred;
- gnutls_certificate_credentials certCred;
-};
-
-static TLSGlobal g_gnutlsGlobal;
-
-
-#endif // VMIME_BUILDING_DOC
-
-
-
-TLSSession::TLSSession(ref <security::cert::certificateVerifier> cv)
- : m_certVerifier(cv)
+TLSSession::TLSSession()
{
- int res;
-
- m_gnutlsSession = new gnutls_session;
-
- if (gnutls_init(m_gnutlsSession, GNUTLS_CLIENT) != 0)
- throw std::bad_alloc();
-
- // Sets some default priority on the ciphers, key exchange methods,
- // macs and compression methods.
-#if VMIME_HAVE_GNUTLS_PRIORITY_FUNCS
- gnutls_dh_set_prime_bits(*m_gnutlsSession, 128);
-
- if ((res = gnutls_priority_set_direct
- (*m_gnutlsSession, "NORMAL:%SSL3_RECORD_VERSION", NULL)) != 0)
- {
- if ((res = gnutls_priority_set_direct
- (*m_gnutlsSession, "NORMAL", NULL)) != 0)
- {
- throwTLSException
- ("gnutls_priority_set_direct", res);
- }
- }
-
-#else // !VMIME_HAVE_GNUTLS_PRIORITY_FUNCS
-
- gnutls_set_default_priority(*m_gnutlsSession);
-
- // Sets the priority on the certificate types supported by gnutls.
- // Priority is higher for types specified before others. After
- // specifying the types you want, you must append a 0.
- const int certTypePriority[] = { GNUTLS_CRT_X509, 0 };
-
- res = gnutls_certificate_type_set_priority
- (*m_gnutlsSession, certTypePriority);
-
- if (res < 0)
- {
- throwTLSException
- ("gnutls_certificate_type_set_priority", res);
- }
-
- // Sets the priority on the protocol types
- const int protoPriority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
-
- res = gnutls_protocol_set_priority(*m_gnutlsSession, protoPriority);
-
- if (res < 0)
- {
- throwTLSException
- ("gnutls_certificate_type_set_priority", res);
- }
-
- // Priority on the ciphers
- const int cipherPriority[] =
- {
- GNUTLS_CIPHER_ARCFOUR_128,
- GNUTLS_CIPHER_3DES_CBC,
- GNUTLS_CIPHER_AES_128_CBC,
- GNUTLS_CIPHER_AES_256_CBC,
- GNUTLS_CIPHER_ARCFOUR_40,
- GNUTLS_CIPHER_RC2_40_CBC,
- GNUTLS_CIPHER_DES_CBC,
- 0
- };
-
- gnutls_cipher_set_priority(*m_gnutlsSession, cipherPriority);
-
- // Priority on MACs
- const int macPriority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0};
-
- gnutls_mac_set_priority(*m_gnutlsSession, macPriority);
-
- // Priority on key exchange methods
- const int kxPriority[] =
- {
- GNUTLS_KX_RSA,
- GNUTLS_KX_DHE_DSS,
- GNUTLS_KX_DHE_RSA,
- GNUTLS_KX_ANON_DH,
- GNUTLS_KX_SRP,
- GNUTLS_KX_RSA_EXPORT,
- GNUTLS_KX_SRP_RSA,
- GNUTLS_KX_SRP_DSS,
- 0
- };
-
- gnutls_kx_set_priority(*m_gnutlsSession, kxPriority);
-
- // Priority on compression methods
- const int compressionPriority[] =
- {
- GNUTLS_COMP_ZLIB,
- //GNUTLS_COMP_LZO,
- GNUTLS_COMP_NULL,
- 0
- };
-
- gnutls_compression_set_priority(*m_gnutlsSession, compressionPriority);
-
-#endif // !VMIME_HAVE_GNUTLS_PRIORITY_FUNCS
-
- // Initialize credentials
- gnutls_credentials_set(*m_gnutlsSession,
- GNUTLS_CRD_ANON, g_gnutlsGlobal.anonCred);
-
- gnutls_credentials_set(*m_gnutlsSession,
- GNUTLS_CRD_CERTIFICATE, g_gnutlsGlobal.certCred);
-}
-
-
-TLSSession::TLSSession(const TLSSession&)
- : object()
-{
- // Not used
-}
-
-
-TLSSession::~TLSSession()
-{
- if (m_gnutlsSession)
- {
- gnutls_deinit(*m_gnutlsSession);
-
- delete m_gnutlsSession;
- m_gnutlsSession = NULL;
- }
-}
-
-
-ref <TLSSocket> TLSSession::getSocket(ref <socket> sok)
-{
- return vmime::create <TLSSocket>
- (thisRef().dynamicCast <TLSSession>(), sok);
-}
-
-
-ref <security::cert::certificateVerifier> TLSSession::getCertificateVerifier()
-{
- return m_certVerifier;
-}
-
-
-void TLSSession::throwTLSException(const string& fname, const int code)
-{
- std::ostringstream msg;
-
- msg << fname + "() returned code ";
- msg << std::hex << code;
- msg << ": ";
- msg << gnutls_strerror(code);
-
- throw exceptions::tls_exception(msg.str());
}
@@ -294,5 +45,4 @@ void TLSSession::throwTLSException(const string& fname, const int code)
} // vmime
-#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS
-
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT
diff --git a/src/net/tls/TLSSocket.cpp b/src/net/tls/TLSSocket.cpp
index 6fa8d02d..266834bf 100644
--- a/src/net/tls/TLSSocket.cpp
+++ b/src/net/tls/TLSSocket.cpp
@@ -24,18 +24,10 @@
#include "vmime/config.hpp"
-#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS
+#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT
-#include <gnutls/gnutls.h>
-#include <gnutls/x509.h>
-
#include "vmime/net/tls/TLSSocket.hpp"
-#include "vmime/net/tls/TLSSession.hpp"
-
-#include "vmime/platform.hpp"
-
-#include "vmime/security/cert/X509Certificate.hpp"
namespace vmime {
@@ -43,375 +35,10 @@ namespace net {
namespace tls {
-TLSSocket::TLSSocket(ref <TLSSession> session, ref <socket> sok)
- : m_session(session), m_wrapped(sok), m_connected(false),
- m_handshaking(false), m_ex(NULL)
-{
- gnutls_transport_set_ptr(*m_session->m_gnutlsSession, this);
-
- gnutls_transport_set_push_function(*m_session->m_gnutlsSession, gnutlsPushFunc);
- gnutls_transport_set_pull_function(*m_session->m_gnutlsSession, gnutlsPullFunc);
-}
-
-
-TLSSocket::~TLSSocket()
-{
- if (m_ex)
- {
- delete m_ex;
- m_ex = NULL;
- }
-
- try
- {
- disconnect();
- }
- catch (...)
- {
- // Don't throw exception in destructor
- }
-}
-
-
-void TLSSocket::connect(const string& address, const port_t port)
-{
- m_wrapped->connect(address, port);
-
- handshake(NULL);
-
- m_connected = true;
-}
-
-
-void TLSSocket::disconnect()
-{
- if (m_connected)
- {
- gnutls_bye(*m_session->m_gnutlsSession, GNUTLS_SHUT_RDWR);
-
- m_wrapped->disconnect();
-
- m_connected = false;
- }
-}
-
-
-bool TLSSocket::isConnected() const
-{
- return m_wrapped->isConnected() && m_connected;
-}
-
-
-TLSSocket::size_type TLSSocket::getBlockSize() const
-{
- return 16384; // 16 KB
-}
-
-
-void TLSSocket::receive(string& buffer)
-{
- const int size = receiveRaw(m_buffer, sizeof(m_buffer));
- buffer = vmime::string(m_buffer, size);
-}
-
-
-void TLSSocket::send(const string& buffer)
-{
- sendRaw(buffer.data(), buffer.length());
-}
-
-
-TLSSocket::size_type TLSSocket::receiveRaw(char* buffer, const size_type count)
-{
- const ssize_t ret = gnutls_record_recv
- (*m_session->m_gnutlsSession,
- buffer, static_cast <size_t>(count));
-
- if (m_ex)
- internalThrow();
-
- if (ret < 0)
- {
- if (ret == GNUTLS_E_AGAIN)
- return 0;
-
- TLSSession::throwTLSException("gnutls_record_recv", ret);
- }
-
- return static_cast <int>(ret);
-}
-
-
-void TLSSocket::sendRaw(const char* buffer, const size_type count)
-{
- gnutls_record_send
- (*m_session->m_gnutlsSession,
- buffer, static_cast <size_t>(count));
-
- if (m_ex)
- internalThrow();
-}
-
-
-void TLSSocket::handshake(ref <timeoutHandler> toHandler)
-{
- if (toHandler)
- toHandler->resetTimeOut();
-
- // Start handshaking process
- m_handshaking = true;
- m_toHandler = toHandler;
-
- try
- {
- while (true)
- {
- const int ret = gnutls_handshake(*m_session->m_gnutlsSession);
-
- if (m_ex)
- internalThrow();
-
- if (ret < 0)
- {
- if (ret == GNUTLS_E_AGAIN ||
- ret == GNUTLS_E_INTERRUPTED)
- {
- // Non-fatal error
- platform::getHandler()->wait();
- }
- else
- {
- TLSSession::throwTLSException("gnutls_handshake", ret);
- }
- }
- else
- {
- // Successful handshake
- break;
- }
- }
- }
- catch (...)
- {
- m_handshaking = false;
- m_toHandler = NULL;
-
- throw;
- }
-
- m_handshaking = false;
- m_toHandler = NULL;
-
- // Verify server's certificate(s)
- ref <security::cert::certificateChain> certs = getPeerCertificates();
-
- if (certs == NULL)
- throw exceptions::tls_exception("No peer certificate.");
-
- m_session->getCertificateVerifier()->verify(certs);
-
- m_connected = true;
-}
-
-
-ssize_t TLSSocket::gnutlsPushFunc
- (gnutls_transport_ptr trspt, const void* data, size_t len)
-{
- TLSSocket* sok = reinterpret_cast <TLSSocket*>(trspt);
-
- try
- {
- sok->m_wrapped->sendRaw
- (reinterpret_cast <const char*>(data), static_cast <int>(len));
- }
- catch (exception& e)
- {
- // Workaround for bad behaviour when throwing C++ exceptions
- // from C functions (GNU TLS)
- sok->m_ex = e.clone();
- return -1;
- }
-
- return len;
-}
-
-
-ssize_t TLSSocket::gnutlsPullFunc
- (gnutls_transport_ptr trspt, void* data, size_t len)
-{
- TLSSocket* sok = reinterpret_cast <TLSSocket*>(trspt);
-
- try
- {
- // Workaround for cross-platform asynchronous handshaking:
- // gnutls_handshake() only returns GNUTLS_E_AGAIN if recv()
- // returns -1 and errno is set to EGAIN...
- if (sok->m_handshaking)
- {
- while (true)
- {
- const ssize_t ret = static_cast <ssize_t>
- (sok->m_wrapped->receiveRaw
- (reinterpret_cast <char*>(data),
- static_cast <int>(len)));
-
- if (ret == 0)
- {
- // No data available yet
- platform::getHandler()->wait();
- }
- else
- {
- return ret;
- }
-
- // Check whether the time-out delay is elapsed
- if (sok->m_toHandler && sok->m_toHandler->isTimeOut())
- {
- if (!sok->m_toHandler->handleTimeOut())
- throw exceptions::operation_timed_out();
-
- sok->m_toHandler->resetTimeOut();
- }
- }
- }
- else
- {
- const ssize_t n = static_cast <ssize_t>
- (sok->m_wrapped->receiveRaw
- (reinterpret_cast <char*>(data),
- static_cast <int>(len)));
-
- if (n == 0)
- return GNUTLS_E_AGAIN; // This seems like a hack, really...
-
- return n;
- }
- }
- catch (exception& e)
- {
- // Workaround for bad behaviour when throwing C++ exceptions
- // from C functions (GNU TLS)
- sok->m_ex = e.clone();
- return -1;
- }
-}
-
-
-ref <security::cert::certificateChain> TLSSocket::getPeerCertificates() const
-{
- unsigned int certCount = 0;
- const gnutls_datum* rawData = gnutls_certificate_get_peers
- (*m_session->m_gnutlsSession, &certCount);
-
- if (rawData == NULL)
- return NULL;
-
- // Try X.509
- gnutls_x509_crt* x509Certs = new gnutls_x509_crt[certCount];
-
- for (unsigned int i = 0; i < certCount; ++i)
- {
- gnutls_x509_crt_init(x509Certs + i);
-
- int res = gnutls_x509_crt_import(x509Certs[i], rawData + i,
- GNUTLS_X509_FMT_DER);
-
- if (res < 0)
- {
- // XXX more fine-grained error reporting?
- delete [] x509Certs;
- return NULL;
- }
- }
-
- {
- std::vector <ref <security::cert::certificate> > certs;
- bool error = false;
-
- for (unsigned int i = 0 ; i < certCount ; ++i)
- {
- size_t dataSize = 0;
-
- gnutls_x509_crt_export(x509Certs[i],
- GNUTLS_X509_FMT_DER, NULL, &dataSize);
-
- std::vector <byte_t> data(dataSize);
-
- gnutls_x509_crt_export(x509Certs[i],
- GNUTLS_X509_FMT_DER, &data[0], &dataSize);
-
- ref <security::cert::X509Certificate> cert =
- security::cert::X509Certificate::import(&data[0], dataSize);
-
- if (cert != NULL)
- certs.push_back(cert);
- else
- error = true;
-
- gnutls_x509_crt_deinit(x509Certs[i]);
- }
-
- delete [] x509Certs;
-
- if (error)
- return NULL;
-
- return vmime::create <security::cert::certificateChain>(certs);
- }
-
- delete [] x509Certs;
-
- return NULL;
-}
-
-
-// Following is a workaround for C++ exceptions to pass correctly between
-// C and C++ calls.
-//
-// gnutls_record_recv() calls TLSSocket::gnutlsPullFunc, and exceptions
-// thrown by the socket can not be caught.
-
-#ifndef VMIME_BUILDING_DOC
-
-class TLSSocket_DeleteExWrapper : public object
-{
-public:
-
- TLSSocket_DeleteExWrapper(exception* ex) : m_ex(ex) { }
- ~TLSSocket_DeleteExWrapper() { delete m_ex; }
-
-private:
-
- exception* m_ex;
-};
-
-#endif // VMIME_BUILDING_DOC
-
-
-void TLSSocket::internalThrow()
-{
- static std::vector <ref <TLSSocket_DeleteExWrapper> > exToDelete;
-
- if (m_ex)
- {
- // Reset the current exception pointer to prevent the same
- // exception from being thrown again later
- exception* ex = m_ex;
- m_ex = NULL;
-
- // To avoid memory leaks
- exToDelete.push_back(vmime::create <TLSSocket_DeleteExWrapper>(ex));
-
- throw *ex;
- }
-}
-
-
} // tls
} // net
} // vmime
-#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT
diff --git a/src/net/tls/gnutls/TLSSession_GnuTLS.cpp b/src/net/tls/gnutls/TLSSession_GnuTLS.cpp
new file mode 100644
index 00000000..644a74ec
--- /dev/null
+++ b/src/net/tls/gnutls/TLSSession_GnuTLS.cpp
@@ -0,0 +1,304 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2009 Vincent Richard <[email protected]>
+//
+// 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 the Free Software Foundation; either version 3 of
+// the License, or (at your option) any later version.
+//
+// This program 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
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// Linking this library statically or dynamically with other modules is making
+// a combined work based on this library. Thus, the terms and conditions of
+// the GNU General Public License cover the whole combination.
+//
+
+#include "vmime/config.hpp"
+
+
+#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS
+
+
+#include <gnutls/gnutls.h>
+#if GNUTLS_VERSION_NUMBER < 0x030000
+#include <gnutls/extra.h>
+#endif
+
+
+// Dependency on gcrypt is not needed since GNU TLS version 2.12.
+// See here: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=638651
+#if GNUTLS_VERSION_NUMBER <= 0x020b00
+# define VMIME_GNUTLS_NEEDS_GCRYPT 1
+#endif
+
+#if VMIME_HAVE_PTHREAD
+# include <pthread.h>
+# if VMIME_GNUTLS_NEEDS_GCRYPT
+# include <gcrypt.h>
+# endif
+# include <errno.h>
+#endif // VMIME_HAVE_PTHREAD
+
+#include "vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp"
+#include "vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp"
+
+#include "vmime/exception.hpp"
+
+
+// Enable GnuTLS debugging by defining GNUTLS_DEBUG
+//#define GNUTLS_DEBUG 1
+
+
+#include <sstream>
+#include <iomanip>
+
+#if VMIME_DEBUG && GNUTLS_DEBUG
+ #include <iostream>
+#endif // VMIME_DEBUG && GNUTLS_DEBUG
+
+
+#if VMIME_HAVE_PTHREAD && VMIME_GNUTLS_NEEDS_GCRYPT && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL)
+extern "C"
+{
+ GCRY_THREAD_OPTION_PTHREAD_IMPL;
+}
+#endif // VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL
+
+
+namespace vmime {
+namespace net {
+namespace tls {
+
+
+#ifndef VMIME_BUILDING_DOC
+
+// Initialize GNU TLS library
+struct TLSGlobal
+{
+ TLSGlobal()
+ {
+#if VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL)
+ #if VMIME_GNUTLS_NEEDS_GCRYPT
+ gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
+ #endif // VMIME_GNUTLS_NEEDS_GCRYPT
+#endif // VMIME_HAVE_PTHREAD && defined(GCRY_THREAD_OPTION_PTHREAD_IMPL
+
+ gnutls_global_init();
+ //gnutls_global_init_extra();
+
+#if VMIME_DEBUG && GNUTLS_DEBUG
+ gnutls_global_set_log_function(TLSLogFunc);
+ gnutls_global_set_log_level(10);
+#endif // VMIME_DEBUG && GNUTLS_DEBUG
+
+ gnutls_anon_allocate_client_credentials(&anonCred);
+ gnutls_certificate_allocate_credentials(&certCred);
+ }
+
+ ~TLSGlobal()
+ {
+ gnutls_anon_free_client_credentials(anonCred);
+ gnutls_certificate_free_credentials(certCred);
+
+ gnutls_global_deinit();
+ }
+
+#if VMIME_DEBUG && GNUTLS_DEBUG
+
+ static void TLSLogFunc(int level, const char *str)
+ {
+ std::cerr << "GNUTLS: [" << level << "] " << str << std::endl;
+ }
+
+#endif // VMIME_DEBUG && GNUTLS_DEBUG
+
+
+ gnutls_anon_client_credentials anonCred;
+ gnutls_certificate_credentials certCred;
+};
+
+static TLSGlobal g_gnutlsGlobal;
+
+
+#endif // VMIME_BUILDING_DOC
+
+
+
+// static
+ref <TLSSession> TLSSession::create(ref <security::cert::certificateVerifier> cv)
+{
+ return vmime::create <TLSSession_GnuTLS>(cv);
+}
+
+
+TLSSession_GnuTLS::TLSSession_GnuTLS(ref <security::cert::certificateVerifier> cv)
+ : m_certVerifier(cv)
+{
+ int res;
+
+ m_gnutlsSession = new gnutls_session;
+
+ if (gnutls_init(m_gnutlsSession, GNUTLS_CLIENT) != 0)
+ throw std::bad_alloc();
+
+ // Sets some default priority on the ciphers, key exchange methods,
+ // macs and compression methods.
+#if HAVE_GNUTLS_PRIORITY_FUNCS
+ gnutls_dh_set_prime_bits(*m_gnutlsSession, 128);
+
+ if ((res = gnutls_priority_set_direct
+ (*m_gnutlsSession, "NORMAL:%SSL3_RECORD_VERSION", NULL)) != 0)
+ {
+ if ((res = gnutls_priority_set_direct
+ (*m_gnutlsSession, "NORMAL", NULL)) != 0)
+ {
+ throwTLSException
+ ("gnutls_priority_set_direct", res);
+ }
+ }
+
+#else // !HAVE_GNUTLS_PRIORITY_FUNCS
+
+ gnutls_set_default_priority(*m_gnutlsSession);
+
+ // Sets the priority on the certificate types supported by gnutls.
+ // Priority is higher for types specified before others. After
+ // specifying the types you want, you must append a 0.
+ const int certTypePriority[] = { GNUTLS_CRT_X509, 0 };
+
+ res = gnutls_certificate_type_set_priority
+ (*m_gnutlsSession, certTypePriority);
+
+ if (res < 0)
+ {
+ throwTLSException
+ ("gnutls_certificate_type_set_priority", res);
+ }
+
+ // Sets the priority on the protocol types
+ const int protoPriority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
+
+ res = gnutls_protocol_set_priority(*m_gnutlsSession, protoPriority);
+
+ if (res < 0)
+ {
+ throwTLSException
+ ("gnutls_certificate_type_set_priority", res);
+ }
+
+ // Priority on the ciphers
+ const int cipherPriority[] =
+ {
+ GNUTLS_CIPHER_ARCFOUR_128,
+ GNUTLS_CIPHER_3DES_CBC,
+ GNUTLS_CIPHER_AES_128_CBC,
+ GNUTLS_CIPHER_AES_256_CBC,
+ GNUTLS_CIPHER_ARCFOUR_40,
+ GNUTLS_CIPHER_RC2_40_CBC,
+ GNUTLS_CIPHER_DES_CBC,
+ 0
+ };
+
+ gnutls_cipher_set_priority(*m_gnutlsSession, cipherPriority);
+
+ // Priority on MACs
+ const int macPriority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0};
+
+ gnutls_mac_set_priority(*m_gnutlsSession, macPriority);
+
+ // Priority on key exchange methods
+ const int kxPriority[] =
+ {
+ GNUTLS_KX_RSA,
+ GNUTLS_KX_DHE_DSS,
+ GNUTLS_KX_DHE_RSA,
+ GNUTLS_KX_ANON_DH,
+ GNUTLS_KX_SRP,
+ GNUTLS_KX_RSA_EXPORT,
+ GNUTLS_KX_SRP_RSA,
+ GNUTLS_KX_SRP_DSS,
+ 0
+ };
+
+ gnutls_kx_set_priority(*m_gnutlsSession, kxPriority);
+
+ // Priority on compression methods
+ const int compressionPriority[] =
+ {
+ GNUTLS_COMP_ZLIB,
+ //GNUTLS_COMP_LZO,
+ GNUTLS_COMP_NULL,
+ 0
+ };
+
+ gnutls_compression_set_priority(*m_gnutlsSession, compressionPriority);
+
+#endif // !HAVE_GNUTLS_PRIORITY_FUNCS
+
+ // Initialize credentials
+ gnutls_credentials_set(*m_gnutlsSession,
+ GNUTLS_CRD_ANON, g_gnutlsGlobal.anonCred);
+
+ gnutls_credentials_set(*m_gnutlsSession,
+ GNUTLS_CRD_CERTIFICATE, g_gnutlsGlobal.certCred);
+}
+
+
+TLSSession_GnuTLS::TLSSession_GnuTLS(const TLSSession_GnuTLS&)
+ : TLSSession()
+{
+ // Not used
+}
+
+
+TLSSession_GnuTLS::~TLSSession_GnuTLS()
+{
+ if (m_gnutlsSession)
+ {
+ gnutls_deinit(*m_gnutlsSession);
+
+ delete m_gnutlsSession;
+ m_gnutlsSession = NULL;
+ }
+}
+
+
+ref <TLSSocket> TLSSession_GnuTLS::getSocket(ref <socket> sok)
+{
+ return TLSSocket::wrap(thisRef().dynamicCast <TLSSession>(), sok);
+}
+
+
+ref <security::cert::certificateVerifier> TLSSession_GnuTLS::getCertificateVerifier()
+{
+ return m_certVerifier;
+}
+
+
+void TLSSession_GnuTLS::throwTLSException(const string& fname, const int code)
+{
+ std::ostringstream msg;
+
+ msg << fname + "() returned code ";
+ msg << std::hex << code;
+ msg << ": ";
+ msg << gnutls_strerror(code);
+
+ throw exceptions::tls_exception(msg.str());
+}
+
+
+} // tls
+} // net
+} // vmime
+
+
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS
diff --git a/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp b/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp
new file mode 100644
index 00000000..35e1d415
--- /dev/null
+++ b/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp
@@ -0,0 +1,424 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2009 Vincent Richard <[email protected]>
+//
+// 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 the Free Software Foundation; either version 3 of
+// the License, or (at your option) any later version.
+//
+// This program 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
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// Linking this library statically or dynamically with other modules is making
+// a combined work based on this library. Thus, the terms and conditions of
+// the GNU General Public License cover the whole combination.
+//
+
+#include "vmime/config.hpp"
+
+
+#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS
+
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+#include "vmime/net/tls/gnutls/TLSSocket_GnuTLS.hpp"
+#include "vmime/net/tls/gnutls/TLSSession_GnuTLS.hpp"
+
+#include "vmime/platform.hpp"
+
+#include "vmime/security/cert/X509Certificate.hpp"
+
+
+namespace vmime {
+namespace net {
+namespace tls {
+
+
+// static
+ref <TLSSocket> TLSSocket::wrap(ref <TLSSession> session, ref <socket> sok)
+{
+ return vmime::create <TLSSocket_GnuTLS>
+ (session.dynamicCast <TLSSession_GnuTLS>(), sok);
+}
+
+
+TLSSocket_GnuTLS::TLSSocket_GnuTLS(ref <TLSSession_GnuTLS> session, ref <socket> sok)
+ : m_session(session), m_wrapped(sok), m_connected(false),
+ m_handshaking(false), m_ex(NULL)
+{
+ gnutls_transport_set_ptr(*m_session->m_gnutlsSession, this);
+
+ gnutls_transport_set_push_function(*m_session->m_gnutlsSession, gnutlsPushFunc);
+ gnutls_transport_set_pull_function(*m_session->m_gnutlsSession, gnutlsPullFunc);
+}
+
+
+TLSSocket_GnuTLS::~TLSSocket_GnuTLS()
+{
+ if (m_ex)
+ {
+ delete m_ex;
+ m_ex = NULL;
+ }
+
+ try
+ {
+ disconnect();
+ }
+ catch (...)
+ {
+ // Don't throw exception in destructor
+ }
+}
+
+
+void TLSSocket_GnuTLS::connect(const string& address, const port_t port)
+{
+ m_wrapped->connect(address, port);
+
+ handshake(NULL);
+
+ m_connected = true;
+}
+
+
+void TLSSocket_GnuTLS::disconnect()
+{
+ if (m_connected)
+ {
+ gnutls_bye(*m_session->m_gnutlsSession, GNUTLS_SHUT_RDWR);
+
+ m_wrapped->disconnect();
+
+ m_connected = false;
+ }
+}
+
+
+bool TLSSocket_GnuTLS::isConnected() const
+{
+ return m_wrapped->isConnected() && m_connected;
+}
+
+
+TLSSocket::size_type TLSSocket_GnuTLS::getBlockSize() const
+{
+ return 16384; // 16 KB
+}
+
+
+void TLSSocket_GnuTLS::receive(string& buffer)
+{
+ const int size = receiveRaw(m_buffer, sizeof(m_buffer));
+ buffer = vmime::string(m_buffer, size);
+}
+
+
+void TLSSocket_GnuTLS::send(const string& buffer)
+{
+ sendRaw(buffer.data(), buffer.length());
+}
+
+
+TLSSocket::size_type TLSSocket_GnuTLS::receiveRaw(char* buffer, const size_type count)
+{
+ const ssize_t ret = gnutls_record_recv
+ (*m_session->m_gnutlsSession,
+ buffer, static_cast <size_t>(count));
+
+ if (m_ex)
+ internalThrow();
+
+ if (ret < 0)
+ {
+ if (ret == GNUTLS_E_AGAIN)
+ return 0;
+
+ TLSSession_GnuTLS::throwTLSException("gnutls_record_recv", ret);
+ }
+
+ return static_cast <int>(ret);
+}
+
+
+void TLSSocket_GnuTLS::sendRaw(const char* buffer, const size_type count)
+{
+ gnutls_record_send
+ (*m_session->m_gnutlsSession,
+ buffer, static_cast <size_t>(count));
+
+ if (m_ex)
+ internalThrow();
+}
+
+
+void TLSSocket_GnuTLS::handshake(ref <timeoutHandler> toHandler)
+{
+ if (toHandler)
+ toHandler->resetTimeOut();
+
+ // Start handshaking process
+ m_handshaking = true;
+ m_toHandler = toHandler;
+
+ try
+ {
+ while (true)
+ {
+ const int ret = gnutls_handshake(*m_session->m_gnutlsSession);
+
+ if (m_ex)
+ internalThrow();
+
+ if (ret < 0)
+ {
+ if (ret == GNUTLS_E_AGAIN ||
+ ret == GNUTLS_E_INTERRUPTED)
+ {
+ // Non-fatal error
+ platform::getHandler()->wait();
+ }
+ else
+ {
+ TLSSession_GnuTLS::throwTLSException("gnutls_handshake", ret);
+ }
+ }
+ else
+ {
+ // Successful handshake
+ break;
+ }
+ }
+ }
+ catch (...)
+ {
+ m_handshaking = false;
+ m_toHandler = NULL;
+
+ throw;
+ }
+
+ m_handshaking = false;
+ m_toHandler = NULL;
+
+ // Verify server's certificate(s)
+ ref <security::cert::certificateChain> certs = getPeerCertificates();
+
+ if (certs == NULL)
+ throw exceptions::tls_exception("No peer certificate.");
+
+ m_session->getCertificateVerifier()->verify(certs);
+
+ m_connected = true;
+}
+
+
+ssize_t TLSSocket_GnuTLS::gnutlsPushFunc
+ (gnutls_transport_ptr trspt, const void* data, size_t len)
+{
+ TLSSocket_GnuTLS* sok = reinterpret_cast <TLSSocket_GnuTLS*>(trspt);
+
+ try
+ {
+ sok->m_wrapped->sendRaw
+ (reinterpret_cast <const char*>(data), static_cast <int>(len));
+ }
+ catch (exception& e)
+ {
+ // Workaround for bad behaviour when throwing C++ exceptions
+ // from C functions (GNU TLS)
+ sok->m_ex = e.clone();
+ return -1;
+ }
+
+ return len;
+}
+
+
+ssize_t TLSSocket_GnuTLS::gnutlsPullFunc
+ (gnutls_transport_ptr trspt, void* data, size_t len)
+{
+ TLSSocket_GnuTLS* sok = reinterpret_cast <TLSSocket_GnuTLS*>(trspt);
+
+ try
+ {
+ // Workaround for cross-platform asynchronous handshaking:
+ // gnutls_handshake() only returns GNUTLS_E_AGAIN if recv()
+ // returns -1 and errno is set to EGAIN...
+ if (sok->m_handshaking)
+ {
+ while (true)
+ {
+ const ssize_t ret = static_cast <ssize_t>
+ (sok->m_wrapped->receiveRaw
+ (reinterpret_cast <char*>(data),
+ static_cast <int>(len)));
+
+ if (ret == 0)
+ {
+ // No data available yet
+ platform::getHandler()->wait();
+ }
+ else
+ {
+ return ret;
+ }
+
+ // Check whether the time-out delay is elapsed
+ if (sok->m_toHandler && sok->m_toHandler->isTimeOut())
+ {
+ if (!sok->m_toHandler->handleTimeOut())
+ throw exceptions::operation_timed_out();
+
+ sok->m_toHandler->resetTimeOut();
+ }
+ }
+ }
+ else
+ {
+ const ssize_t n = static_cast <ssize_t>
+ (sok->m_wrapped->receiveRaw
+ (reinterpret_cast <char*>(data),
+ static_cast <int>(len)));
+
+ if (n == 0)
+ return GNUTLS_E_AGAIN; // This seems like a hack, really...
+
+ return n;
+ }
+ }
+ catch (exception& e)
+ {
+ // Workaround for bad behaviour when throwing C++ exceptions
+ // from C functions (GNU TLS)
+ sok->m_ex = e.clone();
+ return -1;
+ }
+}
+
+
+ref <security::cert::certificateChain> TLSSocket_GnuTLS::getPeerCertificates() const
+{
+ unsigned int certCount = 0;
+ const gnutls_datum* rawData = gnutls_certificate_get_peers
+ (*m_session->m_gnutlsSession, &certCount);
+
+ if (rawData == NULL)
+ return NULL;
+
+ // Try X.509
+ gnutls_x509_crt* x509Certs = new gnutls_x509_crt[certCount];
+
+ for (unsigned int i = 0; i < certCount; ++i)
+ {
+ gnutls_x509_crt_init(x509Certs + i);
+
+ int res = gnutls_x509_crt_import(x509Certs[i], rawData + i,
+ GNUTLS_X509_FMT_DER);
+
+ if (res < 0)
+ {
+ // XXX more fine-grained error reporting?
+ delete [] x509Certs;
+ return NULL;
+ }
+ }
+
+ {
+ std::vector <ref <security::cert::certificate> > certs;
+ bool error = false;
+
+ for (unsigned int i = 0 ; i < certCount ; ++i)
+ {
+ size_t dataSize = 0;
+
+ gnutls_x509_crt_export(x509Certs[i],
+ GNUTLS_X509_FMT_DER, NULL, &dataSize);
+
+ std::vector <byte_t> data(dataSize);
+
+ gnutls_x509_crt_export(x509Certs[i],
+ GNUTLS_X509_FMT_DER, &data[0], &dataSize);
+
+ ref <security::cert::X509Certificate> cert =
+ security::cert::X509Certificate::import(&data[0], dataSize);
+
+ if (cert != NULL)
+ certs.push_back(cert);
+ else
+ error = true;
+
+ gnutls_x509_crt_deinit(x509Certs[i]);
+ }
+
+ delete [] x509Certs;
+
+ if (error)
+ return NULL;
+
+ return vmime::create <security::cert::certificateChain>(certs);
+ }
+
+ delete [] x509Certs;
+
+ return NULL;
+}
+
+
+// Following is a workaround for C++ exceptions to pass correctly between
+// C and C++ calls.
+//
+// gnutls_record_recv() calls TLSSocket::gnutlsPullFunc, and exceptions
+// thrown by the socket can not be caught.
+
+#ifndef VMIME_BUILDING_DOC
+
+class TLSSocket_DeleteExWrapper : public object
+{
+public:
+
+ TLSSocket_DeleteExWrapper(exception* ex) : m_ex(ex) { }
+ ~TLSSocket_DeleteExWrapper() { delete m_ex; }
+
+private:
+
+ exception* m_ex;
+};
+
+#endif // VMIME_BUILDING_DOC
+
+
+void TLSSocket_GnuTLS::internalThrow()
+{
+ static std::vector <ref <TLSSocket_DeleteExWrapper> > exToDelete;
+
+ if (m_ex)
+ {
+ // Reset the current exception pointer to prevent the same
+ // exception from being thrown again later
+ exception* ex = m_ex;
+ m_ex = NULL;
+
+ // To avoid memory leaks
+ exToDelete.push_back(vmime::create <TLSSocket_DeleteExWrapper>(ex));
+
+ throw *ex;
+ }
+}
+
+
+} // tls
+} // net
+} // vmime
+
+
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_GNUTLS
diff --git a/src/net/tls/openssl/OpenSSLInitializer.cpp b/src/net/tls/openssl/OpenSSLInitializer.cpp
new file mode 100755
index 00000000..ec23ceb4
--- /dev/null
+++ b/src/net/tls/openssl/OpenSSLInitializer.cpp
@@ -0,0 +1,152 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2009 Vincent Richard <[email protected]>
+//
+// 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 the Free Software Foundation; either version 3 of
+// the License, or (at your option) any later version.
+//
+// This program 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
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// Linking this library statically or dynamically with other modules is making
+// a combined work based on this library. Thus, the terms and conditions of
+// the GNU General Public License cover the whole combination.
+//
+
+#include "vmime/config.hpp"
+
+
+#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL
+
+
+#include "vmime/net/tls/openssl/OpenSSLInitializer.hpp"
+
+#include "vmime/utility/sync/autoLock.hpp"
+#include "vmime/utility/sync/criticalSection.hpp"
+
+#include "vmime/platform.hpp"
+
+#include <openssl/ssl.h>
+#include <openssl/rand.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+
+#if OPENSSL_VERSION_NUMBER >= 0x0907000L
+# include <openssl/conf.h>
+#endif
+
+
+namespace vmime {
+namespace net {
+namespace tls {
+
+
+ref <vmime::utility::sync::criticalSection >* OpenSSLInitializer::sm_mutexes;
+int OpenSSLInitializer::sm_initCount(0);
+
+
+OpenSSLInitializer::OpenSSLInitializer()
+{
+ initialize();
+}
+
+
+OpenSSLInitializer::~OpenSSLInitializer()
+{
+ uninitialize();
+}
+
+
+// static
+ref <vmime::utility::sync::criticalSection> OpenSSLInitializer::getMutex()
+{
+ static ref <vmime::utility::sync::criticalSection> criticalSection
+ = vmime::platform::getHandler()->createCriticalSection();
+
+ return criticalSection;
+}
+
+
+// static
+void OpenSSLInitializer::initialize()
+{
+ ref <vmime::utility::sync::criticalSection> mutex = getMutex();
+ vmime::utility::sync::autoLock <vmime::utility::sync::criticalSection> lock(mutex);
+
+ if (++sm_initCount == 1)
+ {
+#if OPENSSL_VERSION_NUMBER >= 0x0907000L
+ OPENSSL_config(NULL);
+#endif
+
+ SSL_load_error_strings();
+ SSL_library_init();
+ OpenSSL_add_all_algorithms();
+
+ unsigned char seed[SEEDSIZE];
+ vmime::platform::getHandler()->generateRandomBytes(seed, SEEDSIZE);
+ RAND_seed(seed, SEEDSIZE);
+
+ int numMutexes = CRYPTO_num_locks();
+ sm_mutexes = new ref <vmime::utility::sync::criticalSection>[numMutexes];
+
+ for (int i = 0 ; i < numMutexes ; ++i)
+ sm_mutexes[i] = vmime::platform::getHandler()->createCriticalSection();
+
+ CRYPTO_set_locking_callback(&OpenSSLInitializer::lock);
+ CRYPTO_set_id_callback(&OpenSSLInitializer::id);
+ }
+}
+
+
+// static
+void OpenSSLInitializer::uninitialize()
+{
+ ref <vmime::utility::sync::criticalSection> mutex = getMutex();
+ vmime::utility::sync::autoLock <vmime::utility::sync::criticalSection> lock(mutex);
+
+ if (--sm_initCount == 0)
+ {
+ EVP_cleanup();
+ ERR_free_strings();
+
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_id_callback(NULL);
+
+ delete [] sm_mutexes;
+ }
+}
+
+
+// static
+void OpenSSLInitializer::lock(int mode, int n, const char* /* file */, int /* line */)
+{
+ if (mode & CRYPTO_LOCK)
+ sm_mutexes[n]->lock();
+ else
+ sm_mutexes[n]->unlock();
+}
+
+
+// static
+unsigned long OpenSSLInitializer::id()
+{
+ return vmime::platform::getHandler()->getThreadId();
+}
+
+
+} // tls
+} // net
+} // vmime
+
+
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL
+
diff --git a/src/net/tls/openssl/TLSSession_OpenSSL.cpp b/src/net/tls/openssl/TLSSession_OpenSSL.cpp
new file mode 100755
index 00000000..e03c90f6
--- /dev/null
+++ b/src/net/tls/openssl/TLSSession_OpenSSL.cpp
@@ -0,0 +1,138 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2009 Vincent Richard <[email protected]>
+//
+// 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 the Free Software Foundation; either version 3 of
+// the License, or (at your option) any later version.
+//
+// This program 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
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// Linking this library statically or dynamically with other modules is making
+// a combined work based on this library. Thus, the terms and conditions of
+// the GNU General Public License cover the whole combination.
+//
+
+#include "vmime/config.hpp"
+
+
+#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL
+
+
+#if VMIME_HAVE_PTHREAD
+# include <pthread.h>
+# include <gcrypt.h>
+# include <errno.h>
+#endif // VMIME_HAVE_PTHREAD
+
+#include "vmime/net/tls/openssl/TLSSession_OpenSSL.hpp"
+#include "vmime/net/tls/openssl/OpenSSLInitializer.hpp"
+
+#include "vmime/exception.hpp"
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+
+namespace vmime {
+namespace net {
+namespace tls {
+
+
+// Thread-safe OpenSSL initialization
+static OpenSSLInitializer g_openSSLGlobal;
+
+
+// static
+ref <TLSSession> TLSSession::create(ref <security::cert::certificateVerifier> cv)
+{
+ return vmime::create <TLSSession_OpenSSL>(cv);
+}
+
+
+TLSSession_OpenSSL::TLSSession_OpenSSL(ref <vmime::security::cert::certificateVerifier> cv)
+ : m_sslctx(0), m_certVerifier(cv)
+{
+ m_sslctx = SSL_CTX_new(SSLv23_client_method());
+ SSL_CTX_set_options(m_sslctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
+}
+
+
+TLSSession_OpenSSL::TLSSession_OpenSSL(const TLSSession_OpenSSL&)
+ : TLSSession()
+{
+ // Not used
+}
+
+
+TLSSession_OpenSSL::~TLSSession_OpenSSL()
+{
+ SSL_CTX_free(m_sslctx);
+}
+
+
+ref <TLSSocket> TLSSession_OpenSSL::getSocket(ref <socket> sok)
+{
+ return TLSSocket::wrap(thisRef().dynamicCast <TLSSession>(), sok);
+}
+
+
+ref <security::cert::certificateVerifier> TLSSession_OpenSSL::getCertificateVerifier()
+{
+ return m_certVerifier;
+}
+
+
+void TLSSession_OpenSSL::usePrivateKeyFile(const vmime::string& keyfile)
+{
+ if (SSL_CTX_use_PrivateKey_file(m_sslctx, keyfile.c_str(), SSL_FILETYPE_PEM) != 1)
+ {
+ unsigned long errCode = ERR_get_error();
+ char buffer[256];
+ ERR_error_string_n(errCode, buffer, sizeof(buffer));
+ vmime::string sslErr(buffer);
+ std::ostringstream oss;
+ oss << "Error loading private key from file " << keyfile;
+ oss << " - msg: " << sslErr;
+ throw exceptions::certificate_exception(oss.str());
+ }
+}
+
+
+void TLSSession_OpenSSL::useCertificateChainFile(const vmime::string& chainFile)
+{
+ if (SSL_CTX_use_certificate_chain_file(m_sslctx, chainFile.c_str()) != 1)
+ {
+ unsigned long errCode = ERR_get_error();
+ char buffer[256];
+ ERR_error_string_n(errCode, buffer, sizeof(buffer));
+ vmime::string sslErr(buffer);
+ std::ostringstream oss;
+ oss << "Error loading certificate from file " << chainFile;
+ oss << " - msg: " << sslErr;
+ throw exceptions::certificate_exception(oss.str());
+ }
+}
+
+
+SSL_CTX* TLSSession_OpenSSL::getContext() const
+{
+ return m_sslctx;
+}
+
+
+} // tls
+} // net
+} // vmime
+
+
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL
+
diff --git a/src/net/tls/openssl/TLSSocket_OpenSSL.cpp b/src/net/tls/openssl/TLSSocket_OpenSSL.cpp
new file mode 100755
index 00000000..cbd1f9fd
--- /dev/null
+++ b/src/net/tls/openssl/TLSSocket_OpenSSL.cpp
@@ -0,0 +1,416 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2009 Vincent Richard <[email protected]>
+//
+// 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 the Free Software Foundation; either version 3 of
+// the License, or (at your option) any later version.
+//
+// This program 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
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// Linking this library statically or dynamically with other modules is making
+// a combined work based on this library. Thus, the terms and conditions of
+// the GNU General Public License cover the whole combination.
+//
+
+#include "vmime/config.hpp"
+
+
+#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL
+
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "vmime/net/tls/openssl/TLSSocket_OpenSSL.hpp"
+#include "vmime/net/tls/openssl/TLSSession_OpenSSL.hpp"
+
+#include "vmime/platform.hpp"
+
+#include "vmime/security/cert/openssl/X509Certificate_OpenSSL.hpp"
+
+#include <vector>
+
+
+namespace vmime {
+namespace net {
+namespace tls {
+
+
+// static
+ref <TLSSocket> TLSSocket::wrap(ref <TLSSession> session, ref <socket> sok)
+{
+ return vmime::create <TLSSocket_OpenSSL>
+ (session.dynamicCast <TLSSession_OpenSSL>(), sok);
+}
+
+
+TLSSocket_OpenSSL::TLSSocket_OpenSSL(ref <TLSSession_OpenSSL> session, ref <socket> sok)
+ : m_session(session), m_wrapped(sok), m_connected(false), m_ssl(0)
+{
+}
+
+
+TLSSocket_OpenSSL::~TLSSocket_OpenSSL()
+{
+ try
+ {
+ disconnect();
+
+ if (m_ssl)
+ {
+ SSL_free(m_ssl);
+ m_ssl = 0;
+ }
+ }
+ catch (...)
+ {
+ // Don't throw in destructor
+ }
+}
+
+
+void TLSSocket_OpenSSL::createSSLHandle()
+{
+ if (m_wrapped->isConnected())
+ {
+ static BIO_METHOD customBIOMethod;
+ ::memset(&customBIOMethod, 0, sizeof(customBIOMethod));
+
+ customBIOMethod.type = 100 | BIO_TYPE_SOURCE_SINK;
+ customBIOMethod.name = "vmime::socket glue";
+ customBIOMethod.bwrite = bio_write;
+ customBIOMethod.bread = bio_read;
+ customBIOMethod.bputs = bio_puts;
+ customBIOMethod.bgets = bio_gets;
+ customBIOMethod.ctrl = bio_ctrl;
+ customBIOMethod.create = bio_create;
+ customBIOMethod.destroy = bio_destroy;
+
+ BIO* sockBio = BIO_new(&customBIOMethod);
+ sockBio->ptr = this;
+
+ m_ssl = SSL_new(m_session->getContext());
+
+ if (!m_ssl)
+ {
+ BIO_free(sockBio);
+ throw exceptions::tls_exception("Cannot create SSL object");
+ }
+
+ SSL_set_bio(m_ssl, sockBio, sockBio);
+ SSL_set_connect_state(m_ssl);
+ }
+ else
+ {
+ throw exceptions::tls_exception("Unconnected socket error");
+ }
+}
+
+
+void TLSSocket_OpenSSL::connect(const string& address, const port_t port)
+{
+ m_wrapped->connect(address, port);
+
+ createSSLHandle();
+
+ handshake(NULL);
+
+ m_connected = true;
+}
+
+
+void TLSSocket_OpenSSL::disconnect()
+{
+ if (m_connected)
+ {
+ if (m_ssl)
+ {
+ // Don't shut down the socket more than once.
+ int shutdownState = SSL_get_shutdown(m_ssl);
+ bool shutdownSent = (shutdownState & SSL_SENT_SHUTDOWN) == SSL_SENT_SHUTDOWN;
+
+ if (!shutdownSent)
+ SSL_shutdown(m_ssl);
+ }
+
+ m_wrapped->disconnect();
+ m_connected = false;
+ }
+}
+
+
+bool TLSSocket_OpenSSL::isConnected() const
+{
+ return m_wrapped->isConnected() && m_connected;
+}
+
+
+TLSSocket::size_type TLSSocket_OpenSSL::getBlockSize() const
+{
+ return 16384; // 16 KB
+}
+
+
+void TLSSocket_OpenSSL::receive(string& buffer)
+{
+ const int size = receiveRaw(m_buffer, sizeof(m_buffer));
+ buffer = vmime::string(m_buffer, size);
+}
+
+
+void TLSSocket_OpenSSL::send(const string& buffer)
+{
+ sendRaw(buffer.data(), buffer.length());
+}
+
+
+TLSSocket::size_type TLSSocket_OpenSSL::receiveRaw(char* buffer, const size_type count)
+{
+ int rc = SSL_read(m_ssl, buffer, count);
+ handleError(rc);
+ return rc;
+}
+
+
+void TLSSocket_OpenSSL::sendRaw(const char* buffer, const size_type count)
+{
+ int rc = SSL_write(m_ssl, buffer, count);
+ handleError(rc);
+}
+
+
+void TLSSocket_OpenSSL::handshake(ref <timeoutHandler> toHandler)
+{
+ if (toHandler)
+ toHandler->resetTimeOut();
+
+ // Start handshaking process
+ m_toHandler = toHandler;
+
+ if (!m_ssl)
+ createSSLHandle();
+
+ try
+ {
+ // int ret = SSL_connect(m_ssl);
+ int ret = SSL_do_handshake(m_ssl);
+ handleError(ret);
+ }
+ catch (...)
+ {
+ SSL_free(m_ssl);
+ m_ssl = 0;
+ m_toHandler = NULL;
+ throw;
+ }
+
+ m_toHandler = NULL;
+
+ // Verify server's certificate(s)
+ ref <security::cert::certificateChain> certs = getPeerCertificates();
+
+ if (certs == NULL)
+ throw exceptions::tls_exception("No peer certificate.");
+
+ m_session->getCertificateVerifier()->verify(certs);
+
+ m_connected = true;
+}
+
+
+ref <security::cert::certificateChain> TLSSocket_OpenSSL::getPeerCertificates() const
+{
+ STACK_OF(X509)* chain = SSL_get_peer_cert_chain(m_ssl);
+
+ if (chain == NULL)
+ return NULL;
+
+ int certCount = sk_X509_num(chain);
+
+ if (certCount == 0)
+ return NULL;
+
+ bool error = false;
+ std::vector <ref <security::cert::certificate> > certs;
+
+ for (int i = 0; i < certCount && !error; i++)
+ {
+ ref <vmime::security::cert::X509Certificate> cert =
+ vmime::security::cert::X509Certificate_OpenSSL::importInternal(sk_X509_value(chain, i));
+
+ if (cert)
+ certs.push_back(cert);
+ else
+ error = true;
+ }
+
+ if (error)
+ return NULL;
+
+ return vmime::create <security::cert::certificateChain>(certs);
+}
+
+
+void TLSSocket_OpenSSL::handleError(int rc)
+{
+ if (rc > 0) return;
+
+ int sslError = SSL_get_error(m_ssl, rc);
+ long lastError = ERR_get_error();
+
+ switch (sslError)
+ {
+ case SSL_ERROR_ZERO_RETURN:
+ return;
+
+ case SSL_ERROR_SYSCALL:
+ {
+ if (lastError == 0)
+ {
+ if (rc == 0)
+ throw exceptions::tls_exception("SSL connection unexpectedly closed");
+ else
+ {
+ vmime::string msg;
+ std::ostringstream oss(msg);
+ oss << "The BIO reported an error: %d" << rc;
+ oss.flush();
+ throw exceptions::tls_exception(oss.str());
+ }
+ }
+ break;
+ }
+
+ //// Follwoing errors should not occur
+ // With SSL_MODE_AUTO_RETRY these should not happen
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ // This happens only for BIOs of type BIO_s_connect() or BIO_s_accept()
+ case SSL_ERROR_WANT_CONNECT:
+ case SSL_ERROR_WANT_ACCEPT:
+ // SSL_CTX_set_client_cert_cb related, not used
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ case SSL_ERROR_SSL:
+ default:
+
+ if (lastError == 0)
+ {
+ throw exceptions::tls_exception("Unexpected SSL IO error");
+ }
+ else
+ {
+ char buffer[256];
+ ERR_error_string_n(lastError, buffer, sizeof(buffer));
+ vmime::string msg(buffer);
+ throw exceptions::tls_exception(msg);
+ }
+
+ break;
+ }
+}
+
+
+// Implementation of custom BIO methods
+
+
+// static
+int TLSSocket_OpenSSL::bio_write(BIO* bio, const char* buf, int len)
+{
+ if (buf == NULL || len <= 0)
+ return 0;
+
+ TLSSocket_OpenSSL *sok = reinterpret_cast <TLSSocket_OpenSSL*>(bio->ptr);
+
+ sok->m_wrapped->sendRaw(buf, len);
+
+ return len;
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_read(BIO* bio, char* buf, int len)
+{
+ if (buf == NULL || len <= 0)
+ return 0;
+
+ TLSSocket_OpenSSL *sok = reinterpret_cast <TLSSocket_OpenSSL*>(bio->ptr);
+
+ const int n = sok->m_wrapped->receiveRaw(buf, len);
+
+ if (n == 0)
+ BIO_set_retry_read(bio); // This seems like a hack, really...
+ else
+ BIO_clear_retry_flags(bio);
+
+ return n;
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_puts(BIO* bio, const char* str)
+{
+ return bio_write(bio, str, strlen(str));
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_gets(BIO* /* bio */, char* /* buf */, int /* len */)
+{
+ return -1;
+}
+
+
+// static
+long TLSSocket_OpenSSL::bio_ctrl(BIO* /* bio */, int cmd, long /* num */, void* /* ptr */)
+{
+ if (cmd == BIO_CTRL_FLUSH)
+ {
+ // OpenSSL library needs this
+ return 1;
+ }
+
+ return 0;
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_create(BIO* bio)
+{
+ bio->init = 1;
+ bio->num = 0;
+ bio->ptr = NULL;
+ bio->flags = 0;
+
+ return 1;
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_destroy(BIO* bio)
+{
+ if (bio == NULL)
+ return 0;
+
+ bio->ptr = NULL;
+ bio->init = 0;
+ bio->flags = 0;
+
+ return 1;
+}
+
+
+} // tls
+} // net
+} // vmime
+
+
+#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_TLS_SUPPORT && VMIME_TLS_SUPPORT_LIB_IS_OPENSSL