From 3e9e8c9265f722d294c0060e1ccf29695fa5d2eb Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Thu, 29 Nov 2012 22:08:25 +0100 Subject: Better handling of SSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITE. Sockets on Windows platform are now non-blocking (thanks to Mehmet Bozkurt). --- src/net/tls/gnutls/TLSSocket_GnuTLS.cpp | 56 ++++++++++++++-- src/net/tls/openssl/TLSSession_OpenSSL.cpp | 3 + src/net/tls/openssl/TLSSocket_OpenSSL.cpp | 102 ++++++++++++++++++++++++----- 3 files changed, 140 insertions(+), 21 deletions(-) (limited to 'src/net') diff --git a/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp b/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp index 35e1d415..477f655b 100644 --- a/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp +++ b/src/net/tls/gnutls/TLSSocket_GnuTLS.cpp @@ -53,7 +53,7 @@ ref TLSSocket::wrap(ref session, ref sok) TLSSocket_GnuTLS::TLSSocket_GnuTLS(ref session, ref sok) : m_session(session), m_wrapped(sok), m_connected(false), - m_handshaking(false), m_ex(NULL) + m_handshaking(false), m_ex(NULL), m_status(0) { gnutls_transport_set_ptr(*m_session->m_gnutlsSession, this); @@ -131,6 +131,8 @@ void TLSSocket_GnuTLS::send(const string& buffer) TLSSocket::size_type TLSSocket_GnuTLS::receiveRaw(char* buffer, const size_type count) { + m_status &= ~STATUS_WOULDBLOCK; + const ssize_t ret = gnutls_record_recv (*m_session->m_gnutlsSession, buffer, static_cast (count)); @@ -141,23 +143,67 @@ TLSSocket::size_type TLSSocket_GnuTLS::receiveRaw(char* buffer, const size_type if (ret < 0) { if (ret == GNUTLS_E_AGAIN) + { + m_status |= STATUS_WOULDBLOCK; return 0; + } TLSSession_GnuTLS::throwTLSException("gnutls_record_recv", ret); } - return static_cast (ret); + return static_cast (ret); } void TLSSocket_GnuTLS::sendRaw(const char* buffer, const size_type count) { - gnutls_record_send + ssize_t ret = gnutls_record_send + (*m_session->m_gnutlsSession, + buffer, static_cast (count)); + + if (m_ex) + internalThrow(); + + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + { + m_status |= STATUS_WOULDBLOCK; + return; + } + + TLSSession_GnuTLS::throwTLSException("gnutls_record_send", ret); + } +} + + +TLSSocket::size_type TLSSocket_GnuTLS::sendRawNonBlocking(const char* buffer, const size_type count) +{ + ssize_t ret = gnutls_record_send (*m_session->m_gnutlsSession, buffer, static_cast (count)); if (m_ex) internalThrow(); + + if (ret < 0) + { + if (ret == GNUTLS_E_AGAIN) + { + m_status |= STATUS_WOULDBLOCK; + return 0; + } + + TLSSession_GnuTLS::throwTLSException("gnutls_record_send", ret); + } + + return static_cast (ret); +} + + +unsigned int TLSSocket_GnuTLS::getStatus() const +{ + return m_status | m_wrapped->getStatus(); } @@ -290,8 +336,8 @@ ssize_t TLSSocket_GnuTLS::gnutlsPullFunc (reinterpret_cast (data), static_cast (len))); - if (n == 0) - return GNUTLS_E_AGAIN; // This seems like a hack, really... + if (n == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) + return GNUTLS_E_AGAIN; return n; } diff --git a/src/net/tls/openssl/TLSSession_OpenSSL.cpp b/src/net/tls/openssl/TLSSession_OpenSSL.cpp index 5d7e1052..248322d4 100755 --- a/src/net/tls/openssl/TLSSession_OpenSSL.cpp +++ b/src/net/tls/openssl/TLSSession_OpenSSL.cpp @@ -62,6 +62,9 @@ TLSSession_OpenSSL::TLSSession_OpenSSL(ref TLSSocket::wrap(ref session, ref sok) TLSSocket_OpenSSL::TLSSocket_OpenSSL(ref session, ref sok) - : m_session(session), m_wrapped(sok), m_connected(false), m_ssl(0) + : m_session(session), m_wrapped(sok), m_connected(false), m_ssl(0), m_ex(NULL) { } @@ -162,8 +162,10 @@ TLSSocket::size_type TLSSocket_OpenSSL::getBlockSize() const void TLSSocket_OpenSSL::receive(string& buffer) { - const int size = receiveRaw(m_buffer, sizeof(m_buffer)); - buffer = vmime::string(m_buffer, size); + const size_type size = receiveRaw(m_buffer, sizeof(m_buffer)); + + if (size >= 0) + buffer = vmime::string(m_buffer, size); } @@ -175,19 +177,35 @@ void TLSSocket_OpenSSL::send(const string& buffer) TLSSocket::size_type TLSSocket_OpenSSL::receiveRaw(char* buffer, const size_type count) { - int rc = SSL_read(m_ssl, buffer, count); + int rc = SSL_read(m_ssl, buffer, static_cast (count)); handleError(rc); + + if (rc < 0) + return 0; + return rc; } void TLSSocket_OpenSSL::sendRaw(const char* buffer, const size_type count) { - int rc = SSL_write(m_ssl, buffer, count); + int rc = SSL_write(m_ssl, buffer, static_cast (count)); handleError(rc); } +TLSSocket_OpenSSL::size_type TLSSocket_OpenSSL::sendRawNonBlocking(const char* buffer, const size_type count) +{ + int rc = SSL_write(m_ssl, buffer, static_cast (count)); + handleError(rc); + + if (rc < 0) + rc = 0; + + return rc; +} + + void TLSSocket_OpenSSL::handshake(ref toHandler) { if (toHandler) @@ -260,6 +278,13 @@ ref TLSSocket_OpenSSL::getPeerCertificates() } +void TLSSocket_OpenSSL::internalThrow() +{ + if (m_ex.get()) + throw *m_ex; +} + + void TLSSocket_OpenSSL::handleError(int rc) { if (rc > 0) return; @@ -277,12 +302,14 @@ void TLSSocket_OpenSSL::handleError(int rc) 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 << "The BIO reported an error: " << rc; oss.flush(); throw exceptions::tls_exception(oss.str()); } @@ -290,10 +317,16 @@ void TLSSocket_OpenSSL::handleError(int rc) break; } - //// Follwoing errors should not occur - // With SSL_MODE_AUTO_RETRY these should not happen 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: @@ -319,6 +352,12 @@ void TLSSocket_OpenSSL::handleError(int rc) } +unsigned int TLSSocket_OpenSSL::getStatus() const +{ + return m_wrapped->getStatus(); +} + + // Implementation of custom BIO methods @@ -330,9 +369,28 @@ int TLSSocket_OpenSSL::bio_write(BIO* bio, const char* buf, int len) TLSSocket_OpenSSL *sok = reinterpret_cast (bio->ptr); - sok->m_wrapped->sendRaw(buf, len); + try + { + BIO_clear_retry_flags(bio); + + const size_type n = sok->m_wrapped->sendRawNonBlocking(buf, len); + + BIO_clear_retry_flags(bio); + + if (n == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) + { + BIO_set_retry_write(bio); + return -1; + } - return len; + return static_cast (len); + } + catch (exception& e) + { + // Workaround for passing C++ exceptions from C BIO functions + sok->m_ex.reset(e.clone()); + return -1; + } } @@ -344,21 +402,33 @@ int TLSSocket_OpenSSL::bio_read(BIO* bio, char* buf, int len) TLSSocket_OpenSSL *sok = reinterpret_cast (bio->ptr); - const int n = sok->m_wrapped->receiveRaw(buf, len); + try + { + const size_type 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; + if (sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK) + { + BIO_set_retry_read(bio); + return -1; + } + + return static_cast (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, strlen(str)); + return bio_write(bio, str, static_cast (strlen(str))); } -- cgit