// // 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_POSIX && VMIME_HAVE_MESSAGING_FEATURES #include "vmime/platforms/posix/posixSocket.hpp" #include "vmime/platforms/posix/posixHandler.hpp" #ifndef _GNU_SOURCE #define _GNU_SOURCE // for getaddrinfo_a() in #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "vmime/utility/stringUtils.hpp" #include "vmime/exception.hpp" #if defined(EWOULDBLOCK) # define IS_EAGAIN(x) ((x) == EAGAIN || (x) == EWOULDBLOCK || (x) == EINTR || (x) == EINPROGRESS) #else # define IS_EAGAIN(x) ((x) == EAGAIN || (x) == EINTR || (x) == EINPROGRESS) #endif // Workaround for detection of strerror_r variants #if VMIME_HAVE_STRERROR_R namespace { char* vmime_strerror_r_result(int /* res */, char* buf) { // XSI-compliant prototype: // int strerror_r(int errnum, char *buf, size_t buflen); return buf; } char* vmime_strerror_r_result(char* res, char* /* buf */) { // GNU-specific prototype: // char *strerror_r(int errnum, char *buf, size_t buflen); return res; } } #endif // VMIME_HAVE_STRERROR_R namespace vmime { namespace platforms { namespace posix { // // posixSocket // posixSocket::posixSocket(shared_ptr th) : m_timeoutHandler(th), m_desc(-1), m_status(0) { } posixSocket::~posixSocket() { if (m_desc != -1) { ::close(m_desc); } } void posixSocket::connect(const vmime::string& address, const vmime::port_t port) { // Close current connection, if any if (m_desc != -1) { ::close(m_desc); m_desc = -1; } if (m_tracer) { std::ostringstream trace; trace << "Connecting to " << address << ", port " << port; m_tracer->traceSend(trace.str()); } #if VMIME_HAVE_GETADDRINFO // use thread-safe and IPv6-aware getaddrinfo() if available // Resolve address, if needed m_serverAddress = address; struct ::addrinfo* addrInfo = NULL; // resolved addresses resolve(&addrInfo, address, port); // Connect to host int sock = -1; int connectErrno = 0; if (m_timeoutHandler != NULL) { m_timeoutHandler->resetTimeOut(); } for (struct ::addrinfo* curAddrInfo = addrInfo ; sock == -1 && curAddrInfo != NULL ; curAddrInfo = curAddrInfo->ai_next, connectErrno = ETIMEDOUT) { if (curAddrInfo->ai_family != AF_INET && curAddrInfo->ai_family != AF_INET6) { continue; } sock = ::socket(curAddrInfo->ai_family, curAddrInfo->ai_socktype, curAddrInfo->ai_protocol); if (sock < 0) { connectErrno = errno; continue; // try next } #if VMIME_HAVE_SO_KEEPALIVE // Enable TCP Keepalive int keepAlive_optval = 1; socklen_t keepAlive_optlen = sizeof(keepAlive_optval); ::setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive_optval, keepAlive_optlen); #endif // VMIME_HAVE_SO_KEEPALIVE #if VMIME_HAVE_SO_NOSIGPIPE // Return EPIPE instead of generating SIGPIPE int nosigpipe_optval = 1; socklen_t nosigpipe_optlen = sizeof(nosigpipe_optval); ::setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe_optval, nosigpipe_optlen); #endif // VMIME_HAVE_SO_NOSIGPIPE if (m_timeoutHandler) { ::fcntl(sock, F_SETFL, ::fcntl(sock, F_GETFL) | O_NONBLOCK); if (::connect(sock, curAddrInfo->ai_addr, curAddrInfo->ai_addrlen) < 0) { switch (errno) { case 0: case EINPROGRESS: case EINTR: #if defined(EAGAIN) case EAGAIN: #endif // EAGAIN #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) case EWOULDBLOCK: #endif // EWOULDBLOCK // Connection in progress break; default: connectErrno = errno; ::close(sock); sock = -1; continue; // try next } // Wait for socket to be connected. bool connected = false; const int pollTimeout = 1000; // poll() timeout (ms) const int tryNextTimeout = 5000; // maximum time before trying next (ms) timeval startTime = { 0, 0 }; gettimeofday(&startTime, /* timezone */ NULL); do { pollfd fds[1]; fds[0].fd = sock; fds[0].events = POLLIN | POLLOUT; const int ret = ::poll(fds, sizeof(fds) / sizeof(fds[0]), pollTimeout); // Success if (ret > 0) { if (fds[0].revents & (POLLIN | POLLOUT)) { int error = 0; socklen_t len = sizeof(error); if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { connectErrno = errno; } else { if (error != 0) { connectErrno = error; } else { connected = true; } } } break; // Error } else if (ret < -1) { if (errno != EAGAIN && errno != EINTR) { // Cancel connection connectErrno = errno; break; } } // Check for timeout if (m_timeoutHandler->isTimeOut()) { if (!m_timeoutHandler->handleTimeOut()) { // Cancel connection connectErrno = ETIMEDOUT; break; } else { // Reset timeout and keep waiting for connection m_timeoutHandler->resetTimeOut(); } } else { // Keep waiting for connection } timeval curTime = { 0, 0 }; gettimeofday(&curTime, /* timezone */ NULL); if (curAddrInfo->ai_next != NULL && curTime.tv_usec - startTime.tv_usec >= tryNextTimeout * 1000) { connectErrno = ETIMEDOUT; break; } } while (true); if (!connected) { ::close(sock); sock = -1; continue; // try next } break; } else { // Connection successful break; } } else { if (::connect(sock, curAddrInfo->ai_addr, curAddrInfo->ai_addrlen) < 0) { connectErrno = errno; ::close(sock); sock = -1; continue; // try next } } } ::freeaddrinfo(addrInfo); if (sock == -1) { try { throwSocketError(connectErrno); } catch (exceptions::socket_exception& e) { // wrap throw vmime::exceptions::connection_error("Error while connecting socket.", e); } } m_desc = sock; #else // !VMIME_HAVE_GETADDRINFO // 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 (addr.sin_addr.s_addr == static_cast (-1)) { ::hostent* hostInfo = ::gethostbyname(address.c_str()); if (hostInfo == NULL) { // 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 == -1) { try { throwSocketError(errno); } catch (exceptions::socket_exception& e) { // wrap throw vmime::exceptions::connection_error("Error while creating socket.", e); } } // Start connection if (::connect(m_desc, reinterpret_cast (&addr), sizeof(addr)) == -1) { try { throwSocketError(errno); } catch (exceptions::socket_exception& e) { // wrap ::close(m_desc); m_desc = -1; // Error throw vmime::exceptions::connection_error("Error while connecting socket.", e); } } #endif // VMIME_HAVE_GETADDRINFO ::fcntl(m_desc, F_SETFL, ::fcntl(m_desc, F_GETFL) | O_NONBLOCK); } void posixSocket::resolve( struct ::addrinfo** addrInfo, const vmime::string& address, const vmime::port_t port ) { char portStr[16]; snprintf(portStr, sizeof(portStr), "%u", static_cast (port)); struct ::addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME | AI_NUMERICSERV; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; #if VMIME_HAVE_GETADDRINFO_A // If getaddrinfo_a() is available, use asynchronous resolving to allow // the timeout handler to cancel the operation struct ::gaicb gaiRequest; memset(&gaiRequest, 0, sizeof(gaiRequest)); gaiRequest.ar_name = address.c_str(); gaiRequest.ar_service = portStr; gaiRequest.ar_request = &hints; struct ::gaicb* gaiRequests = &gaiRequest; int gaiError; if ((gaiError = getaddrinfo_a(GAI_NOWAIT, &gaiRequests, 1, NULL)) != 0) { throw vmime::exceptions::connection_error( "getaddrinfo_a() failed: " + std::string(gai_strerror(gaiError)) ); } if (m_timeoutHandler) { m_timeoutHandler->resetTimeOut(); } while (true) { struct timespec gaiTimeout; gaiTimeout.tv_sec = 1; // query timeout handler every second gaiTimeout.tv_nsec = 0; gaiError = gai_suspend(&gaiRequests, 1, &gaiTimeout); if (gaiError == 0 || gaiError == EAI_ALLDONE) { const int ret = gai_error(&gaiRequest); if (ret != 0) { throw vmime::exceptions::connection_error( "getaddrinfo_a() request failed: " + std::string(gai_strerror(ret)) ); } else { *addrInfo = gaiRequest.ar_result; break; } } else if (gaiError != EAI_AGAIN) { if (gaiError == EAI_SYSTEM) { const int ret = gai_error(&gaiRequest); if (ret != EAI_INPROGRESS && errno != 0) { try { throwSocketError(errno); } catch (exceptions::socket_exception& e) { // wrap throw vmime::exceptions::connection_error("Error while connecting socket.", e); } } } else { throw vmime::exceptions::connection_error( "gai_suspend() failed: " + std::string(gai_strerror(gaiError)) ); } } // Check for timeout if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) { if (!m_timeoutHandler->handleTimeOut()) { throw exceptions::operation_timed_out(); } else { // Reset timeout and keep waiting for connection m_timeoutHandler->resetTimeOut(); } } } #else // !VMIME_HAVE_GETADDRINFO_A if (::getaddrinfo(address.c_str(), portStr, &hints, addrInfo) != 0) { // Error: cannot resolve address throw vmime::exceptions::connection_error("Cannot resolve address."); } #endif // VMIME_HAVE_GETADDRINFO_A } bool posixSocket::isConnected() const { if (m_desc == -1) { return false; } char buff; return ::recv(m_desc, &buff, 1, MSG_PEEK) != 0; } void posixSocket::disconnect() { if (m_desc != -1) { if (m_tracer) { m_tracer->traceSend("Disconnecting"); } ::shutdown(m_desc, SHUT_RDWR); ::close(m_desc); m_desc = -1; } } static bool isNumericAddress(const char* address) { #if VMIME_HAVE_GETADDRINFO 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; } #else return inet_addr(address) != INADDR_NONE; #endif } const string posixSocket::getPeerAddress() const { // Get address of connected peer sockaddr peer; socklen_t peerLen = sizeof(peer); if (getpeername(m_desc, &peer, &peerLen) != 0) { throwSocketError(errno); } // Convert to numerical presentation format char buf[INET6_ADDRSTRLEN]; if (!inet_ntop(peer.sa_family, &(reinterpret_cast (&peer))->sin_addr, buf, sizeof(buf))) { throwSocketError(errno); } return string(buf); } const string posixSocket::getPeerName() const { // Get address of connected peer sockaddr peer; socklen_t peerLen = sizeof(peer); if (getpeername(m_desc, &peer, &peerLen) != 0) { throwSocketError(errno); } // 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())) { #if VMIME_HAVE_GETNAMEINFO 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); } #else struct hostent *hp; if ((hp = gethostbyaddr(reinterpret_cast (&peer), sizeof(peer), peer.sa_family)) != NULL) { return string(hp->h_name); } #endif } return m_serverAddress; } size_t posixSocket::getBlockSize() const { return 16384; // 16 KB } bool posixSocket::waitForData(const bool read, const bool write, const int msecs) { for (int i = 0 ; i <= msecs / 10 ; ++i) { // Check whether data is available pollfd fds[1]; fds[0].fd = m_desc; fds[0].events = 0; if (read) { fds[0].events |= POLLIN; } if (write) { fds[0].events |= POLLOUT; } const int ret = ::poll(fds, sizeof(fds) / sizeof(fds[0]), 10 /* ms */); if (ret < 0) { if (errno != EAGAIN && errno != EINTR) { throwSocketError(errno); } } else if (ret > 0) { if (fds[0].revents & (POLLIN | POLLOUT)) { 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 posixSocket::waitForRead(const int msecs) { return waitForData(/* read */ true, /* write */ false, msecs); } bool posixSocket::waitForWrite(const int msecs) { return waitForData(/* read */ false, /* write */ true, msecs); } void posixSocket::receive(vmime::string& buffer) { const size_t size = receiveRaw(m_buffer, sizeof(m_buffer)); buffer = utility::stringUtils::makeStringFromBytes(m_buffer, size); } size_t posixSocket::receiveRaw(byte_t* buffer, const size_t count) { m_status &= ~STATUS_WOULDBLOCK; // Check whether data is available if (!waitForRead(50 /* msecs */)) { m_status |= STATUS_WOULDBLOCK; // Continue waiting for data return 0; } // Read available data ssize_t ret = ::recv(m_desc, buffer, count, 0); if (ret < 0) { if (!IS_EAGAIN(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 return 0; } else if (ret == 0) { // Host shutdown throwSocketError(ENOTCONN); } else { // Data received, reset timeout if (m_timeoutHandler) { m_timeoutHandler->resetTimeOut(); } } return ret; } void posixSocket::send(const vmime::string& buffer) { sendRaw(reinterpret_cast (buffer.data()), buffer.length()); } void posixSocket::send(const char* str) { sendRaw(reinterpret_cast (str), ::strlen(str)); } void posixSocket::sendRaw(const byte_t* buffer, const size_t count) { m_status &= ~STATUS_WOULDBLOCK; size_t size = count; while (size > 0) { #if VMIME_HAVE_MSG_NOSIGNAL const ssize_t ret = ::send(m_desc, buffer, size, MSG_NOSIGNAL); #else const ssize_t ret = ::send(m_desc, buffer, size, 0); #endif if (ret <= 0) { if (ret < 0 && !IS_EAGAIN(errno)) { throwSocketError(errno); } waitForWrite(50 /* msecs */); } else { buffer += ret; size -= ret; } } // Reset timeout if (m_timeoutHandler) { m_timeoutHandler->resetTimeOut(); } } size_t posixSocket::sendRawNonBlocking(const byte_t* buffer, const size_t count) { m_status &= ~STATUS_WOULDBLOCK; #if VMIME_HAVE_MSG_NOSIGNAL const ssize_t ret = ::send(m_desc, buffer, count, MSG_NOSIGNAL); #else const ssize_t ret = ::send(m_desc, buffer, count, 0); #endif if (ret <= 0) { if (ret < 0 && !IS_EAGAIN(errno)) { throwSocketError(errno); } // Check if we are timed out if (m_timeoutHandler && m_timeoutHandler->isTimeOut()) { if (!m_timeoutHandler->handleTimeOut()) { // Could not send data within timeout delay throw exceptions::operation_timed_out(); } else { // Reset timeout m_timeoutHandler->resetTimeOut(); } } m_status |= STATUS_WOULDBLOCK; // No data can be written at this time return 0; } // Reset timeout if (m_timeoutHandler) { m_timeoutHandler->resetTimeOut(); } return ret; } void posixSocket::throwSocketError(const int err) { const char* msg = NULL; switch (err) { case EACCES: msg = "EACCES: permission denied"; break; case EAFNOSUPPORT: msg = "EAFNOSUPPORT: address family not supported"; break; case EMFILE: msg = "EMFILE: process file table overflow"; break; case ENFILE: msg = "ENFILE: system limit reached"; break; case EPROTONOSUPPORT: msg = "EPROTONOSUPPORT: protocol not supported"; break; case EAGAIN: msg = "EGAIN: blocking operation"; break; case EBADF: msg = "EBADF: invalid descriptor"; break; case ECONNRESET: msg = "ECONNRESET: connection reset by peer"; break; case EFAULT: msg = "EFAULT: bad user space address"; break; case EINTR: msg = "EINTR: signal occurred before transmission"; break; case EINVAL: msg = "EINVAL: invalid argument"; break; case EMSGSIZE: msg = "EMSGSIZE: message cannot be sent atomically"; break; case ENOBUFS: msg = "ENOBUFS: output queue is full"; break; case ENOMEM: msg = "ENOMEM: out of memory"; break; case EPIPE: msg = "EPIPE: broken pipe"; break; case ENOTCONN: msg = "ENOTCONN: not connected"; break; case ECONNREFUSED: msg = "ECONNREFUSED: connection refused"; break; } if (msg) { throw exceptions::socket_exception(msg); } else { // Use strerror() to get string describing error number #if VMIME_HAVE_STRERROR_R char errbuf[512]; throw exceptions::socket_exception( vmime_strerror_r_result( strerror_r(err, errbuf, sizeof(errbuf)), errbuf ) ); #else // !VMIME_HAVE_STRERROR_R const std::string strmsg(strerror(err)); throw exceptions::socket_exception(strmsg); #endif // VMIME_HAVE_STRERROR_R } } unsigned int posixSocket::getStatus() const { return m_status; } shared_ptr posixSocket::getTimeoutHandler() { return m_timeoutHandler; } void posixSocket::setTracer(const shared_ptr & tracer) { m_tracer = tracer; } shared_ptr posixSocket::getTracer() { return m_tracer; } // // posixSocketFactory // shared_ptr posixSocketFactory::create() { shared_ptr th; return make_shared (th); } shared_ptr posixSocketFactory::create(const shared_ptr & th) { return make_shared (th); } } // posix } // platforms } // vmime #endif // VMIME_PLATFORM_IS_POSIX && VMIME_HAVE_MESSAGING_FEATURES