summaryrefslogtreecommitdiffstats
path: root/libmail/smtp.C
diff options
context:
space:
mode:
authorSam Varshavchik2013-08-19 16:39:41 -0400
committerSam Varshavchik2013-08-25 14:43:51 -0400
commit9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch)
tree7a81a04cb51efb078ee350859a64be2ebc6b8813 /libmail/smtp.C
parenta9520698b770168d1f33d6301463bb70a19655ec (diff)
downloadcourier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2
Initial checkin
Imported from subversion report, converted to git. Updated all paths in scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'libmail/smtp.C')
-rw-r--r--libmail/smtp.C1420
1 files changed, 1420 insertions, 0 deletions
diff --git a/libmail/smtp.C b/libmail/smtp.C
new file mode 100644
index 0000000..e225f8d
--- /dev/null
+++ b/libmail/smtp.C
@@ -0,0 +1,1420 @@
+/*
+** Copyright 2002-2011, Double Precision Inc.
+**
+** See COPYING for distribution information.
+*/
+#include "libmail_config.h"
+#include "misc.H"
+#include "smtp.H"
+#include "driver.H"
+#include "search.H"
+#include "imaphmac.H"
+#include "base64.H"
+#include "smtpfolder.H"
+#include "tcpd/spipe.h"
+#include "unicode/unicode.h"
+#include "libcouriertls.h"
+
+#include <errno.h>
+
+#include <sstream>
+#include <iomanip>
+
+#include <sys/types.h>
+#include <signal.h>
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
+#endif
+#ifndef WIFEXITED
+#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
+#endif
+
+using namespace std;
+
+#define SMTPTIMEOUT 60
+
+#define INACTIVITY_TIMEOUT 30
+
+#define DIGIT(c) (strchr("0123456789", (c)))
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+LIBMAIL_START
+
+static bool open_smtp(mail::account *&accountRet,
+ mail::account::openInfo &oi,
+ mail::callback &callback,
+ mail::callback::disconnect &disconnectCallback)
+{
+ mail::loginInfo nntpLoginInfo;
+
+ if (!mail::loginUrlDecode(oi.url, nntpLoginInfo))
+ return false;
+
+ if (nntpLoginInfo.method != "smtp" &&
+ nntpLoginInfo.method != "smtps" &&
+ nntpLoginInfo.method != "sendmail")
+ return false;
+
+ accountRet=new mail::smtp(oi.url, oi.pwd, oi.certificates, callback,
+ disconnectCallback,
+ oi.loginCallbackObj);
+ return true;
+}
+
+static bool smtp_remote(string url, bool &flag)
+{
+ mail::loginInfo nntpLoginInfo;
+
+ if (!mail::loginUrlDecode(url, nntpLoginInfo))
+ return false;
+
+ if (nntpLoginInfo.method != "smtp" &&
+ nntpLoginInfo.method != "smtps")
+ return false;
+
+ flag=true;
+ return true;
+}
+
+driver smtp_driver= { &open_smtp, &smtp_remote };
+
+LIBMAIL_END
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Helper class sends outbound message
+//
+///////////////////////////////////////////////////////////////////////////
+
+mail::smtp::Blast::Blast(mail::smtp *smtpArg)
+ : eofSent(false), mySmtp(smtpArg), lastChar('\n'),
+ bytesTotal(0), bytesDone(0)
+{
+}
+
+mail::smtp::Blast::~Blast()
+{
+ if (mySmtp)
+ mySmtp->myBlaster=NULL;
+}
+
+bool mail::smtp::Blast::fillWriteBuffer()
+{
+ if (!mySmtp)
+ {
+ if (!eofSent)
+ {
+ eofSent=true;
+ writebuffer += "\r\n.\r\n";
+ return true;
+ }
+ return false;
+ }
+
+ mySmtp->installHandler(mySmtp->responseHandler);
+ // Reset the timeout every time the write buffer gets filled
+
+ char buffer[BUFSIZ];
+
+ size_t i;
+
+ FILE *fp=mySmtp->sendQueue.front().message;
+
+ for (i=0; i<sizeof(buffer); i++)
+ {
+ int ch=getc(fp);
+
+ if (ch == EOF)
+ {
+ writebuffer += string(buffer, buffer+i);
+ if (lastChar != '\n')
+ writebuffer += "\r\n";
+ writebuffer += ".\r\n";
+
+ mail::callback *c=mySmtp->sendQueue.front().callback;
+
+ if (c)
+ c->reportProgress(bytesTotal, bytesTotal,
+ 0, 1);
+
+ mySmtp->myBlaster=NULL;
+ mySmtp=NULL;
+ eofSent=true;
+ return true;
+ }
+
+ ++bytesDone;
+
+ if (lastChar == '\n' && ch == '.')
+ {
+ ungetc(ch, fp); // Leading .s are doubled.
+ --bytesDone;
+ }
+
+ if (ch == '\n' && lastChar != '\r')
+ {
+ ungetc(ch, fp);
+ --bytesDone;
+ ch='\r';
+ }
+
+ buffer[i]=lastChar=ch;
+ }
+
+ mail::callback *c=mySmtp->sendQueue.front().callback;
+
+ if (c)
+ c->reportProgress(bytesDone, bytesTotal, 0, 1);
+
+ writebuffer += string(buffer, buffer+sizeof(buffer));
+ return true;
+}
+
+// Something arrived from the server.
+
+int mail::smtp::socketRead(const string &readbuffer)
+{
+ size_t n=readbuffer.find('\n');
+
+ if (n == std::string::npos)
+ return 0;
+
+ string l=readbuffer.substr(0, n++);
+
+ size_t cr;
+
+ while ((cr=l.find('\r')) != std::string::npos)
+ l=l.substr(0, cr) + l.substr(cr+1);
+
+ // Collect multiline SMTP replies. Keep only the last 30 lines
+ // received.
+
+ smtpResponse.push_back(l);
+
+ if (smtpResponse.size() > 30)
+ smtpResponse.erase(smtpResponse.begin());
+
+ string::iterator b=l.begin(), e=l.end();
+ int numCode=0;
+
+ while (b != e && isdigit((int)(unsigned char)*b))
+ numCode=numCode * 10 + (*b++ - '0');
+
+ if (b == l.begin()+3)
+ {
+ if (b == e || unicode_isspace((unsigned char)*b))
+ {
+ string s="";
+
+ list<string>::iterator b, e;
+
+ b=smtpResponse.begin();
+ e=smtpResponse.end();
+
+ while (b != e)
+ {
+ if (s.size() > 0) s += "\n";
+
+ s += *b++;
+ }
+
+ smtpResponse.clear();
+
+ if (responseHandler != 0)
+ (this->*responseHandler)(numCode, s);
+ else
+ fatalError=true;
+ // SMTP msg when no handler installed, something's
+ // gone wrong.
+
+ }
+ }
+
+ return n;
+}
+
+void mail::smtp::installHandler(void (mail::smtp::*handler)(int,
+ string))
+{
+ responseHandler=handler;
+ responseTimeout=0;
+
+ if (handler != 0)
+ {
+ time(&responseTimeout);
+ responseTimeout += SMTPTIMEOUT;
+ }
+}
+
+
+void mail::smtp::disconnect(const char *reason)
+{
+ while (!sendQueue.empty())
+ {
+ struct messageQueueInfo &info=sendQueue.front();
+
+ info.callback->fail(reason);
+ fclose(info.message);
+ sendQueue.pop();
+ }
+ pingTimeout=0;
+
+ if (getfd() >= 0)
+ {
+ string errmsg=socketDisconnect();
+
+ if (errmsg.size() == 0)
+ errmsg=reason;
+
+ if (orderlyShutdown)
+ success(errmsg);
+ else if (smtpLoginInfo.callbackPtr)
+ error(errmsg);
+ else
+ disconnect_callback.disconnected(errmsg.c_str());
+ }
+}
+
+void mail::smtp::resumed()
+{
+ if (responseHandler != 0)
+ {
+ time(&responseTimeout);
+ responseTimeout += SMTPTIMEOUT;
+ }
+}
+
+
+void mail::smtp::handler(vector<pollfd> &fds, int &ioTimeout)
+{
+ if (fatalError)
+ return;
+
+ int fd_save=getfd();
+
+ time_t now;
+
+ time(&now);
+
+ if (responseHandler != 0)
+ {
+ if (now >= responseTimeout)
+ {
+ fatalError=true;
+ ioTimeout=0;
+ (this->*responseHandler)(500, "500 Server timed out.");
+ return;
+ }
+
+ if (ioTimeout >= (responseTimeout - now) * 1000)
+ {
+ ioTimeout=(responseTimeout - now)*1000;
+ }
+ }
+
+ if (pingTimeout)
+ {
+ if (pingTimeout < now)
+ {
+ mail::smtpInfo dummy;
+
+ send(NULL, dummy, NULL, false);
+ }
+ else
+ {
+ if (ioTimeout >= (pingTimeout - now)*1000)
+ {
+ ioTimeout=(pingTimeout - now)*1000;
+ }
+ }
+ }
+
+
+ mail::fd::process(fds, ioTimeout);
+
+ if (getfd() < 0)
+ {
+ size_t i;
+
+ for (i=fds.size(); i; )
+ {
+ --i;
+
+ if (fds[i].fd == fd_save)
+ {
+ fds.erase(fds.begin()+i, fds.begin()+i+1);
+ break;
+ }
+ }
+ }
+}
+
+mail::smtp::smtp(string url, string passwd,
+ std::vector<std::string> &certificates,
+ mail::callback &callback,
+ mail::callback::disconnect &disconnectCallback,
+ loginCallback *loginCallbackFunc)
+ : mail::fd(disconnectCallback, certificates), orderlyShutdown(false),
+ ready2send(false), fatalError(true), pingTimeout(0)
+{
+ responseHandler=NULL;
+ smtpLoginInfo.callbackPtr= &callback;
+ smtpLoginInfo.loginCallbackFunc=loginCallbackFunc;
+
+ if (!loginUrlDecode(url, smtpLoginInfo))
+ {
+ smtpLoginInfo.callbackPtr->fail("Invalid SMTP URL.");
+ return;
+ }
+
+ if (smtpLoginInfo.method == "sendmail") // Local sendmail
+ {
+ smtpLoginInfo.use_ssl=false;
+ smtpLoginInfo.uid="";
+ smtpLoginInfo.pwd="";
+
+ string errmsg;
+
+ int pipefd[2];
+
+ if (libmail_streampipe(pipefd) < 0)
+ {
+ smtpLoginInfo.callbackPtr->fail(strerror(errno));
+ return;
+ }
+
+ pid_t pid1=fork();
+
+ if (pid1 < 0)
+ {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ smtpLoginInfo.callbackPtr->fail(strerror(errno));
+ return;
+ }
+
+ if (pid1 == 0)
+ {
+ close(0);
+ close(1);
+ errno=EIO;
+ if (dup(pipefd[1]) != 0 || dup(pipefd[1]) != 1)
+ {
+ perror("500 dup");
+ exit(1);
+ }
+ close(pipefd[0]);
+ close(pipefd[1]);
+
+ pid1=fork();
+ if (pid1 < 0)
+ {
+ printf("500 fork() failed.\r\n");
+ exit(1);
+ }
+
+ if (pid1)
+ exit(0);
+ execl(SENDMAIL, SENDMAIL, "-bs", (char *)NULL);
+
+ printf("500 %s: %s\n", SENDMAIL, strerror(errno));
+ exit(0);
+ }
+
+ close(pipefd[1]);
+ int waitstat;
+ pid_t pid2;
+
+ while ((pid2=wait(&waitstat)) != pid1)
+ {
+ if (kill(pid1, 0))
+ break;
+ }
+
+
+ try {
+ errmsg=socketAttach(pipefd[0]);
+ } catch (...) {
+ close(pipefd[0]);
+ LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
+ }
+ }
+ else
+ {
+ smtpLoginInfo.use_ssl= smtpLoginInfo.method == "smtps";
+
+ if (passwd.size() > 0)
+ smtpLoginInfo.pwd=passwd;
+
+ string errmsg=socketConnect(smtpLoginInfo, "smtp", "smtps");
+
+ if (errmsg.size() > 0)
+ {
+ smtpLoginInfo.callbackPtr->fail(errmsg);
+ return;
+ }
+ }
+
+ fatalError=false;
+ hmac_method_index=0;
+ installHandler(&mail::smtp::greetingResponse);
+}
+
+// Login failed
+
+void mail::smtp::error(string errMsg)
+{
+ mail::callback *c=smtpLoginInfo.callbackPtr;
+
+ smtpLoginInfo.callbackPtr=NULL;
+
+ if (c)
+ c->fail(errMsg);
+
+}
+
+// Login succeeded
+
+void mail::smtp::success(string errMsg)
+{
+ mail::callback *c=smtpLoginInfo.callbackPtr;
+
+ smtpLoginInfo.callbackPtr=NULL;
+
+ if (c)
+ c->success(errMsg);
+}
+
+// Send EHLO/HELO
+
+void mail::smtp::howdy(const char *hi)
+{
+ char buffer[512];
+
+ buffer[sizeof(buffer)-1]=0;
+
+ if (gethostname(buffer, sizeof(buffer)-1) < 0)
+ strcpy(buffer, "localhost");
+
+ socketWrite(string(hi) + buffer + "\r\n");
+}
+
+void mail::smtp::greetingResponse(int n, string s)
+{
+ switch (n / 100) {
+ case 1:
+ case 2:
+ case 3:
+ break;
+ default:
+ error(s);
+ return;
+ }
+
+ installHandler(&mail::smtp::ehloResponse);
+ howdy("EHLO ");
+}
+
+void mail::smtp::ehloResponse(int n, string s)
+{
+ switch (n / 100) {
+ case 1:
+ case 2:
+ case 3:
+ break;
+ default: // Maybe HELO will work
+ installHandler(&mail::smtp::heloResponse);
+ howdy("HELO ");
+ return;
+ }
+
+ // Parse EHLO capability list
+
+ bool firstLine=true;
+
+ while (s.size() > 0)
+ {
+ string line;
+
+ size_t p=s.find('\n');
+
+ if (p == std::string::npos)
+ {
+ line=s;
+ s="";
+ }
+ else
+ {
+ line=s.substr(0, p);
+ s=s.substr(p+1);
+ }
+
+ if (firstLine)
+ {
+ firstLine=false;
+ continue;
+ }
+
+ while ((p=s.find('\r')) != std::string::npos)
+ s=s.substr(0, p) + s.substr(p+1);
+
+ for (p=0; p<line.size(); p++)
+ if (!DIGIT((int)(unsigned char)line[p]))
+ break;
+
+ if (p < line.size())
+ p++;
+
+ // Put capability to uppercase
+
+ line=mail::iconvert::convert_tocase(line.substr(p),
+ "iso-8859-1",
+ unicode_uc);
+
+ for (p=0; p<line.size(); p++)
+ if (unicode_isspace((unsigned char)line[p]) ||
+ line[p] == '=')
+ break;
+
+ string capa=line.substr(0, p);
+
+ // Save SASL authentication methods as capabilities
+ // AUTH=method
+
+ if (capa == "AUTH" || capa == "XSECURITY")
+ {
+ if (p < line.size())
+ p++;
+
+ line=line.substr(p);
+
+ while (line.size() > 0)
+ {
+ for (p=0; p<line.size(); p++)
+ if (unicode_isspace((unsigned char)
+ line[p]) ||
+ line[p] == ',')
+ break;
+
+ if (p > 0)
+ {
+ capabilities.insert(capa + "=" +
+ line.substr(0, p));
+ }
+ if (p < line.size())
+ p++;
+ line=line.substr(p);
+ }
+ }
+ else
+ capabilities.insert(capa);
+ }
+ starttls();
+}
+
+void mail::smtp::heloResponse(int n, string s)
+{
+ switch (n / 100) {
+ case 1:
+ case 2:
+ case 3:
+ starttls();
+ return;
+ }
+
+ error(s);
+}
+
+void mail::smtp::starttls()
+{
+#if HAVE_LIBCOURIERTLS
+
+ if (smtpLoginInfo.use_ssl // Already SSL-encrypted
+ || smtpLoginInfo.options.count("notls") > 0
+ || !hasCapability("STARTTLS")
+ || socketEncrypted())
+ {
+ begin_auth();
+ return;
+ }
+
+ installHandler(&mail::smtp::starttlsResponse);
+
+ socketWrite("STARTTLS\r\n");
+#else
+ begin_auth();
+ return;
+#endif
+}
+
+void mail::smtp::starttlsResponse(int n, string s)
+{
+ switch (n / 100) {
+ case 1:
+ case 2:
+ if (!socketBeginEncryption(smtpLoginInfo))
+ {
+ smtpLoginInfo.callbackPtr=NULL;
+ return;
+ }
+ capabilities.clear();
+ greetingResponse(n, s);
+ return;
+ }
+
+ error(s);
+}
+
+void mail::smtp::begin_auth()
+{
+ if (!hasCapability("AUTH=EXTERNAL"))
+ {
+ begin_auth_nonexternal();
+ return;
+ }
+ installHandler(&mail::smtp::auth_external_response);
+ socketWrite("AUTH EXTERNAL =\r\n");
+}
+
+void mail::smtp::auth_external_response(int n, string s)
+{
+ switch (n / 100) {
+ case 1:
+ case 2:
+ case 3:
+ authenticated();
+ return;
+ }
+ begin_auth_nonexternal();
+}
+
+void mail::smtp::begin_auth_nonexternal()
+{
+ int i;
+
+ if (smtpLoginInfo.uid.size() == 0)
+ {
+ authenticate_hmac();
+ return;
+ }
+
+ for (i=0; mail::imaphmac::hmac_methods[i]; ++i)
+ {
+ if (hasCapability(string("AUTH=CRAM-") +
+ mail::imaphmac
+ ::hmac_methods[hmac_method_index]->getName()
+ ))
+ break;
+ }
+
+ if (mail::imaphmac::hmac_methods[i] ||
+ hasCapability("AUTH=LOGIN"))
+ {
+ if (smtpLoginInfo.pwd.size() == 0)
+ {
+ currentCallback=smtpLoginInfo.loginCallbackFunc;
+
+ if (currentCallback)
+ {
+ currentCallback->target=this;
+ currentCallback->getPassword();
+ return;
+ }
+ }
+ }
+ authenticate_hmac();
+}
+
+void mail::smtp::loginInfoCallback(std::string str)
+{
+ currentCallback=NULL;
+ smtpLoginInfo.pwd=str;
+ authenticate_hmac();
+}
+
+void mail::smtp::loginInfoCallbackCancel()
+{
+ currentCallback=NULL;
+ error("Login cancelled");
+}
+
+void mail::smtp::authenticate_hmac()
+{
+ for (;;)
+ {
+ if (mail::imaphmac::hmac_methods[hmac_method_index] == NULL ||
+ smtpLoginInfo.uid.size() == 0)
+ {
+ authenticate_login(); // Exhausted SASL, forget it.
+ return;
+ }
+
+ if (hasCapability(string("AUTH=CRAM-") +
+ mail::imaphmac
+ ::hmac_methods[hmac_method_index]->getName()
+ ))
+ break;
+ hmac_method_index++;
+ }
+
+ installHandler(&mail::smtp::hmacResponse);
+
+ socketWrite(string("AUTH CRAM-") +
+ mail::imaphmac::hmac_methods[hmac_method_index]->getName()
+ + "\r\n");
+}
+
+void mail::smtp::hmacResponse(int n, string s)
+{
+ switch (n / 100) {
+ case 1:
+ case 2:
+ authenticated();
+ return;
+ }
+
+ if ((n / 100) != 3 || s.size() < 4)
+ {
+ ++hmac_method_index;
+ authenticate_hmac();
+ return;
+ }
+
+ s=mail::decodebase64str(s.substr(4));
+ s=(*mail::imaphmac::hmac_methods[hmac_method_index])(smtpLoginInfo
+ .pwd, s);
+
+ string sHex;
+
+ {
+ ostringstream hexEncode;
+
+ hexEncode << hex;
+
+ string::iterator b=s.begin();
+ string::iterator e=s.end();
+
+ while (b != e)
+ hexEncode << setw(2) << setfill('0')
+ << (int)(unsigned char)*b++;
+
+ sHex=hexEncode.str();
+ }
+
+ s=mail::encodebase64str(smtpLoginInfo.uid + " " + sHex);
+ socketWrite(s + "\r\n");
+}
+
+void mail::smtp::authenticate_login()
+{
+ if (!hasCapability("AUTH=LOGIN") || smtpLoginInfo.uid.size() == 0 ||
+ smtpLoginInfo.options.count("cram"))
+ {
+ authenticated();
+ return;
+ }
+
+ installHandler(&mail::smtp::loginUseridResponse);
+
+ socketWrite("AUTH LOGIN\r\n");
+}
+
+void mail::smtp::loginUseridResponse(int n, string msg)
+{
+ if ((n / 100) != 3)
+ {
+ error(msg);
+ return;
+ }
+
+ installHandler(&mail::smtp::loginPasswdResponse);
+ socketWrite(string(mail::encodebase64str(smtpLoginInfo.uid))
+ + "\r\n");
+}
+
+void mail::smtp::loginPasswdResponse(int n, string msg)
+{
+ if ((n / 100) != 3)
+ {
+ error(msg);
+ return;
+ }
+
+ installHandler(&mail::smtp::loginResponse);
+ socketWrite(string(mail::encodebase64str(smtpLoginInfo.pwd))
+ + "\r\n");
+}
+
+void mail::smtp::loginResponse(int n, string msg)
+{
+ if ((n / 100) != 2)
+ {
+ error(msg);
+ return;
+ }
+
+ authenticated();
+}
+
+void mail::smtp::authenticated()
+{
+ ready2send=true;
+ if (!sendQueue.empty())
+ startNextMessage(); // Already something's queued up.
+ else
+ pingTimeout=time(NULL) + INACTIVITY_TIMEOUT;
+ success("Connected to " + smtpLoginInfo.server);
+}
+
+mail::smtp::~smtp()
+{
+ disconnect("Connection forcibly aborted.");
+}
+
+void mail::smtp::logout(mail::callback &callback)
+{
+ if (fatalError)
+ {
+ callback.success("Server timed out.");
+ return;
+ }
+
+ smtpLoginInfo.callbackPtr= &callback;
+ orderlyShutdown=true;
+ installHandler(&mail::smtp::quitResponse);
+
+ socketWrite("QUIT\r\n");
+}
+
+void mail::smtp::quitResponse(int n, string s)
+{
+ if (!socketEndEncryption())
+ {
+ socketDisconnect();
+ success(s);
+ }
+}
+
+void mail::smtp::checkNewMail(mail::callback &callback) // STUB
+{
+ callback.success("OK");
+}
+
+bool mail::smtp::hasCapability(string capability)
+{
+ if (capability == LIBMAIL_SINGLEFOLDER)
+ return false;
+
+ return capabilities.count(capability) > 0;
+}
+
+string mail::smtp::getCapability(string name)
+{
+ upper(name);
+
+ if (name == LIBMAIL_SERVERTYPE)
+ {
+ return "smtp";
+ }
+
+ if (name == LIBMAIL_SERVERDESCR)
+ {
+ return "ESMTP server";
+ }
+
+ if (name == LIBMAIL_SINGLEFOLDER)
+ return "";
+
+ return capabilities.count(name) > 0 ? "1":"";
+}
+
+
+// Not implemented
+
+mail::folder *mail::smtp::folderFromString(string)
+{
+ errno=EINVAL;
+ return NULL;
+}
+
+// Not implemented
+
+void mail::smtp::readTopLevelFolders(mail::callback::folderList &callback1,
+ mail::callback &callback2)
+{
+ vector<const mail::folder *> dummy;
+
+ callback1.success(dummy);
+ callback2.success("OK");
+}
+
+// Not implemented
+
+void mail::smtp::findFolder(string folder,
+ class mail::callback::folderList &callback1,
+ class mail::callback &callback2)
+{
+ callback2.fail("Folder not found.");
+}
+
+// This is why we're here:
+
+mail::folder *mail::smtp::getSendFolder(const mail::smtpInfo &info,
+ const mail::folder *folderArg,
+ string &errmsg)
+{
+ if (folderArg)
+ {
+ return mail::account::getSendFolder(info, folderArg, errmsg);
+ }
+
+ if (info.recipients.size() == 0)
+ {
+ errno=ENOENT;
+ errmsg="Empty recipient list.";
+ return NULL;
+ }
+
+ mail::smtpFolder *folder=new mail::smtpFolder(this, info);
+
+ if (!folder)
+ {
+ errmsg=strerror(errno);
+ return NULL;
+ }
+ return folder;
+}
+
+
+void mail::smtp::readMessageAttributes(const vector<size_t> &messages,
+ MessageAttributes attributes,
+ mail::callback::message &callback)
+{
+ callback.fail("Invalid message number.");
+}
+
+
+void mail::smtp::readMessageContent(const vector<size_t> &messages,
+ bool peek,
+ enum mail::readMode readType,
+ mail::callback::message &callback)
+{
+ callback.fail("Invalid message number.");
+}
+
+void mail::smtp::readMessageContent(size_t messageNum,
+ bool peek,
+ const mail::mimestruct &msginfo,
+ enum mail::readMode readType,
+ mail::callback::message &callback)
+{
+ callback.fail("Invalid message number.");
+}
+
+void mail::smtp::readMessageContentDecoded(size_t messageNum,
+ bool peek,
+ const mail::mimestruct &msginfo,
+ mail::callback::message &callback)
+{
+ callback.fail("Invalid message number.");
+}
+
+size_t mail::smtp::getFolderIndexSize()
+{
+ return 0;
+}
+
+mail::messageInfo mail::smtp::getFolderIndexInfo(size_t messageNum)
+{
+ return mail::messageInfo();
+}
+
+void mail::smtp::saveFolderIndexInfo(size_t messageNumber,
+ const mail::messageInfo &info,
+ mail::callback &callback)
+{
+ callback.fail("Invalid message number.");
+}
+
+void mail::smtp::updateFolderIndexFlags(const vector<size_t> &messages,
+ bool doFlip,
+ bool enableDisable,
+ const mail::messageInfo &flags,
+ mail::callback &callback)
+{
+ callback.fail("Invalid message number.");
+}
+
+void mail::smtp::updateFolderIndexInfo(mail::callback &callback)
+{
+ callback.success("OK");
+}
+
+void mail::smtp::removeMessages(const std::vector<size_t> &messages,
+ mail::callback &cb)
+{
+ updateFolderIndexInfo(cb);
+}
+
+void mail::smtp::copyMessagesTo(const vector<size_t> &messages,
+ mail::folder *copyTo,
+ mail::callback &callback)
+{
+ callback.fail("Invalid message number.");
+}
+
+void mail::smtp::searchMessages(const mail::searchParams &searchInfo,
+ mail::searchCallback &callback)
+{
+ vector<size_t> empty;
+
+ callback.success(empty);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Another message is ready to go out. Called by mail::smtpFolder::go,
+// and one other place.
+
+void mail::smtp::send(FILE *tmpfile, mail::smtpInfo &info,
+ mail::callback *callback, bool flag8bit)
+{
+ pingTimeout=0;
+
+ struct messageQueueInfo msgInfo;
+
+ msgInfo.message=tmpfile;
+ msgInfo.messageInfo=info;
+ msgInfo.callback=callback;
+ msgInfo.flag8bit=flag8bit;
+
+ bool wasEmpty=sendQueue.empty();
+
+ sendQueue.push(msgInfo);
+
+ if (wasEmpty && ready2send)
+ startNextMessage();
+}
+
+void mail::smtp::startNextMessage()
+{
+ if (fatalError)
+ {
+ messageProcessed(500, "500 A fatal error occurred while connected to remote server.");
+ return;
+ }
+
+ socketWrite("RSET\r\n");
+ installHandler(&mail::smtp::rsetResponse);
+}
+
+void mail::smtp::rsetResponse(int result, string message)
+{
+ struct messageQueueInfo &info=sendQueue.front();
+
+ if (info.message == NULL) // Just a NO-OP ping.
+ {
+ sendQueue.pop();
+
+ if (!sendQueue.empty())
+ startNextMessage();
+ else
+ pingTimeout=time(NULL) + INACTIVITY_TIMEOUT;
+ return;
+ }
+
+ switch (result / 100) {
+ case 1:
+ case 2:
+ case 3:
+ break;
+ default:
+ messageProcessed(result, message);
+ return;
+ }
+
+ if (!validString(info.messageInfo.sender))
+ return;
+
+ {
+ vector<string>::iterator b=info.messageInfo.recipients.begin(),
+ e=info.messageInfo.recipients.end();
+
+ while (b != e)
+ if (!validString(*b++))
+ return;
+ }
+
+ {
+ map<string, string>::iterator
+ b=info.messageInfo.options.begin(),
+ e=info.messageInfo.options.end();
+
+ while (b != e)
+ if (!validString((*b++).second))
+ return;
+ }
+
+ string mailfrom="MAIL FROM:<" + info.messageInfo.sender + ">";
+
+ if (hasCapability("8BITMIME"))
+ mailfrom += info.flag8bit ? " BODY=8BITMIME":" BODY=7BIT";
+
+ if (hasCapability("SIZE"))
+ {
+ long pos;
+
+ if (fseek(info.message, 0L, SEEK_END) < 0 ||
+ (pos=ftell(info.message)) < 0)
+ {
+ messageProcessed(500, strerror(errno));
+ return;
+ }
+
+ pos= pos < 1024 * 1024 ? pos * 70 / 68: pos/68 * 70;
+
+ string buffer;
+
+ {
+ ostringstream o;
+
+ o << pos;
+ buffer=o.str();
+ }
+
+ mailfrom += " SIZE=";
+ mailfrom += buffer;
+ }
+
+ if (info.messageInfo.options.count("DSN") > 0 ||
+ info.messageInfo.options.count("RET") > 0)
+ {
+ if (!hasCapability("DSN"))
+ {
+ messageProcessed(500, "500 This mail server does not support delivery status notifications.");
+ return;
+ }
+ }
+
+ if (hasCapability("DSN"))
+ {
+ if (info.messageInfo.options.count("RET") > 0)
+ mailfrom += " RET=" + info.messageInfo.options
+ .find("RET")->second;
+ }
+
+ if (hasCapability("XVERP=COURIER"))
+ {
+ if (info.messageInfo.options.count("VERP") > 0)
+ mailfrom += " VERP";
+ }
+
+ if (info.messageInfo.options.count("SECURITY") > 0)
+ {
+ string sec=info.messageInfo.options.find("SECURITY")->second;
+
+ if (info.messageInfo.options.count("XSECURITY=" + sec) == 0)
+ {
+ messageProcessed(500, "500 requested security not available.");
+ return;
+ }
+
+ mailfrom += " SECURITY=" + sec;
+ }
+
+ pipelineCmd=mailfrom;
+
+ mailfrom += "\r\n";
+
+ pipelineCount=0;
+ installHandler(&mail::smtp::mailfromResponse);
+ socketWrite(mailfrom);
+}
+
+// RCPT TO handler in pipeline mode.
+
+void mail::smtp::pipelinedResponse(int result, string message)
+{
+ switch (result / 100) {
+ case 1:
+ case 2:
+ case 3:
+ break;
+ default:
+ if (pipelineError.size() == 0)
+ pipelineError=sendQueue.front().messageInfo
+ .recipients[rcptCount] + ": " + message;
+ break;
+ }
+
+ ++rcptCount;
+ if (--pipelineCount || fatalError)
+ return; // More on the way
+
+ if (pipelineError.size() > 0)
+ messageProcessed(500, pipelineError);
+ else
+ {
+ installHandler(&mail::smtp::dataResponse);
+ socketWrite("DATA\r\n");
+ }
+}
+
+// MAIL FROM response. RCPT TO response in non-pipelined mode.
+
+void mail::smtp::mailfromResponse(int result, string message)
+{
+ switch (result / 100) {
+ case 1:
+ case 2:
+ case 3:
+ break;
+ default:
+ messageProcessed(result, pipelineCmd + ": " + message);
+ return;
+ }
+
+ struct messageQueueInfo &info=sendQueue.front();
+
+ rcptCount=0;
+ if (pipelineCount == 0 && hasCapability("PIPELINING") &&
+ smtpLoginInfo.options.count("nopipelining") == 0)
+ {
+ string rcptto="";
+
+ vector<string>::iterator b=info.messageInfo.recipients.begin(),
+ e=info.messageInfo.recipients.end();
+
+ pipelineError="";
+
+ while (b != e)
+ {
+ rcptto += "RCPT TO:<" + *b++ + ">";
+
+ if (info.messageInfo.options.count("DSN") > 0)
+ rcptto += " NOTIFY="
+ + info.messageInfo.options.find("DSN")
+ ->second;
+ rcptto += "\r\n";
+ ++pipelineCount;
+ }
+
+ installHandler(&mail::smtp::pipelinedResponse);
+ socketWrite(rcptto);
+ return;
+ }
+
+ if (pipelineCount < info.messageInfo.recipients.size())
+ {
+ pipelineCmd=info.messageInfo.recipients[pipelineCount++];
+ string rcptto= "RCPT TO:<" + pipelineCmd + ">";
+
+ if (info.messageInfo.options.count("DSN") > 0)
+ rcptto += " NOTIFY="
+ + info.messageInfo.options.find("DSN")
+ ->second;
+
+ rcptto += "\r\n";
+ socketWrite(rcptto);
+ return;
+ }
+
+ installHandler(&mail::smtp::dataResponse);
+ socketWrite("DATA\r\n");
+}
+
+// 1st DATA response
+
+void mail::smtp::dataResponse(int result, string message)
+{
+ switch (result / 100) {
+ case 1:
+ case 2:
+ case 3:
+ break;
+ default:
+ messageProcessed(result, message);
+ break;
+ }
+
+ long fpos;
+
+ if ( fseek(sendQueue.front().message, 0L, SEEK_END) < 0 ||
+ (fpos=ftell(sendQueue.front().message)) < 0 ||
+ fseek(sendQueue.front().message, 0L, SEEK_SET) < 0 ||
+ (myBlaster=new Blast(this)) == NULL)
+
+ {
+ fatalError=true;
+ messageProcessed(500, strerror(errno));
+ return;
+ }
+
+ myBlaster->bytesTotal=fpos;
+ installHandler(&mail::smtp::data2Response);
+ socketWrite(myBlaster);
+}
+
+void mail::smtp::data2Response(int result, string message)
+{
+ if (myBlaster)
+ myBlaster->mySmtp=NULL;
+ installHandler(NULL); // Nothing more.
+ messageProcessed(result, message);
+}
+
+bool mail::smtp::validString(string s)
+{
+ string::iterator b=s.begin(), e=s.end();
+
+ while (b != e)
+ {
+ if ((int)(unsigned char)*b <= ' ' || *b == '>' || *b == '<')
+ {
+ messageProcessed(500, "500 Invalid character in message sender, recipient, or options.");
+ return false;
+ }
+ b++;
+ }
+ return true;
+}
+
+void mail::smtp::messageProcessed(int result, string message)
+{
+ struct messageQueueInfo &info=sendQueue.front();
+
+ mail::callback *callback=info.callback;
+ fclose(info.message);
+
+ try {
+ sendQueue.pop();
+ if (!sendQueue.empty())
+ startNextMessage();
+ else
+ pingTimeout=time(NULL) + INACTIVITY_TIMEOUT;
+
+ switch (result / 100) {
+ case 1:
+ case 2:
+ case 3:
+ callback->success(message);
+ break;
+ default:
+ callback->fail(message);
+ break;
+ }
+
+ } catch (...) {
+ callback->fail("An exception has occurred while processing message.");
+ }
+}
+
+string mail::smtp::translatePath(string path)
+{
+ return "INBOX"; // NOOP
+}