// // VMime library (http://www.vmime.org) // Copyright (C) 2002 Vincent Richard // // 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_PLATFORM_IS_WINDOWS && VMIME_HAVE_MESSAGING_FEATURES #pragma warning(disable: 4267) #include "vmime/platforms/windows/windowsSocket.hpp" #include "vmime/utility/stringUtils.hpp" #include "vmime/exception.hpp" #include namespace vmime { namespace platforms { namespace windows { // // windowsSocket // windowsSocket::windowsSocket(shared_ptr th) : m_timeoutHandler(th), m_desc(INVALID_SOCKET), m_status(0) { WSAData wsaData; WSAStartup(MAKEWORD(1, 1), &wsaData); } windowsSocket::~windowsSocket() { if (m_desc != INVALID_SOCKET) { ::closesocket(m_desc); } WSACleanup(); } void windowsSocket::connect(const vmime::string& address, const vmime::port_t port) { // Close current connection, if any if (m_desc != INVALID_SOCKET) { ::closesocket(m_desc); m_desc = INVALID_SOCKET; } // Resolve address ::sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(static_cast (port)); addr.sin_addr.s_addr = ::inet_addr(address.c_str()); if (m_tracer) { std::ostringstream trace; trace << "Connecting to " << address << ", port " << port; m_tracer->traceSend(trace.str()); } if (addr.sin_addr.s_addr == static_cast (-1)) { ::hostent* hostInfo = ::gethostbyname(address.c_str()); if (!hostInfo) { // Error: cannot resolve address throw vmime::exceptions::connection_error("Cannot resolve address."); } memcpy(reinterpret_cast (&addr.sin_addr), hostInfo->h_addr, hostInfo->h_length); } m_serverAddress = address; // Get a new socket m_desc = ::socket(AF_INET, SOCK_STREAM, 0); if (m_desc == INVALID_SOCKET) { try { int err = WSAGetLastError(); throwSocketError(err); } catch (exceptions::socket_exception& e) { throw vmime::exceptions::connection_error("Error while creating socket.", e); } } // Start connection if (::connect(m_desc, reinterpret_cast (&addr), sizeof(addr)) == -1) { try { int err = WSAGetLastError(); throwSocketError(err); } catch (exceptions::socket_exception& e) { ::closesocket(m_desc); m_desc = INVALID_SOCKET; // Error 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 { if (m_desc == INVALID_SOCKET) { return false; } char buff; return ::recv(m_desc, &buff, 1, MSG_PEEK) != 0; } void windowsSocket::disconnect() { if (m_desc != INVALID_SOCKET) { if (m_tracer) { m_tracer->traceSend("Disconnecting"); } ::shutdown(m_desc, SD_BOTH); ::closesocket(m_desc); m_desc = INVALID_SOCKET; } } static bool isNumericAddress(const char* address) { struct addrinfo hint, *info = NULL; memset(&hint, 0, sizeof(hint)); hint.ai_family = AF_UNSPEC; hint.ai_flags = AI_NUMERICHOST; if (getaddrinfo(address, 0, &hint, &info) == 0) { freeaddrinfo(info); return true; } else { return false; } } const string windowsSocket::getPeerAddress() const { // Get address of connected peer sockaddr peer; socklen_t peerLen = sizeof(peer); getpeername(m_desc, reinterpret_cast (&peer), &peerLen); // Convert to numerical presentation format char host[NI_MAXHOST + 1]; char service[NI_MAXSERV + 1]; if (getnameinfo(reinterpret_cast (&peer), peerLen, host, sizeof(host), service, sizeof(service), /* flags */ NI_NUMERICHOST) == 0) { return string(host); } return ""; // should not happen } const string windowsSocket::getPeerName() const { // Get address of connected peer sockaddr peer; socklen_t peerLen = sizeof(peer); getpeername(m_desc, reinterpret_cast (&peer), &peerLen); // If server address as specified when connecting is a numeric // address, try to get a host name for it if (isNumericAddress(m_serverAddress.c_str())) { char host[NI_MAXHOST + 1]; char service[NI_MAXSERV + 1]; if (getnameinfo(reinterpret_cast (&peer), peerLen, host, sizeof(host), service, sizeof(service), /* flags */ NI_NAMEREQD) == 0) { return string(host); } } return m_serverAddress; } size_t windowsSocket::getBlockSize() const { return 16384; // 16 KB } void windowsSocket::receive(vmime::string& buffer) { const size_t size = receiveRaw(m_buffer, sizeof(m_buffer)); buffer = utility::stringUtils::makeStringFromBytes(m_buffer, size); } size_t windowsSocket::receiveRaw(byte_t* buffer, const size_t count) { m_status &= ~STATUS_WOULDBLOCK; // Check whether data is available if (!waitForRead(50 /* msecs */)) { // No data available at this time // 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 { // Reset timeout m_timeoutHandler->resetTimeOut(); } } // Continue waiting for data return 0; } // Read available data int ret = ::recv(m_desc, reinterpret_cast (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) { sendRaw(reinterpret_cast (buffer.data()), buffer.length()); } void windowsSocket::send(const char* str) { sendRaw(reinterpret_cast (str), strlen(str)); } void windowsSocket::sendRaw(const byte_t* buffer, const size_t count) { m_status &= ~STATUS_WOULDBLOCK; size_t size = count; while (size > 0) { const int ret = ::send(m_desc, reinterpret_cast (buffer), size, 0); if (ret == SOCKET_ERROR) { int err = WSAGetLastError(); if (err != WSAEWOULDBLOCK) { throwSocketError(err); } waitForWrite(50 /* msecs */); } else { buffer += ret; size -= ret; } } // Reset timeout if (m_timeoutHandler) { m_timeoutHandler->resetTimeOut(); } } size_t windowsSocket::sendRawNonBlocking(const byte_t* buffer, const size_t count) { m_status &= ~STATUS_WOULDBLOCK; const int ret = ::send(m_desc, reinterpret_cast (buffer), count, 0); if (ret == SOCKET_ERROR) { int err = WSAGetLastError(); if (err == WSAEWOULDBLOCK) { // Check if we are timed out if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) { if (!m_timeoutHandler->handleTimeOut()) { // Could not send data within timeout delay throwSocketError(err); } else { // Reset timeout m_timeoutHandler->resetTimeOut(); } } m_status |= STATUS_WOULDBLOCK; // No data can be written at this time return 0; } else { throwSocketError(err); } } // Reset timeout if (m_timeoutHandler) { m_timeoutHandler->resetTimeOut(); } 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); } bool windowsSocket::waitForData(const bool read, const bool write, const int msecs) { for (int i = 0 ; i <= msecs / 10 ; ++i) { // Check whether data is available fd_set fds; FD_ZERO(&fds); FD_SET(m_desc, &fds); struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 10000; // 10 ms int ret = ::select(m_desc + 1, read ? &fds : NULL, write ? &fds : NULL, NULL, &tv); if (ret == SOCKET_ERROR) { int err = WSAGetLastError(); throwSocketError(err); } else if (ret > 0) { return true; } // No data available at this time // Check if we are timed out if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) { if (!m_timeoutHandler->handleTimeOut()) { // Server did not react within timeout delay throw exceptions::operation_timed_out(); } else { // Reset timeout m_timeoutHandler->resetTimeOut(); } } } return false; // time out } bool windowsSocket::waitForRead(const int msecs) { return waitForData(/* read */ true, /* write */ false, msecs); } bool windowsSocket::waitForWrite(const int msecs) { return waitForData(/* read */ false, /* write */ true, msecs); } shared_ptr windowsSocket::getTimeoutHandler() { return m_timeoutHandler; } void windowsSocket::setTracer(const shared_ptr & tracer) { m_tracer = tracer; } shared_ptr windowsSocket::getTracer() { return m_tracer; } // // posixSocketFactory // shared_ptr windowsSocketFactory::create() { shared_ptr th; return make_shared (th); } shared_ptr windowsSocketFactory::create(const shared_ptr & th) { return make_shared (th); } } // posix } // platforms } // vmime #endif // VMIME_PLATFORM_IS_WINDOWS && VMIME_HAVE_MESSAGING_FEATURES