summaryrefslogtreecommitdiffstats
path: root/libmail/fd.C
diff options
context:
space:
mode:
Diffstat (limited to 'libmail/fd.C')
-rw-r--r--libmail/fd.C850
1 files changed, 850 insertions, 0 deletions
diff --git a/libmail/fd.C b/libmail/fd.C
new file mode 100644
index 0000000..1b4240b
--- /dev/null
+++ b/libmail/fd.C
@@ -0,0 +1,850 @@
+/*
+** Copyright 2002-2008, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+#include "config.h"
+#include "mail.H"
+#include "fd.H"
+#include "fdtls.H"
+#include "logininfo.H"
+#include "soxwrap/soxwrap.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <iostream>
+#include <sstream>
+#include <cstring>
+#include <cstdlib>
+
+using namespace std;
+
+///////////////////////////////////////////////////////////////////////////
+
+string mail::fd::rootCertRequiredErrMsg;
+
+mail::fd::fd(mail::callback::disconnect &disconnect_callback,
+ std::vector<std::string> &certificatesArg) :
+ mail::account(disconnect_callback), socketfd(-1),
+ ioDebugFlag(false), certificates(certificatesArg), writtenFlag(false),
+ tls(NULL)
+{
+}
+
+
+mail::fd::~fd()
+{
+ if (tls)
+ delete tls;
+ if (socketfd >= 0)
+ sox_close(socketfd);
+
+ while (!writequeue.empty())
+ {
+ WriteBuffer *p=writequeue.front();
+ writequeue.pop();
+ delete p;
+ }
+}
+
+string *mail::fd::getWriteBuffer()
+{
+ while (!writequeue.empty())
+ {
+ WriteBuffer *p=writequeue.front();
+
+ if (p->writebuffer.size() > 0)
+ return (&p->writebuffer);
+
+ if (p->fillWriteBuffer())
+ {
+ if (p->writebuffer.size() > 0)
+ return (&p->writebuffer);
+ break; // Handler's waiting for a server response
+ }
+
+ writequeue.pop();
+ delete p;
+ }
+
+ return NULL;
+}
+
+static void debug_io(const char *type, int socketfd,
+ const char *b, const char *e)
+{
+ ostringstream o;
+
+ o << type << "(" << socketfd << "): ";
+
+ while (b != e)
+ {
+ if (*b == '\r')
+ o << "\\r";
+ else if (*b == '\n')
+ o << "\\n";
+ else if (*b == '\t')
+ o << "\\t";
+ else
+ o << (((*b) & 127) < 32 ? '.':*b);
+ b++;
+ }
+ o << endl;
+
+ string s=o.str();
+
+ if (write(2, s.c_str(), s.size()) < 0)
+ ; // Ignore gcc warning
+}
+
+#define DEBUG_IO(a,b,c) \
+ do { if (ioDebugFlag) debug_io((a),(socketfd),(b),(c)); } while (0)
+
+string mail::fd::socketDisconnect()
+{
+ string errmsg="";
+
+ if (socketfd < 0)
+ return errmsg;
+
+ DEBUG_IO("Socket.CLOSE", NULL, NULL);
+
+#if HAVE_LIBCOURIERTLS
+
+ if (tls)
+ {
+ if (tls->errmsg.size() > 0)
+ errmsg=tls->errmsg;
+
+ delete tls;
+ tls=NULL;
+ }
+#endif
+
+ sox_close(socketfd);
+ socketfd= -1;
+
+ return errmsg;
+}
+
+string mail::fd::socketConnect(mail::loginInfo &loginInfo,
+ const char *plainservice,
+ const char *sslservice)
+{
+
+#if HAVE_LIBCOURIERTLS
+
+#else
+ if (loginInfo.use_ssl)
+ return "SSL support not available.";
+
+#endif
+
+ struct addrinfo *res;
+
+ string server=loginInfo.server, portnum;
+ const char *port=loginInfo.use_ssl ? sslservice:plainservice;
+ size_t n=server.find(':');
+
+ if (n != std::string::npos)
+ {
+ port=NULL;
+ portnum=server.substr(n+1);
+ server=server.substr(0, n);
+ }
+
+ if (loginInfo.options.count("debugio") > 0)
+ ioDebugFlag=true;
+
+ struct addrinfo hints;
+
+ memset(&hints, 0, sizeof(hints));
+
+ hints.ai_family=PF_UNSPEC;
+ hints.ai_socktype=SOCK_STREAM;
+
+ int errcode=getaddrinfo(server.c_str(), port, &hints, &res);
+
+ if (errcode)
+ return gai_strerror(errcode);
+
+ if (!port)
+ {
+ int nport=atoi(portnum.c_str());
+
+ if (nport <= 0 || nport > 65535)
+ {
+ freeaddrinfo(res);
+ return strerror(EINVAL);
+ }
+
+ switch (res->ai_addr->sa_family) {
+ case AF_INET:
+ ((struct sockaddr_in *)res->ai_addr)
+ ->sin_port=htons(nport);
+ break;
+#ifdef AF_INET6
+ case AF_INET6:
+ ((struct sockaddr_in6 *)res->ai_addr)
+ ->sin6_port=htons(nport);
+ break;
+#endif
+ default:
+ freeaddrinfo(res);
+ return strerror(EAFNOSUPPORT);
+ }
+ }
+
+ socketfd=socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+
+ connecting=1;
+
+ // Put socket into non-blocking mode. We're 100% asynchronous.
+ // Set close-on-exec bit, we don't want this fd to be seen by any
+ // other process.
+
+ if (socketfd < 0 || fcntl(socketfd, F_SETFL, O_NONBLOCK) < 0 ||
+ fcntl(socketfd, F_SETFD, FD_CLOEXEC) < 0)
+ {
+ freeaddrinfo(res);
+ return strerror(errno);
+ }
+
+#if HAVE_LIBCOURIERTLS
+
+ if (loginInfo.use_ssl)
+ {
+ string errmsg= starttls(loginInfo, false);
+
+ if (errmsg.size() > 0)
+ {
+ freeaddrinfo(res);
+ return errmsg;
+ }
+ }
+#endif
+
+ if (sox_connect(socketfd, res->ai_addr, res->ai_addrlen) < 0)
+ {
+ freeaddrinfo(res);
+ if (errno != EINPROGRESS)
+ return strerror(errno);
+
+ // Async connection in progress.
+ }
+ else // Managed to connect right away.
+ {
+ freeaddrinfo(res);
+ connecting=3; // Managed to skip some steps in process()
+ }
+
+ return "";
+}
+
+string mail::fd::socketAttach(int fd)
+{
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0 ||
+ fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
+ {
+ return strerror(errno);
+ }
+
+ socketfd=fd;
+ connecting=3;
+ return "";
+}
+
+bool mail::fd::socketBeginEncryption(mail::loginInfo &loginInfo)
+{
+#if HAVE_LIBCOURIERTLS
+
+ string errmsg=starttls(loginInfo, true);
+
+ if (errmsg.size() > 0)
+ {
+ loginInfo.callbackPtr->fail(errmsg);
+ return false;
+ }
+
+ return establishtls();
+
+#else
+
+
+ loginInfo.callbackPtr->fail("SSL/TLS encryption not available.");
+ return false;
+
+#endif
+}
+
+time_t mail::fd::getTimeoutSetting(mail::loginInfo &loginInfo,
+ const char *name, time_t defaultValue,
+ time_t minValue, time_t maxValue)
+{
+ map<string, string>::iterator p=loginInfo.options.find(name);
+
+ if (p != loginInfo.options.end())
+ {
+ istringstream i(p->second);
+
+ i >> defaultValue;
+ }
+
+ if (defaultValue < minValue)
+ defaultValue=minValue;
+
+ if (defaultValue > maxValue)
+ defaultValue=maxValue;
+ return defaultValue;
+}
+
+//
+// Allocate a new TLS structure. Called before any TLS/SSL negotiation
+// takes place.
+//
+
+string mail::fd::starttls(mail::loginInfo &loginInfo,
+ bool starttlsFlag)
+{
+#if HAVE_LIBCOURIERTLS
+
+ if (tls)
+ return "";
+
+ if ((tls=new mail::fdTLS(starttlsFlag, certificates)) == NULL)
+ return strerror(errno);
+
+ tls->fd=socketfd;
+ loginInfo.tlsInfo=tls;
+ //tls->callback= loginInfo.callbackPtr;
+ tls->tls_info.app_data=tls;
+ tls->tls_info.getconfigvar= &mail::fdTLS::get_tls_config_var;
+ tls->tls_info.tls_err_msg= &mail::fdTLS::get_tls_err_msg;
+ tls->tls_info.getpemclientcert4ca=
+ &mail::fdTLS::get_tls_client_certs;
+ tls->tls_info.releasepemclientcert4ca=
+ &mail::fdTLS::free_tls_client_certs;
+
+ tls->domain="";
+
+ if (loginInfo.options.count("novalidate-cert") == 0)
+ {
+ tls->domain=loginInfo.server;
+ tls->tls_info.peer_verify_domain=tls->domain.c_str();
+
+ const char *p=mail::fdTLS
+ ::get_tls_config_var("TLS_TRUSTCERTS", tls);
+
+ if (!p || !*p)
+ {
+ if (rootCertRequiredErrMsg.size() > 0)
+ return rootCertRequiredErrMsg;
+ }
+ }
+#endif
+ return "";
+}
+
+//
+// Begin SSL/TLS negotiation.
+//
+
+bool mail::fd::establishtls()
+{
+#if HAVE_LIBCOURIERTLS
+
+ tls->errmsg="Unable to establish a secure connection.";
+ if ((tls->ctx=tls_create(0, &tls->tls_info)) == NULL ||
+ (tls->ssl=tls_connect(tls->ctx, socketfd)) == NULL)
+ {
+ string errmsg=tls->errmsg;
+ // mail::callback *c=tls->callback;
+
+ delete tls;
+ tls=NULL;
+
+ disconnect(errmsg.c_str());
+ //if (c)
+ // c->fail(errmsg);
+ return false;
+ }
+
+ tls_transfer_init(&tls->tls_transfer);
+#endif
+
+ return true;
+}
+
+bool mail::fd::socketConnected()
+{
+ if (socketfd < 0)
+ return false;
+
+#if HAVE_LIBCOURIERTLS
+ if (tls)
+ tls->errmsg="";
+#endif
+ return true;
+}
+
+bool mail::fd::socketEndEncryption()
+{
+#if HAVE_LIBCOURIERTLS
+ if (tls)
+ {
+ tls_closing(&tls->tls_transfer);
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+//
+// Handle any pending I/O to/from the server.
+//
+
+int mail::fd::process(std::vector<pollfd> &fds, int &timeout)
+{
+ mail::pollfd myPollFd;
+
+ char buffer[BUFSIZ];
+ int n;
+ socklen_t s;
+
+ bool mustReadSomething=false;
+
+ myPollFd.fd=socketfd;
+ myPollFd.events=0;
+ myPollFd.revents=0;
+
+ if (socketfd < 0) // Disconnected, flush anything that's written.
+ {
+ while (!writequeue.empty())
+ {
+ WriteBuffer *p=writequeue.front();
+ writequeue.pop();
+ delete p;
+ }
+ return (-1);
+ }
+
+ // First step in asynchronous connection logic: wait for the socket
+ // to become writable.
+
+ switch (connecting) {
+ case 1:
+ myPollFd.events=pollwrite;
+ fds.push_back(myPollFd);
+ ++connecting;
+ return (0);
+ case 2:
+
+ // Socket is writable, check the error code of the connection
+ // request.
+
+ s=sizeof(n);
+ if (sox_getsockopt(socketfd, SOL_SOCKET, SO_ERROR, &n, &s) < 0
+ || (errno=EINVAL, s) < sizeof(n)
+ || (errno=n) != 0)
+ {
+ timeout=0;
+ disconnect(strerror(errno));
+ return (-1);
+ }
+
+ /* FALLTHROUGH */
+
+ case 3:
+ connecting=0;
+
+ // Step 3 - begin SSL/TLS negotation, if requested.
+
+#if HAVE_LIBCOURIERTLS
+
+ if (tls)
+ if (!establishtls())
+ {
+ timeout=0;
+ return (-1);
+ }
+#endif
+ }
+
+ string *writePtr;
+
+#if HAVE_LIBCOURIERTLS
+
+ // When the connection has SSL/TLS enabled, everything gets handled
+ // by libcouriertls.
+
+ if (tls)
+ {
+ if (tls->tls_transfer.writeleft == 0 &&
+ (writePtr=getWriteBuffer()) != NULL)
+ {
+ // Transfer the next chunk of data to write to the
+ // TLS buffer
+
+ tls->writebuffer= *writePtr;
+ tls->tls_transfer.writeleft=tls->writebuffer.size();
+ tls->tls_transfer.writeptr=&tls->writebuffer[0];
+ writtenBuffer(writePtr->size());
+ // As far as the write queue is concerned, this
+ // stuff was written.
+
+ }
+
+ // Repeatedly call tls_transfer() until it stops doing
+ // something useful.
+
+ for (;;)
+ {
+ // Set the read ptr to start of the read buffer,
+ // remember the current write ptr, then call
+ // tls_transfer()
+
+ tls->tls_transfer.readptr=tls->readBuffer;
+ tls->tls_transfer.readleft=sizeof(tls->readBuffer);
+
+ const char *save_writeptr=tls->tls_transfer.writeptr;
+
+ int rc;
+ {
+ fd_set r, w;
+
+ FD_ZERO(&r);
+ FD_ZERO(&w);
+
+ rc=tls_transfer(&tls->tls_transfer, tls->ssl,
+ socketfd, &r, &w);
+
+ if (FD_ISSET(socketfd, &r))
+ myPollFd.events |= pollread;
+
+ if (FD_ISSET(socketfd, &w))
+ myPollFd.events |= pollwrite;
+ }
+
+ if ((tls_isclosing(&tls->tls_transfer) ||
+ tls_isclosed(&tls->tls_transfer)) &&
+ !tls->tlsShutdownSent)
+ {
+ tls->tlsShutdownSent=true;
+ }
+
+ readbuffer.append(tls->readBuffer,
+ tls->tls_transfer.readptr);
+
+ // We might've read something.
+
+ if (rc < 0)
+ {
+#if 0
+ cerr << "DEBUG: SSL connection terminate: buffer="
+ << readbuffer.size()
+ << ", tls_isclosed: " << tls_isclosed(&tls->tls_transfer)
+ << ", errmsg=" << tls->errmsg
+ << endl;
+#endif
+
+ if (readbuffer.size() > 0)
+ {
+ // There's still unfinished stuff in
+ // readbuffer, so pretend we're happy,
+ // for now, but set retry to 0 seconds,
+ // so we go back in here to process
+ // more read data.
+
+ myPollFd.events |= pollwrite;
+ // If we don't do that, we'll return
+ // without requesting any r/w activity,
+ // so the application will now stall.
+ // Let's request write status, which
+ // should come back immediately and
+ // result in -1 return from
+ // tls_transfer().
+
+ mustReadSomething=true;
+
+ break; // Finish processing read data
+ }
+
+ timeout=0;
+
+ DEBUG_IO("EOF.SSL", NULL, NULL);
+ if (tls)
+ disconnect(tls_isclosed(&tls->
+ tls_transfer)
+ ? NULL:strerror(errno));
+ return (-1);
+ }
+
+ if (save_writeptr != tls->tls_transfer.writeptr)
+ {
+ DEBUG_IO("Write.SSL",save_writeptr,
+ tls->tls_transfer.writeptr);
+ }
+
+ if (tls->tls_transfer.readptr != tls->readBuffer)
+ {
+ DEBUG_IO("Read.SSL",
+ tls->readBuffer,
+ tls->tls_transfer.readptr);
+ }
+
+ if (save_writeptr != tls->tls_transfer.writeptr ||
+ tls->tls_transfer.readptr != tls->readBuffer)
+ {
+ timeout=0;
+ }
+ break;
+ }
+ }
+ else
+#endif
+
+ // Connection not encrypted
+
+ for (;;)
+ {
+ n=sox_read(socketfd, buffer, sizeof(buffer));
+
+ if (n < 0)
+ {
+ if (errno == EAGAIN)
+ {
+ myPollFd.events |= pollread;
+ break;
+ }
+ DEBUG_IO("Read.ERROR", NULL, NULL);
+ timeout=0;
+ disconnect(strerror(errno));
+ return (-1);
+ }
+
+ if (n == 0)
+ {
+ timeout=0;
+ mustReadSomething=true;
+ break;
+ }
+
+ if (n > 0)
+ DEBUG_IO("Read", buffer, buffer+n);
+
+ readbuffer=readbuffer.append(buffer, n);
+ if (readbuffer.length() >= sizeof(buffer))
+ {
+ myPollFd.events |= pollread;
+ break;
+ }
+ }
+
+
+
+ while (
+#if HAVE_LIBCOURIERTLS
+
+ !tls &&
+#endif
+ (writePtr=getWriteBuffer()) != NULL)
+ {
+ n= ::sox_write(socketfd, &(*writePtr)[0], writePtr->size());
+
+ if (n < 0)
+ {
+ DEBUG_IO("Write.ERROR", NULL, NULL);
+ if (errno == EAGAIN)
+ {
+ myPollFd.events |= pollwrite;
+ break;
+ }
+ timeout=0;
+ disconnect(strerror(errno));
+ return (-1);
+ }
+
+ if (n > 0)
+ {
+ const char *p=(*writePtr).c_str();
+
+ DEBUG_IO("Write", p, p+n);
+ }
+
+ if (n == 0)
+ {
+ DEBUG_IO("Write.EOF", NULL, NULL);
+ timeout=0;
+ disconnect("Connection closed by remote host.");
+ return (-1);
+ }
+
+ writtenBuffer(n);
+ }
+
+ //
+ // Try to process any read data.
+ //
+
+ MONITOR(mail::account);
+
+ while (readbuffer.length() > 0)
+ {
+#if 0
+ static size_t skip=0;
+
+ if (skip == 0)
+ {
+ cerr << "PROCESS: [" << readbuffer << "]" << endl;
+
+ char linebuf[80];
+
+ cin.getline(linebuf, sizeof(linebuf));
+ if (strcmp(linebuf, "y") == 0)
+ {
+ timeout=0;
+ disconnect("Forced disconnect");
+ return (-1);
+ }
+
+ if (isdigit(linebuf[0]))
+ istrstream(linebuf) >> skip;
+ }
+ else
+ --skip;
+#endif
+
+#if 0
+ {
+ string cpy=readbuffer;
+
+ if (cpy.length() > 32)
+ {
+ cpy.erase(32);
+ cpy += "...";
+ }
+
+ string::iterator b=cpy.begin(), e=cpy.end();
+
+ while (b != e)
+ {
+ if (iscntrl((int)(unsigned char)*b))
+ *b='.';
+ b++;
+ }
+
+ cerr << "Process: [" << cpy << "] ("
+ << readbuffer.length() << " bytes)" << endl;
+ }
+#endif
+
+ int n=socketRead(readbuffer);
+
+ if (DESTROYED())
+ {
+ timeout=0;
+ return (0);
+ }
+
+ if (n <= 0)
+ {
+ timeout=0;
+
+ if (mustReadSomething)
+ break;
+
+ if (myPollFd.events)
+ fds.push_back(myPollFd);
+ return n;
+ }
+
+ mustReadSomething=false;
+
+ readbuffer.erase(0, n);
+
+#if 0
+ if (getWriteBuffer() != NULL && socketfd >= 0)
+#endif
+ {
+ timeout=0; // Callback handler wrote something
+ }
+ }
+#if HAVE_LIBCOURIERTLS
+ if (!DESTROYED() && tls && tls_isclosing(&tls->tls_transfer) &&
+ !tls_isclosed(&tls->tls_transfer))
+ {
+ if (!tls->tlsShutdownSent)
+ myPollFd.events |= pollwrite;
+ else
+ myPollFd.events |= pollread;
+ readbuffer="";
+ mustReadSomething=false;
+ }
+#endif
+
+ if (mustReadSomething)
+ {
+ DEBUG_IO("Read.EOF", NULL, NULL);
+ // Subclass hasn't processed any data, and it
+ // will never see any more data, so give up.
+
+ disconnect("Connection closed by remote host.");
+ return -1;
+ }
+
+ if (myPollFd.events)
+ fds.push_back(myPollFd);
+
+ return 0;
+}
+
+// Write a string. Queue it up, and wait for process() to do its job
+
+void mail::fd::socketWrite(string s)
+{
+ writtenFlag=true;
+
+ WriteBuffer *w=new WriteBuffer;
+
+ if (!w)
+ LIBMAIL_THROW("Out of memory.");
+
+ try {
+ w->writebuffer=s;
+ writequeue.push(w);
+ } catch (...) {
+ delete w;
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+}
+
+void mail::fd::socketWrite(WriteBuffer *w)
+{
+ writtenFlag=true;
+ writequeue.push(w);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Initialize the WriteBuffer superclass.
+
+mail::fd::WriteBuffer::WriteBuffer() : writebuffer("")
+{
+}
+
+mail::fd::WriteBuffer::~WriteBuffer()
+{
+}
+
+bool mail::fd::WriteBuffer::fillWriteBuffer()
+{
+ return false;
+}
+