aboutsummaryrefslogtreecommitdiffstats
path: root/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp')
-rw-r--r--src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp601
1 files changed, 601 insertions, 0 deletions
diff --git a/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp
new file mode 100644
index 00000000..ef6647d6
--- /dev/null
+++ b/src/vmime/net/tls/openssl/TLSSocket_OpenSSL.cpp
@@ -0,0 +1,601 @@
+//
+// VMime library (http://www.vmime.org)
+// Copyright (C) 2002-2013 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/net/tls/openssl/OpenSSLInitializer.hpp"
+
+#include "vmime/platform.hpp"
+
+#include "vmime/security/cert/openssl/X509Certificate_OpenSSL.hpp"
+
+#include "vmime/utility/stringUtils.hpp"
+
+#include <vector>
+#include <cstring>
+
+
+namespace vmime {
+namespace net {
+namespace tls {
+
+
+static OpenSSLInitializer::autoInitializer openSSLInitializer;
+
+
+// static
+BIO_METHOD TLSSocket_OpenSSL::sm_customBIOMethod =
+{
+ 100 | BIO_TYPE_SOURCE_SINK,
+ "vmime::socket glue",
+ TLSSocket_OpenSSL::bio_write,
+ TLSSocket_OpenSSL::bio_read,
+ TLSSocket_OpenSSL::bio_puts,
+ NULL, // gets
+ TLSSocket_OpenSSL::bio_ctrl,
+ TLSSocket_OpenSSL::bio_create,
+ TLSSocket_OpenSSL::bio_destroy,
+ 0
+};
+
+
+// static
+shared_ptr <TLSSocket> TLSSocket::wrap(shared_ptr <TLSSession> session, shared_ptr <socket> sok)
+{
+ return make_shared <TLSSocket_OpenSSL>
+ (dynamicCast <TLSSession_OpenSSL>(session), sok);
+}
+
+
+TLSSocket_OpenSSL::TLSSocket_OpenSSL(shared_ptr <TLSSession_OpenSSL> session, shared_ptr <socket> sok)
+ : m_session(session), m_wrapped(sok), m_connected(false), m_ssl(0), m_status(0), m_ex(NULL)
+{
+}
+
+
+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())
+ {
+ BIO* sockBio = BIO_new(&sm_customBIOMethod);
+ sockBio->ptr = this;
+ sockBio->init = 1;
+
+ 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;
+}
+
+
+size_t TLSSocket_OpenSSL::getBlockSize() const
+{
+ return 16384; // 16 KB
+}
+
+
+const string TLSSocket_OpenSSL::getPeerName() const
+{
+ return m_wrapped->getPeerName();
+}
+
+
+const string TLSSocket_OpenSSL::getPeerAddress() const
+{
+ return m_wrapped->getPeerAddress();
+}
+
+
+void TLSSocket_OpenSSL::receive(string& buffer)
+{
+ const size_t size = receiveRaw(m_buffer, sizeof(m_buffer));
+
+ if (size != 0)
+ buffer = utility::stringUtils::makeStringFromBytes(m_buffer, size);
+ else
+ buffer.clear();
+}
+
+
+void TLSSocket_OpenSSL::send(const string& buffer)
+{
+ sendRaw(reinterpret_cast <const byte_t*>(buffer.data()), buffer.length());
+}
+
+
+void TLSSocket_OpenSSL::send(const char* str)
+{
+ sendRaw(reinterpret_cast <const byte_t*>(str), ::strlen(str));
+}
+
+
+size_t TLSSocket_OpenSSL::receiveRaw(byte_t* buffer, const size_t count)
+{
+ m_status &= ~STATUS_WOULDBLOCK;
+
+ int rc = SSL_read(m_ssl, buffer, static_cast <int>(count));
+
+ if (m_ex.get())
+ internalThrow();
+
+ if (rc <= 0)
+ {
+ int error = SSL_get_error(m_ssl, rc);
+
+ if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ)
+ {
+ m_status |= STATUS_WOULDBLOCK;
+ return 0;
+ }
+
+ handleError(rc);
+ }
+
+ return rc;
+}
+
+
+void TLSSocket_OpenSSL::sendRaw(const byte_t* buffer, const size_t count)
+{
+ sendRawNonBlocking(buffer, count);
+}
+
+
+size_t TLSSocket_OpenSSL::sendRawNonBlocking(const byte_t* buffer, const size_t count)
+{
+ m_status &= ~STATUS_WOULDBLOCK;
+
+ int rc = SSL_write(m_ssl, buffer, static_cast <int>(count));
+
+ if (m_ex.get())
+ internalThrow();
+
+ if (rc <= 0)
+ {
+ int error = SSL_get_error(m_ssl, rc);
+
+ if (error == SSL_ERROR_WANT_WRITE || error == SSL_ERROR_WANT_READ)
+ {
+ m_status |= STATUS_WOULDBLOCK;
+ return 0;
+ }
+
+ handleError(rc);
+ }
+
+ return rc;
+}
+
+
+void TLSSocket_OpenSSL::handshake(shared_ptr <timeoutHandler> toHandler)
+{
+ if (toHandler)
+ toHandler->resetTimeOut();
+
+ // Start handshaking process
+ m_toHandler = toHandler;
+
+ if (!m_ssl)
+ createSSLHandle();
+
+ try
+ {
+ int rc;
+
+ while ((rc = SSL_do_handshake(m_ssl)) <= 0)
+ {
+ const int err = SSL_get_error(m_ssl, rc);
+
+ if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
+ {
+ // No data available yet
+ platform::getHandler()->wait();
+ }
+ else
+ {
+ handleError(rc);
+ }
+
+ // Check whether the time-out delay is elapsed
+ if (m_toHandler && m_toHandler->isTimeOut())
+ {
+ if (!m_toHandler->handleTimeOut())
+ throw exceptions::operation_timed_out();
+
+ m_toHandler->resetTimeOut();
+ }
+ }
+ }
+ catch (...)
+ {
+ SSL_free(m_ssl);
+ m_ssl = 0;
+ m_toHandler = null;
+ throw;
+ }
+
+ m_toHandler = null;
+
+ // Verify server's certificate(s)
+ shared_ptr <security::cert::certificateChain> certs = getPeerCertificates();
+
+ if (certs == NULL)
+ throw exceptions::tls_exception("No peer certificate.");
+
+ m_session->getCertificateVerifier()->verify(certs, getPeerName());
+
+ m_connected = true;
+}
+
+
+shared_ptr <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 <shared_ptr <security::cert::certificate> > certs;
+
+ for (int i = 0; i < certCount && !error; i++)
+ {
+ shared_ptr <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 make_shared <security::cert::certificateChain>(certs);
+}
+
+
+void TLSSocket_OpenSSL::internalThrow()
+{
+ if (m_ex.get())
+ throw *m_ex;
+}
+
+
+void TLSSocket_OpenSSL::handleError(int rc)
+{
+ if (rc > 0) return;
+
+ internalThrow();
+
+ 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: " << rc;
+ oss.flush();
+ throw exceptions::tls_exception(oss.str());
+ }
+ }
+ break;
+ }
+
+ case SSL_ERROR_WANT_READ:
+
+ BIO_set_retry_read(SSL_get_rbio(m_ssl));
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+
+ BIO_set_retry_write(SSL_get_wbio(m_ssl));
+ break;
+
+ // 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;
+ }
+}
+
+
+unsigned int TLSSocket_OpenSSL::getStatus() const
+{
+ return m_status;
+}
+
+
+// Implementation of custom BIO methods
+
+
+// static
+int TLSSocket_OpenSSL::bio_write(BIO* bio, const char* buf, int len)
+{
+ BIO_clear_retry_flags(bio);
+
+ if (buf == NULL || len <= 0)
+ return -1;
+
+ TLSSocket_OpenSSL *sok = reinterpret_cast <TLSSocket_OpenSSL*>(bio->ptr);
+
+ if (!bio->init || !sok)
+ return -1;
+
+ try
+ {
+ const size_t n = sok->m_wrapped->sendRawNonBlocking
+ (reinterpret_cast <const byte_t*>(buf), len);
+
+ if (n == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK)
+ {
+ BIO_set_retry_write(bio);
+ return -1;
+ }
+
+ return static_cast <int>(len);
+ }
+ catch (exception& e)
+ {
+ // Workaround for passing C++ exceptions from C BIO functions
+ sok->m_ex.reset(e.clone());
+ return -1;
+ }
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_read(BIO* bio, char* buf, int len)
+{
+ BIO_clear_retry_flags(bio);
+
+ if (buf == NULL || len <= 0)
+ return -1;
+
+ TLSSocket_OpenSSL *sok = reinterpret_cast <TLSSocket_OpenSSL*>(bio->ptr);
+
+ if (!bio->init || !sok)
+ return -1;
+
+ try
+ {
+ const size_t n = sok->m_wrapped->receiveRaw
+ (reinterpret_cast <byte_t*>(buf), len);
+
+ if (n == 0 || sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK)
+ {
+ BIO_set_retry_read(bio);
+ return -1;
+ }
+
+ return static_cast <int>(n);
+ }
+ catch (exception& e)
+ {
+ // Workaround for passing C++ exceptions from C BIO functions
+ sok->m_ex.reset(e.clone());
+ return -1;
+ }
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_puts(BIO* bio, const char* str)
+{
+ return bio_write(bio, str, static_cast <int>(strlen(str)));
+}
+
+
+// static
+long TLSSocket_OpenSSL::bio_ctrl(BIO* bio, int cmd, long num, void* ptr)
+{
+ long ret = 1;
+
+ switch (cmd)
+ {
+ case BIO_CTRL_INFO:
+
+ ret = 0;
+ break;
+
+ case BIO_CTRL_GET_CLOSE:
+
+ ret = bio->shutdown;
+ break;
+
+ case BIO_CTRL_SET_CLOSE:
+
+ bio->shutdown = static_cast <int>(num);
+ break;
+
+ case BIO_CTRL_PENDING:
+ case BIO_CTRL_WPENDING:
+
+ ret = 0;
+ break;
+
+ case BIO_CTRL_DUP:
+ case BIO_CTRL_FLUSH:
+
+ ret = 1;
+ break;
+
+ default:
+
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_create(BIO* bio)
+{
+ bio->init = 0;
+ bio->num = 0;
+ bio->ptr = NULL;
+ bio->flags = 0;
+
+ return 1;
+}
+
+
+// static
+int TLSSocket_OpenSSL::bio_destroy(BIO* bio)
+{
+ if (bio == NULL)
+ return 0;
+
+ if (bio->shutdown)
+ {
+ 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