Better handling of SSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITE. Sockets on Windows platform are now non-blocking (thanks to Mehmet Bozkurt).

This commit is contained in:
Vincent Richard 2012-11-29 22:08:25 +01:00
parent fc9bc26384
commit 3e9e8c9265
12 changed files with 515 additions and 59 deletions

View File

@ -53,7 +53,7 @@ ref <TLSSocket> TLSSocket::wrap(ref <TLSSession> session, ref <socket> sok)
TLSSocket_GnuTLS::TLSSocket_GnuTLS(ref <TLSSession_GnuTLS> session, ref <socket> sok) TLSSocket_GnuTLS::TLSSocket_GnuTLS(ref <TLSSession_GnuTLS> session, ref <socket> sok)
: m_session(session), m_wrapped(sok), m_connected(false), : 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); 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) TLSSocket::size_type TLSSocket_GnuTLS::receiveRaw(char* buffer, const size_type count)
{ {
m_status &= ~STATUS_WOULDBLOCK;
const ssize_t ret = gnutls_record_recv const ssize_t ret = gnutls_record_recv
(*m_session->m_gnutlsSession, (*m_session->m_gnutlsSession,
buffer, static_cast <size_t>(count)); buffer, static_cast <size_t>(count));
@ -141,23 +143,67 @@ TLSSocket::size_type TLSSocket_GnuTLS::receiveRaw(char* buffer, const size_type
if (ret < 0) if (ret < 0)
{ {
if (ret == GNUTLS_E_AGAIN) if (ret == GNUTLS_E_AGAIN)
{
m_status |= STATUS_WOULDBLOCK;
return 0; return 0;
}
TLSSession_GnuTLS::throwTLSException("gnutls_record_recv", ret); TLSSession_GnuTLS::throwTLSException("gnutls_record_recv", ret);
} }
return static_cast <int>(ret); return static_cast <size_type>(ret);
} }
void TLSSocket_GnuTLS::sendRaw(const char* buffer, const size_type count) void TLSSocket_GnuTLS::sendRaw(const char* buffer, const size_type count)
{ {
gnutls_record_send ssize_t ret = gnutls_record_send
(*m_session->m_gnutlsSession, (*m_session->m_gnutlsSession,
buffer, static_cast <size_t>(count)); buffer, static_cast <size_t>(count));
if (m_ex) if (m_ex)
internalThrow(); 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 <size_t>(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 <size_type>(ret);
}
unsigned int TLSSocket_GnuTLS::getStatus() const
{
return m_status | m_wrapped->getStatus();
} }
@ -290,8 +336,8 @@ ssize_t TLSSocket_GnuTLS::gnutlsPullFunc
(reinterpret_cast <char*>(data), (reinterpret_cast <char*>(data),
static_cast <int>(len))); static_cast <int>(len)));
if (n == 0) if (n == 0 && sok->m_wrapped->getStatus() & socket::STATUS_WOULDBLOCK)
return GNUTLS_E_AGAIN; // This seems like a hack, really... return GNUTLS_E_AGAIN;
return n; return n;
} }

View File

@ -62,6 +62,9 @@ TLSSession_OpenSSL::TLSSession_OpenSSL(ref <vmime::security::cert::certificateVe
m_sslctx = SSL_CTX_new(SSLv23_client_method()); m_sslctx = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_set_options(m_sslctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); SSL_CTX_set_options(m_sslctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
SSL_CTX_set_mode(m_sslctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_cipher_list(m_sslctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
SSL_CTX_set_session_cache_mode(m_sslctx, SSL_SESS_CACHE_OFF);
} }

View File

@ -54,7 +54,7 @@ ref <TLSSocket> TLSSocket::wrap(ref <TLSSession> session, ref <socket> sok)
TLSSocket_OpenSSL::TLSSocket_OpenSSL(ref <TLSSession_OpenSSL> session, ref <socket> sok) TLSSocket_OpenSSL::TLSSocket_OpenSSL(ref <TLSSession_OpenSSL> session, ref <socket> 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,7 +162,9 @@ TLSSocket::size_type TLSSocket_OpenSSL::getBlockSize() const
void TLSSocket_OpenSSL::receive(string& buffer) void TLSSocket_OpenSSL::receive(string& buffer)
{ {
const int size = receiveRaw(m_buffer, sizeof(m_buffer)); const size_type size = receiveRaw(m_buffer, sizeof(m_buffer));
if (size >= 0)
buffer = vmime::string(m_buffer, size); 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) 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 <int>(count));
handleError(rc); handleError(rc);
if (rc < 0)
return 0;
return rc; return rc;
} }
void TLSSocket_OpenSSL::sendRaw(const char* buffer, const size_type count) 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 <int>(count));
handleError(rc); 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 <int>(count));
handleError(rc);
if (rc < 0)
rc = 0;
return rc;
}
void TLSSocket_OpenSSL::handshake(ref <timeoutHandler> toHandler) void TLSSocket_OpenSSL::handshake(ref <timeoutHandler> toHandler)
{ {
if (toHandler) if (toHandler)
@ -260,6 +278,13 @@ ref <security::cert::certificateChain> TLSSocket_OpenSSL::getPeerCertificates()
} }
void TLSSocket_OpenSSL::internalThrow()
{
if (m_ex.get())
throw *m_ex;
}
void TLSSocket_OpenSSL::handleError(int rc) void TLSSocket_OpenSSL::handleError(int rc)
{ {
if (rc > 0) return; if (rc > 0) return;
@ -277,12 +302,14 @@ void TLSSocket_OpenSSL::handleError(int rc)
if (lastError == 0) if (lastError == 0)
{ {
if (rc == 0) if (rc == 0)
{
throw exceptions::tls_exception("SSL connection unexpectedly closed"); throw exceptions::tls_exception("SSL connection unexpectedly closed");
}
else else
{ {
vmime::string msg; vmime::string msg;
std::ostringstream oss(msg); std::ostringstream oss(msg);
oss << "The BIO reported an error: %d" << rc; oss << "The BIO reported an error: " << rc;
oss.flush(); oss.flush();
throw exceptions::tls_exception(oss.str()); throw exceptions::tls_exception(oss.str());
} }
@ -290,10 +317,16 @@ void TLSSocket_OpenSSL::handleError(int rc)
break; break;
} }
//// Follwoing errors should not occur
// With SSL_MODE_AUTO_RETRY these should not happen
case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_READ:
BIO_set_retry_read(SSL_get_rbio(m_ssl));
break;
case SSL_ERROR_WANT_WRITE: 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() // This happens only for BIOs of type BIO_s_connect() or BIO_s_accept()
case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT: 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 // 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 <TLSSocket_OpenSSL*>(bio->ptr); TLSSocket_OpenSSL *sok = reinterpret_cast <TLSSocket_OpenSSL*>(bio->ptr);
sok->m_wrapped->sendRaw(buf, len); try
{
BIO_clear_retry_flags(bio);
return len; 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 static_cast <int>(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 <TLSSocket_OpenSSL*>(bio->ptr); TLSSocket_OpenSSL *sok = reinterpret_cast <TLSSocket_OpenSSL*>(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); 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 <int>(n);
}
catch (exception& e)
{
// Workaround for passing C++ exceptions from C BIO functions
sok->m_ex.reset(e.clone());
return -1;
}
} }
// static // static
int TLSSocket_OpenSSL::bio_puts(BIO* bio, const char* str) int TLSSocket_OpenSSL::bio_puts(BIO* bio, const char* str)
{ {
return bio_write(bio, str, strlen(str)); return bio_write(bio, str, static_cast <int>(strlen(str)));
} }

View File

@ -43,6 +43,13 @@
#include "vmime/exception.hpp" #include "vmime/exception.hpp"
#if defined(EWOULDBLOCK)
# define IS_EAGAIN(x) ((x) == EAGAIN || (x) == EWOULDBLOCK || (x) == EINTR)
#else
# define IS_EAGAIN(x) ((x) == EAGAIN || (x) == EINTR)
#endif
namespace vmime { namespace vmime {
namespace platforms { namespace platforms {
namespace posix { namespace posix {
@ -53,7 +60,7 @@ namespace posix {
// //
posixSocket::posixSocket(ref <vmime::net::timeoutHandler> th) posixSocket::posixSocket(ref <vmime::net::timeoutHandler> th)
: m_timeoutHandler(th), m_desc(-1) : m_timeoutHandler(th), m_desc(-1), m_status(0)
{ {
} }
@ -332,13 +339,15 @@ posixSocket::size_type posixSocket::getBlockSize() const
void posixSocket::receive(vmime::string& buffer) void posixSocket::receive(vmime::string& buffer)
{ {
const int size = receiveRaw(m_buffer, sizeof(m_buffer)); const size_type size = receiveRaw(m_buffer, sizeof(m_buffer));
buffer = vmime::string(m_buffer, size); buffer = vmime::string(m_buffer, size);
} }
posixSocket::size_type posixSocket::receiveRaw(char* buffer, const size_type count) posixSocket::size_type posixSocket::receiveRaw(char* buffer, const size_type count)
{ {
m_status &= ~STATUS_WOULDBLOCK;
// Check whether data is available // Check whether data is available
fd_set fds; fd_set fds;
FD_ZERO(&fds); FD_ZERO(&fds);
@ -348,11 +357,11 @@ posixSocket::size_type posixSocket::receiveRaw(char* buffer, const size_type cou
tv.tv_sec = 1; tv.tv_sec = 1;
tv.tv_usec = 0; tv.tv_usec = 0;
int ret = ::select(m_desc + 1, &fds, NULL, NULL, &tv); ssize_t ret = ::select(m_desc + 1, &fds, NULL, NULL, &tv);
if (ret < 0) if (ret < 0)
{ {
if (errno != EAGAIN) if (!IS_EAGAIN(errno))
throwSocketError(errno); throwSocketError(errno);
// No data available at this time // No data available at this time
@ -372,6 +381,8 @@ posixSocket::size_type posixSocket::receiveRaw(char* buffer, const size_type cou
} }
} }
m_status |= STATUS_WOULDBLOCK;
// Continue waiting for data // Continue waiting for data
return 0; return 0;
} }
@ -381,9 +392,27 @@ posixSocket::size_type posixSocket::receiveRaw(char* buffer, const size_type cou
if (ret < 0) if (ret < 0)
{ {
if (errno != EAGAIN) if (!IS_EAGAIN(errno))
throwSocketError(errno); throwSocketError(errno);
// Check if we are timed out
if (m_timeoutHandler &&
m_timeoutHandler->isTimeOut())
{
if (!m_timeoutHandler->handleTimeOut())
{
// Server did not react within timeout delay
throwSocketError(errno);
}
else
{
// Reset timeout
m_timeoutHandler->resetTimeOut();
}
}
m_status |= STATUS_WOULDBLOCK;
// No data available at this time // No data available at this time
return 0; return 0;
} }
@ -411,15 +440,17 @@ void posixSocket::send(const vmime::string& buffer)
void posixSocket::sendRaw(const char* buffer, const size_type count) void posixSocket::sendRaw(const char* buffer, const size_type count)
{ {
m_status &= ~STATUS_WOULDBLOCK;
size_type size = count; size_type size = count;
while (size > 0) while (size > 0)
{ {
const int ret = ::send(m_desc, buffer, size, 0); const ssize_t ret = ::send(m_desc, buffer, size, 0);
if (ret < 0) if (ret < 0)
{ {
if (errno != EAGAIN) if (!IS_EAGAIN(errno))
throwSocketError(errno); throwSocketError(errno);
platform::getHandler()->wait(); platform::getHandler()->wait();
@ -437,6 +468,27 @@ void posixSocket::sendRaw(const char* buffer, const size_type count)
} }
posixSocket::size_type posixSocket::sendRawNonBlocking(const char* buffer, const size_type count)
{
m_status &= ~STATUS_WOULDBLOCK;
const ssize_t ret = ::send(m_desc, buffer, count, 0);
if (ret < 0)
{
if (!IS_EAGAIN(errno))
throwSocketError(errno);
m_status |= STATUS_WOULDBLOCK;
// No data can be written at this time
return 0;
}
return ret;
}
void posixSocket::throwSocketError(const int err) void posixSocket::throwSocketError(const int err)
{ {
string msg; string msg;
@ -473,6 +525,12 @@ void posixSocket::throwSocketError(const int err)
} }
unsigned int posixSocket::getStatus() const
{
return m_status;
}
// //
// posixSocketFactory // posixSocketFactory

View File

@ -40,11 +40,11 @@ namespace windows {
// //
// posixSocket // windowsSocket
// //
windowsSocket::windowsSocket(ref <vmime::net::timeoutHandler> th) windowsSocket::windowsSocket(ref <vmime::net::timeoutHandler> th)
: m_timeoutHandler(th), m_desc(-1) : m_timeoutHandler(th), m_desc(INVALID_SOCKET), m_status(0)
{ {
WSAData wsaData; WSAData wsaData;
WSAStartup(MAKEWORD(1, 1), &wsaData); WSAStartup(MAKEWORD(1, 1), &wsaData);
@ -53,8 +53,9 @@ windowsSocket::windowsSocket(ref <vmime::net::timeoutHandler> th)
windowsSocket::~windowsSocket() windowsSocket::~windowsSocket()
{ {
if (m_desc != -1) if (m_desc != INVALID_SOCKET)
::closesocket(m_desc); ::closesocket(m_desc);
WSACleanup(); WSACleanup();
} }
@ -62,10 +63,10 @@ windowsSocket::~windowsSocket()
void windowsSocket::connect(const vmime::string& address, const vmime::port_t port) void windowsSocket::connect(const vmime::string& address, const vmime::port_t port)
{ {
// Close current connection, if any // Close current connection, if any
if (m_desc != -1) if (m_desc != INVALID_SOCKET)
{ {
::closesocket(m_desc); ::closesocket(m_desc);
m_desc = -1; m_desc = INVALID_SOCKET;
} }
// Resolve address // Resolve address
@ -93,35 +94,64 @@ void windowsSocket::connect(const vmime::string& address, const vmime::port_t po
// Get a new socket // Get a new socket
m_desc = ::socket(AF_INET, SOCK_STREAM, 0); m_desc = ::socket(AF_INET, SOCK_STREAM, 0);
if (m_desc == -1) if (m_desc == INVALID_SOCKET)
throw vmime::exceptions::connection_error("Error while creating socket."); {
try
{
int err = WSAGetLastError();
throwSocketError(err);
}
catch (exceptions::socket_exception& e)
{
throw vmime::exceptions::connection_error
("Error while creating socket.", e);
}
}
// Start connection // Start connection
if (::connect(m_desc, reinterpret_cast <sockaddr*>(&addr), sizeof(addr)) == -1) if (::connect(m_desc, reinterpret_cast <sockaddr*>(&addr), sizeof(addr)) == -1)
{
try
{
int err = WSAGetLastError();
throwSocketError(err);
}
catch (exceptions::socket_exception& e)
{ {
::closesocket(m_desc); ::closesocket(m_desc);
m_desc = -1; m_desc = INVALID_SOCKET;
// Error // Error
throw vmime::exceptions::connection_error("Error while connecting socket."); throw vmime::exceptions::connection_error
("Error while connecting socket.", e);
} }
} }
// Set socket to non-blocking
unsigned long non_blocking = 1;
::ioctlsocket(m_desc, FIONBIO, &non_blocking);
}
bool windowsSocket::isConnected() const bool windowsSocket::isConnected() const
{ {
return (m_desc != -1); if (m_desc == INVALID_SOCKET)
return false;
char buff;
return ::recv(m_desc, &buff, 1, MSG_PEEK) != 0;
} }
void windowsSocket::disconnect() void windowsSocket::disconnect()
{ {
if (m_desc != -1) if (m_desc != INVALID_SOCKET)
{ {
::shutdown(m_desc, SD_BOTH); ::shutdown(m_desc, SD_BOTH);
::closesocket(m_desc); ::closesocket(m_desc);
m_desc = -1; m_desc = INVALID_SOCKET;
} }
} }
@ -134,47 +164,199 @@ windowsSocket::size_type windowsSocket::getBlockSize() const
void windowsSocket::receive(vmime::string& buffer) void windowsSocket::receive(vmime::string& buffer)
{ {
int ret = ::recv(m_desc, m_buffer, sizeof(m_buffer), 0); const size_type size = receiveRaw(m_buffer, sizeof(m_buffer));
buffer = vmime::string(m_buffer, size);
if (ret == -1)
{
// Error or no data
return;
}
else if (ret > 0)
{
buffer = vmime::string(m_buffer, ret);
}
} }
windowsSocket::size_type windowsSocket::receiveRaw(char* buffer, const size_type count) windowsSocket::size_type windowsSocket::receiveRaw(char* buffer, const size_type count)
{ {
int ret = ::recv(m_desc, buffer, count, 0); m_status &= ~STATUS_WOULDBLOCK;
if (ret == -1) // Check whether data is available
bool timedout;
waitForData(READ, timedout);
if (timedout)
{ {
// Error or no data // No data available at this time
return (0); // Check if we are timed out
if (m_timeoutHandler &&
m_timeoutHandler->isTimeOut())
{
if (!m_timeoutHandler->handleTimeOut())
{
// Server did not react within timeout delay
throwSocketError(WSAETIMEDOUT);
} }
else else
{ {
return (ret); // Reset timeout
m_timeoutHandler->resetTimeOut();
}
}
// Continue waiting for data
return 0;
}
// Read available data
int ret = ::recv(m_desc, buffer, count, 0);
if (ret == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK)
throwSocketError(err);
m_status |= STATUS_WOULDBLOCK;
// Error or no data
return (0);
}
else if (ret == 0)
{
// Host shutdown
throwSocketError(WSAENOTCONN);
}
else
{
// Data received, reset timeout
if (m_timeoutHandler)
m_timeoutHandler->resetTimeOut();
return ret;
} }
} }
void windowsSocket::send(const vmime::string& buffer) void windowsSocket::send(const vmime::string& buffer)
{ {
::send(m_desc, buffer.data(), buffer.length(), 0); sendRaw(buffer.data(), buffer.length());
} }
void windowsSocket::sendRaw(const char* buffer, const size_type count) void windowsSocket::sendRaw(const char* buffer, const size_type count)
{ {
::send(m_desc, buffer, count, 0); m_status &= ~STATUS_WOULDBLOCK;
size_type size = count;
while (size > 0)
{
const int ret = ::send(m_desc, buffer, size, 0);
if (ret == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK)
throwSocketError(err);
bool timedout;
waitForData(WRITE, timedout);
}
else
{
buffer += ret;
size -= ret;
}
} }
// Reset timeout
if (m_timeoutHandler)
m_timeoutHandler->resetTimeOut();
}
windowsSocket::size_type windowsSocket::sendRawNonBlocking(const char* buffer, const size_type count, const bool block)
{
m_status &= ~STATUS_WOULDBLOCK;
const int ret = ::send(m_desc, buffer, count, 0);
if (ret == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK)
{
m_status |= STATUS_WOULDBLOCK;
// No data can be written at this time
return 0;
}
else
{
throwSocketError(err);
}
}
return ret;
}
unsigned int windowsSocket::getStatus() const
{
return m_status;
}
void windowsSocket::throwSocketError(const int err)
{
std::ostringstream oss;
string msg;
LPTSTR str;
if (::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, err, 0, (LPTSTR) &str, 0, NULL) == 0)
{
// Failed getting message
oss << "Unknown socket error (code " << err << ")";
}
else
{
oss << str;
::LocalFree(str);
}
msg = oss.str();
throw exceptions::socket_exception(msg);
}
void windowsSocket::waitForData(const WaitOpType t, bool& timedOut)
{
// Check whether data is available
fd_set fds;
FD_ZERO(&fds);
FD_SET(m_desc, &fds);
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
int ret;
if (t & READ)
ret = ::select(m_desc + 1, &fds, NULL, NULL, &tv);
else if (t & WRITE)
ret = ::select(m_desc + 1, NULL, &fds, NULL, &tv);
else
ret = ::select(m_desc + 1, &fds, &fds, NULL, &tv);
timedOut = (ret == 0);
if (ret == SOCKET_ERROR)
{
int err = WSAGetLastError();
throwSocketError(err);
}
}

View File

@ -177,6 +177,40 @@ void SASLSocket::sendRaw(const char* buffer, const size_type count)
} }
SASLSocket::size_type SASLSocket::sendRawNonBlocking(const char* buffer, const size_type count)
{
byte_t* output = 0;
int outputLen = 0;
m_session->getMechanism()->encode
(m_session, reinterpret_cast <const byte_t*>(buffer), count,
&output, &outputLen);
size_type bytesSent = 0;
try
{
bytesSent = m_wrapped->sendRawNonBlocking
(reinterpret_cast <const char*>(output), outputLen);
}
catch (...)
{
delete [] output;
throw;
}
delete [] output;
return bytesSent;
}
unsigned int SASLSocket::getStatus() const
{
return m_wrapped->getStatus();
}
} // sasl } // sasl
} // security } // security
} // vmime } // vmime

View File

@ -47,11 +47,17 @@ class socket : public object
{ {
public: public:
enum Status
{
STATUS_WOULDBLOCK = 0x1 /**< The receive operation would block. */
};
virtual ~socket() { } virtual ~socket() { }
/** Type used for lengths in streams. /** Type used for lengths in streams.
*/ */
typedef int size_type; typedef long size_type;
/** Connect to the specified address and port. /** Connect to the specified address and port.
@ -84,7 +90,7 @@ public:
* @param count maximum number of bytes to receive (size of buffer) * @param count maximum number of bytes to receive (size of buffer)
* @return number of bytes received/written into output buffer * @return number of bytes received/written into output buffer
*/ */
virtual int receiveRaw(char* buffer, const size_type count) = 0; virtual size_type receiveRaw(char* buffer, const size_type count) = 0;
/** Send (text) data to the socket. /** Send (text) data to the socket.
* *
@ -99,6 +105,15 @@ public:
*/ */
virtual void sendRaw(const char* buffer, const size_type count) = 0; virtual void sendRaw(const char* buffer, const size_type count) = 0;
/** Send (raw) data to the socket.
* Function may returns before all data is sent.
*
* @param buffer data to send
* @param count number of bytes to send (size of buffer)
* @return number of bytes sent
*/
virtual size_type sendRawNonBlocking(const char* buffer, const size_type count) = 0;
/** Return the preferred maximum block size when reading /** Return the preferred maximum block size when reading
* from or writing to this stream. * from or writing to this stream.
* *
@ -106,6 +121,12 @@ public:
*/ */
virtual size_type getBlockSize() const = 0; virtual size_type getBlockSize() const = 0;
/** Return the current status of this socket.
*
* @return status flags for this socket
*/
virtual unsigned int getStatus() const = 0;
protected: protected:
socket() { } socket() { }

View File

@ -70,9 +70,12 @@ public:
void send(const string& buffer); void send(const string& buffer);
void sendRaw(const char* buffer, const size_type count); void sendRaw(const char* buffer, const size_type count);
size_type sendRawNonBlocking(const char* buffer, const size_type count);
size_type getBlockSize() const; size_type getBlockSize() const;
unsigned int getStatus() const;
private: private:
void internalThrow(); void internalThrow();
@ -97,6 +100,8 @@ private:
ref <timeoutHandler> m_toHandler; ref <timeoutHandler> m_toHandler;
exception* m_ex; exception* m_ex;
unsigned int m_status;
}; };

View File

@ -36,6 +36,8 @@
#include "vmime/net/tls/TLSSocket.hpp" #include "vmime/net/tls/TLSSocket.hpp"
#include <memory>
#include <openssl/ssl.h> #include <openssl/ssl.h>
@ -72,9 +74,12 @@ public:
void send(const string& buffer); void send(const string& buffer);
void sendRaw(const char* buffer, const size_type count); void sendRaw(const char* buffer, const size_type count);
size_type sendRawNonBlocking(const char* buffer, const size_type count);
size_type getBlockSize() const; size_type getBlockSize() const;
unsigned int getStatus() const;
private: private:
static int bio_write(BIO* bio, const char* buf, int len); static int bio_write(BIO* bio, const char* buf, int len);
@ -87,8 +92,10 @@ private:
void createSSLHandle(); void createSSLHandle();
void internalThrow();
void handleError(int rc); void handleError(int rc);
ref <TLSSession_OpenSSL> m_session; ref <TLSSession_OpenSSL> m_session;
ref <socket> m_wrapped; ref <socket> m_wrapped;
@ -100,6 +107,9 @@ private:
ref <timeoutHandler> m_toHandler; ref <timeoutHandler> m_toHandler;
SSL* m_ssl; SSL* m_ssl;
// Last exception thrown from C BIO functions
std::auto_ptr <std::exception> m_ex;
}; };

View File

@ -55,9 +55,12 @@ public:
void send(const vmime::string& buffer); void send(const vmime::string& buffer);
void sendRaw(const char* buffer, const size_type count); void sendRaw(const char* buffer, const size_type count);
size_type sendRawNonBlocking(const char* buffer, const size_type count);
size_type getBlockSize() const; size_type getBlockSize() const;
unsigned int getStatus() const;
protected: protected:
static void throwSocketError(const int err); static void throwSocketError(const int err);
@ -68,6 +71,8 @@ private:
char m_buffer[65536]; char m_buffer[65536];
int m_desc; int m_desc;
unsigned int m_status;
}; };

View File

@ -43,6 +43,7 @@ namespace windows {
class windowsSocket : public vmime::net::socket class windowsSocket : public vmime::net::socket
{ {
public: public:
windowsSocket(); windowsSocket();
windowsSocket(ref <vmime::net::timeoutHandler> th); windowsSocket(ref <vmime::net::timeoutHandler> th);
~windowsSocket(); ~windowsSocket();
@ -58,15 +59,33 @@ public:
void send(const vmime::string& buffer); void send(const vmime::string& buffer);
void sendRaw(const char* buffer, const size_type count); void sendRaw(const char* buffer, const size_type count);
size_type sendRawNonBlocking(const char* buffer, const size_type count);
size_type getBlockSize() const; size_type getBlockSize() const;
unsigned int getStatus() const;
protected:
void throwSocketError(const int err);
enum WaitOpType
{
READ = 1,
WRITE = 2,
BOTH = 4
};
void waitForData(const WaitOpType t, bool& timedOut);
private: private:
ref <vmime::net::timeoutHandler> m_timeoutHandler; ref <vmime::net::timeoutHandler> m_timeoutHandler;
char m_buffer[65536]; char m_buffer[65536];
SOCKET m_desc; SOCKET m_desc;
unsigned int m_status;
}; };

View File

@ -63,9 +63,12 @@ public:
void send(const string& buffer); void send(const string& buffer);
void sendRaw(const char* buffer, const size_type count); void sendRaw(const char* buffer, const size_type count);
size_type sendRawNonBlocking(const char* buffer, const size_type count);
size_type getBlockSize() const; size_type getBlockSize() const;
unsigned int getStatus() const;
private: private:
ref <SASLSession> m_session; ref <SASLSession> m_session;