diff options
Diffstat (limited to 'libmail/pop3.C')
| -rw-r--r-- | libmail/pop3.C | 2845 |
1 files changed, 2845 insertions, 0 deletions
diff --git a/libmail/pop3.C b/libmail/pop3.C new file mode 100644 index 0000000..6866821 --- /dev/null +++ b/libmail/pop3.C @@ -0,0 +1,2845 @@ +/* +** Copyright 2002-2008, Double Precision Inc. +** +** See COPYING for distribution information. +*/ +#include "libmail_config.h" +#include "driver.H" +#include "mail.H" +#include "misc.H" +#include "addmessage.H" +#include "pop3.H" +#include "copymessage.H" +#include "search.H" +#include "imaphmac.H" +#include "base64.H" +#include "objectmonitor.H" +#include "libcouriertls.h" +#include "expungelist.H" +#include <sstream> +#include <iomanip> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#include <algorithm> +#include <vector> +#include <signal.h> +#include <fcntl.h> +#include <set> +#include <cstring> + +#include "rfc2045/rfc2045.h" + +using namespace std; + +///////////////////////////////////////////////////////////////////////////// + +LIBMAIL_START + +static bool open_pop3(mail::account *&accountRet, + mail::account::openInfo &oi, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) +{ + mail::loginInfo pop3LoginInfo; + + if (!mail::loginUrlDecode(oi.url, pop3LoginInfo)) + return false; + + if (pop3LoginInfo.method != "pop3" && + pop3LoginInfo.method != "pop3s") + return false; + + accountRet=new mail::pop3(oi.url, oi.pwd, + oi.certificates, + oi.loginCallbackObj, + callback, + disconnectCallback); + return true; +} + +static bool pop3_remote(string url, bool &flag) +{ + mail::loginInfo pop3LoginInfo; + + if (!mail::loginUrlDecode(url, pop3LoginInfo)) + return false; + + if (pop3LoginInfo.method != "pop3" && + pop3LoginInfo.method != "pop3s") + return false; + + flag=true; + return true; +} + +driver pop3_driver= { &open_pop3, &pop3_remote }; + +LIBMAIL_END + +///////////////////////////////////////////////////////////////////////////// +// +// Most tasks will subclass from LoggedInTask which makes sure we're +// logged in to the POP3 server if we're not currently logged in. +// +// Exported methods: +// +// LoggedInTask(mail::pop3 &server) // Constructor +// +// mail::pop3 *myserver; // Inherited from Task +// +// void loggedIn(); // Logged in to the server, begin doing what you need to do +// void loginFailed(string errmsg); // Log in failed, about to be destroyed +// +// int getTimeout(); // Inherited from Task +// void disconnected(const char *reason); // Inherited from Task +// void serverResponse(const char *message); // Inherited from task +// + +class mail::pop3::LoggedInTask : public mail::pop3::Task, + public mail::callback { + + // Inherited from mail::callback + + void success(string); + void fail(string); + + void installedTask(); + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + LoggedInTask(mail::callback *callback, mail::pop3 &myserverArg); + ~LoggedInTask(); + + virtual void loggedIn()=0; + virtual void loginFailed(string error)=0; +}; + +mail::pop3::Task::Task(mail::callback *callbackArg, mail::pop3 &myserverArg) + : callbackPtr(callbackArg), myserver(&myserverArg) +{ + time(&defaultTimeout); + defaultTimeout += myserverArg.timeoutSetting; +} + +mail::pop3::Task::~Task() +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + if (c) + c->fail("POP3 task terminated unexpectedly."); +} + +void mail::pop3::Task::installedTask() +{ + resetTimeout(); +} + +void mail::pop3::Task::resetTimeout() +{ + time(&defaultTimeout); + defaultTimeout += myserver->timeoutSetting; +} + +int mail::pop3::Task::getTimeout() +{ + time_t now; + + time(&now); + + if (now < defaultTimeout) + return defaultTimeout - now; + + string errmsg=myserver->socketDisconnect(); + + if (errmsg.size() == 0) + errmsg="Server timed out."; + + disconnected(errmsg.c_str()); + return 0; +} + +void mail::pop3::Task::disconnected(const char *reason) +{ + if (myserver->tasks.empty() || + (*myserver->tasks.begin()) != this) + kill(getpid(), SIGABRT); + + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + try { + myserver->tasks.erase(myserver->tasks.begin()); + + if (!myserver->tasks.empty()) + (*myserver->tasks.begin())->disconnected(reason); + delete this; + + if (c) + c->fail(reason); + } catch (...) { + if (c) + c->fail(reason); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::pop3::Task::done() +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + if (myserver->tasks.empty() || (*myserver->tasks.begin()) != this) + kill(getpid(), SIGABRT); + + myserver->tasks.erase(myserver->tasks.begin()); + + mail::pop3 *s=myserver; + + bool flag=isLogoutTask(); + + delete this; + + if (!s->tasks.empty()) + (*s->tasks.begin())->installedTask(); + else if (!flag) + time(&s->lastTaskCompleted); + + if (c) + c->fail("POP3 task aborted."); +} + +bool mail::pop3::Task::isLogoutTask() +{ + return false; +} + +bool mail::pop3::Task::willReconnect() +{ + return false; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Log into the server, and create mail::pop3::uidlmap. + +class mail::pop3::LoginTask : public mail::pop3::Task, + public mail::loginInfo::callbackTarget { + + void fail(const char *reason); + + void (LoginTask::*currentHandler)(const char *status); + + void serverResponse(const char *message); + + void installedTask(); + + void greetingHandler(const char *message); + void capaHandler(const char *message); + void stlsHandler(const char *message); + void stlsCapaHandler(const char *message); + + int hmac_index; // Next HMAC method to try. + void hmacHandler(const char *message); + void hmacChallengeHandler(const char *message); + void userHandler(const char *message); + void passHandler(const char *message); + void listHandler(const char *message); + void uidlHandler(const char *message); + + void addCapability(const char *message); + void addStlsCapability(const char *message); + void processExternalLogin(const char *message); + + void processedCapabilities(); + void loggedIn(const char *message); + + void (mail::pop3::LoginTask::*login_callback_handler)(std::string); + + void loginInfoCallback(std::string); + void loginInfoCallbackCancel(); + + void loginCallbackUid(std::string); + void loginCallbackPwd(std::string); + + void stlsCapaDone(); + void nonExternalLogin(); +public: + LoginTask(mail::pop3 &server, mail::callback *callbackArg); + ~LoginTask(); +}; + +mail::pop3::LoginTask::LoginTask(mail::pop3 &server, + mail::callback *callbackArg) + : Task(callbackArg, server) +{ +} + +mail::pop3::LoginTask::~LoginTask() +{ +} + +void mail::pop3::LoginTask::fail(const char *reason) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + try { + string errmsg="ERROR: "; + + if (strncmp(reason, "-ERR", 4) == 0) + { + reason += 4; + + while (*reason == ' ') + reason++; + } + + errmsg += reason; + + done(); + + if (c) + c->fail(errmsg.c_str()); + } catch (...) { + if (c) + c->fail("ERROR: An exception occured in mail::pop3"); + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + +void mail::pop3::LoginTask::serverResponse(const char *message) +{ + if (currentHandler) + (this->*currentHandler)(message); +} + +void mail::pop3::LoginTask::installedTask() +{ + Task::installedTask(); + + if (myserver->getfd() >= 0) + { + loggedIn("+OK Already logged in."); // Already + return; + } + + currentHandler= &LoginTask::greetingHandler; + + myserver->pop3LoginInfo = myserver->savedLoginInfo; + + string errmsg=myserver-> + socketConnect(myserver->pop3LoginInfo, "pop3", "pop3s"); + + if (errmsg.size() > 0) + { + fail(errmsg.c_str()); + return; + } +} + +// First message from the server. + +void mail::pop3::LoginTask::greetingHandler(const char *message) +{ + if (*message != '+') + { + myserver->socketDisconnect(); + fail(message); + return; + } + + myserver->socketWrite("CAPA\r\n"); + currentHandler= &LoginTask::capaHandler; +} + +// CAPA reply. + +void mail::pop3::LoginTask::capaHandler(const char *message) +{ + myserver->capabilities.clear(); + + if (*message == '-') + { + processedCapabilities(); // No capabilities + return; + } + + if (*message != '+') + { + fail(message); + return; + } + currentHandler=&LoginTask::addCapability; +} + +// Capability list. + +void mail::pop3::LoginTask::addCapability(const char *message) +{ + if (strcmp(message, ".") == 0) + { + processedCapabilities(); + return; + } + + string c=message, v=""; + size_t p=c.find(' '); + + if (p != std::string::npos) + { + v=c.substr(p+1); + c=c.substr(0, p); + } + + // Add AUTH=method capability for each SASL listed method. + + if (c == "SASL") + { + while ((p=v.find(' ')) != std::string::npos) + { + c="AUTH=" + v.substr(0, p); + v=v.substr(p+1); + myserver->capabilities.insert(make_pair(c, + string("1"))); + } + c="AUTH=" + v; + v="1"; + } + + if (v.size() == 0) + v="1"; + + myserver->capabilities.insert(make_pair(c, v)); +} + +void mail::pop3::LoginTask::processedCapabilities() +{ +#if HAVE_LIBCOURIERTLS + + if (myserver->pop3LoginInfo.use_ssl + || myserver->pop3LoginInfo.options.count("notls") > 0 + || !myserver->hasCapability("STLS")) + { + stlsCapaDone(); + return; + } + + currentHandler=&LoginTask::stlsHandler; + myserver->socketWrite("STLS\r\n"); +#else + stlsCapaDone(); +#endif +} + +void mail::pop3::LoginTask::stlsHandler(const char *message) +{ + if (*message != '+') + { + fail(message); + return; + } + +#if HAVE_LIBCOURIERTLS + + myserver->pop3LoginInfo.callbackPtr=callbackPtr; + + callbackPtr=NULL; + if (!myserver->socketBeginEncryption(myserver->pop3LoginInfo)) + return; // Can't set callbackPtr to NULL now, because + // this object could be destroyed. + + // If beginEcnryption() succeeded, restore the callback ptr. + callbackPtr=myserver->pop3LoginInfo.callbackPtr; +#endif + currentHandler=&LoginTask::stlsCapaHandler; + myserver->socketWrite("CAPA\r\n"); +} + +void mail::pop3::LoginTask::stlsCapaHandler(const char *message) +{ + myserver->capabilities.clear(); + + if (*message == '-') + { + processedCapabilities(); // No capabilities + return; + } + + if (*message != '+') + { + fail(message); + return; + } + currentHandler=&LoginTask::addStlsCapability; +} + +void mail::pop3::LoginTask::addStlsCapability(const char *message) +{ + if (strcmp(message, ".") == 0) + { + stlsCapaDone(); + return; + } + addCapability(message); +} + +void mail::pop3::LoginTask::stlsCapaDone() +{ + if (!myserver->hasCapability("AUTH=EXTERNAL")) + { + nonExternalLogin(); + return; + } + + currentHandler=&LoginTask::processExternalLogin; + myserver->socketWrite("AUTH EXTERNAL =\r\n"); +} + +void mail::pop3::LoginTask::processExternalLogin(const char *message) +{ + if (*message != '+') + { + nonExternalLogin(); + return; + } + + loggedIn(message); +} + +void mail::pop3::LoginTask::nonExternalLogin() +{ + if (!myserver->pop3LoginInfo.loginCallbackFunc) + { + loginCallbackPwd(myserver->pop3LoginInfo.pwd); + return; + } + + currentHandler=NULL; + if (myserver->pop3LoginInfo.uid.size() > 0) + { + loginCallbackUid(myserver->pop3LoginInfo.uid); + return; + } + + login_callback_handler= &mail::pop3::LoginTask::loginCallbackUid; + currentCallback=myserver->pop3LoginInfo.loginCallbackFunc; + currentCallback->target=this; + currentCallback->getUserid(); +} + +void mail::pop3::LoginTask::loginInfoCallback(std::string cbvalue) +{ + currentCallback=NULL; + (this->*login_callback_handler)(cbvalue); +} + + +void mail::pop3::LoginTask::loginInfoCallbackCancel() +{ + currentCallback=NULL; + fail("Login cancelled."); +} + +void mail::pop3::LoginTask::loginCallbackUid(std::string uid) +{ + myserver->savedLoginInfo.uid= + myserver->pop3LoginInfo.uid=uid; + + if (myserver->pop3LoginInfo.pwd.size() > 0) + { + loginCallbackPwd(myserver->pop3LoginInfo.pwd); + return; + } + + login_callback_handler= &mail::pop3::LoginTask::loginCallbackPwd; + currentCallback=myserver->pop3LoginInfo.loginCallbackFunc; + currentCallback->target=this; + currentCallback->getPassword(); +} + +void mail::pop3::LoginTask::loginCallbackPwd(std::string pwd) +{ + myserver->savedLoginInfo.pwd=myserver->pop3LoginInfo.pwd=pwd; + + hmac_index=0; + hmacHandler("-ERR Login failed"); // Not really +} + +void mail::pop3::LoginTask::hmacHandler(const char *message) +{ + if (*message == '+') + { + loggedIn(message); + return; + } + + for (;;) + { + if (mail::imaphmac::hmac_methods[hmac_index] == NULL || + myserver->pop3LoginInfo.uid.size() == 0) + { + if (myserver->pop3LoginInfo.options.count("cram")) + fail(message); + + if (myserver->pop3LoginInfo.uid.size() == 0) + { + loggedIn("+OK No userid, assuming preauthenticated login."); + return; + } + + currentHandler=&LoginTask::userHandler; + myserver->socketWrite("USER " + + myserver->pop3LoginInfo.uid + + "\r\n"); + return; + } + + if (!myserver->hasCapability(string("AUTH=CRAM-") + + mail::imaphmac + ::hmac_methods[hmac_index] + ->getName())) + { + ++hmac_index; + continue; + } + break; + } + + currentHandler=&LoginTask::hmacChallengeHandler; + myserver->socketWrite(string("AUTH CRAM-") + + mail::imaphmac::hmac_methods[hmac_index] + ->getName() + "\r\n"); +} + +void mail::pop3::LoginTask::hmacChallengeHandler(const char *message) +{ + if (*message != '+') + { + ++hmac_index; + hmacHandler(message); + return; + } + + do + { + ++message; + } while (*message == ' '); + + string s=mail::decodebase64str(message); + + s= (*mail::imaphmac::hmac_methods[hmac_index++]) + (myserver->pop3LoginInfo.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(myserver->pop3LoginInfo.uid + " " + sHex); + currentHandler=&LoginTask::hmacHandler; + myserver->socketWrite(s + "\r\n"); +} + +void mail::pop3::LoginTask::userHandler(const char *message) +{ + if (*message != '+') + { + fail(message); + return; + } + + currentHandler=&LoginTask::passHandler; + myserver->socketWrite("PASS " + + myserver->pop3LoginInfo.pwd + "\r\n"); +} + +void mail::pop3::LoginTask::passHandler(const char *message) +{ + if (*message != '+') + { + fail(message); + return; + } + loggedIn(message); +} + +void mail::pop3::LoginTask::loggedIn(const char *message) +{ + myserver->savedLoginInfo.loginCallbackFunc= + myserver->pop3LoginInfo.loginCallbackFunc=NULL; // Got it + currentHandler=&LoginTask::listHandler; + myserver->listMap.clear(); + myserver->socketWrite("LIST\r\n"); +} + +void mail::pop3::LoginTask::listHandler(const char *message) +{ + if (*message == '-') + { + fail(message); + return; + } + + if (*message == '+') + return; // LIST results follow + + if (strcmp(message, ".") == 0) + { + myserver->uidlMap.clear(); + + if (!myserver->ispop3maildrop()) + { + currentHandler=&LoginTask::uidlHandler; + myserver->socketWrite("UIDL\r\n"); + return; + } + + uidlHandler("."); /* Don't do UIDL for pop3 maildrops */ + return; + } + + int messageNum=0; + unsigned long messageSize=0; + + istringstream i(message); + + i >> messageNum; + + i >> messageSize; + + myserver->listMap.insert(make_pair(messageNum, messageSize)); +} + +void mail::pop3::LoginTask::uidlHandler(const char *message) +{ + if (*message == '-') + { + fail("-ERR This POP3 server does not appear to implement the POP3 UIDL command. Upgrade the POP3 server software, or use the POP3 maildrop mode."); + return; + } + + if (*message == '+') + return; // UIDL results follow + + if (strcmp(message, ".") == 0) + { + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + done(); + + if (c) + { + c->success("Logged in."); + } + return; + } + + int msgNum=0; + + while (*message && isdigit((int)(unsigned char)*message)) + msgNum=msgNum * 10 + (*message++ - '0'); + + while (*message && unicode_isspace((unsigned char)*message)) + message++; + + myserver->uidlMap.insert(make_pair(string(message), msgNum)); +} + + +////////////////////////////////////////////////////////////////////////////// + +mail::pop3::LoggedInTask::LoggedInTask(mail::callback *callback, + mail::pop3 &myserverArg) + : Task(callback, myserverArg) +{ +} + +mail::pop3::LoggedInTask::~LoggedInTask() +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + if (c) + c->fail("An exception occurred while trying to initialize this task."); +} + +void mail::pop3::LoggedInTask::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (callbackPtr) + callbackPtr-> + reportProgress(bytesCompleted, bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +void mail::pop3::LoggedInTask::installedTask() +{ + Task::installedTask(); + + if (myserver->getfd() >= 0) // Logged in, yippee!! + { + loggedIn(); + return; + } + + myserver->installTask(new LoginTask( *myserver, this)); + + if ((*myserver->tasks.begin()) != this) + kill(getpid(), SIGABRT); + + myserver->tasks.erase(myserver->tasks.begin()); // myself + + (*myserver->tasks.begin())->installedTask(); + // Guaranteed something's there. +} + +void mail::pop3::LoggedInTask::success(string message) +{ + myserver->installTask(this); // Try again +} + +void mail::pop3::LoggedInTask::fail(string message) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + delete this; + + if (c) + c->fail(message); +} + +///////////////////////////////////////////////////////////////////////////// + +class mail::pop3::CheckNewMailTask : public mail::pop3::LoggedInTask { + +public: + bool forceSaveSnapshot; + + class sortNewMail { + + map<string, int> &umap; + public: + sortNewMail(map<string, int> &umapArg); + ~sortNewMail(); + + bool operator()(string a, string b); + }; + + + CheckNewMailTask(mail::pop3 &serverArg, mail::callback &callbackArg); + ~CheckNewMailTask(); + + void loggedIn(); + void loginFailed(string errmsg); + + void serverResponse(const char *message); + + void done(); +private: + void (CheckNewMailTask::*currentHandler)(const char *status); + int next2Download; // When subclass by pop3maildrop, next msg 2 get. + bool willReconnectFlag; + + bool willReconnect(); + + + void doNextDownload(); + mail::addMessage *downloadMsg; + + void retrHandler(const char *); + void retrBodyHandler(const char *); + void doDeleteDownloaded(); + void deleHandler(const char *); + void quitHandler(const char *); + + size_t bytesCompleted; + size_t bytesEstimatedTotal; + + size_t messagesEstimatedTotal; + +}; + +mail::pop3::CheckNewMailTask::CheckNewMailTask(mail::pop3 &serverArg, + mail::callback &callbackArg) + : LoggedInTask(&callbackArg, serverArg), + forceSaveSnapshot(false), willReconnectFlag(false) +{ +} + +mail::pop3::CheckNewMailTask::~CheckNewMailTask() +{ +} + +void mail::pop3::CheckNewMailTask::serverResponse(const char *message) +{ + if (currentHandler) + (this->*currentHandler)(message); +} + +void mail::pop3::CheckNewMailTask::loggedIn() +{ + mail::callback *myCallback=callbackPtr; + + callbackPtr=NULL; + + mail::ptr<mail::pop3> serverPtr= myserver; + + bool changedFolder=myserver->reconcileFolderIndex(); + + if (forceSaveSnapshot) + changedFolder=true; // Explicit check new mail saves snapshot + + // + // Now, save a snapshot. + // + if (!serverPtr.isDestroyed() && serverPtr->folderCallback && + changedFolder) + serverPtr->folderCallback->saveSnapshot("POP3"); + + if (!serverPtr.isDestroyed()) + { + if (serverPtr->ispop3maildrop()) + { + serverPtr->pop3maildropreset(); + + callbackPtr=myCallback; + + next2Download=1; + messagesEstimatedTotal=myserver->listMap.size(); + doNextDownload(); + return; + } + done(); + } + + if (myCallback) + myCallback->success("OK"); +} + +void mail::pop3::CheckNewMailTask::loginFailed(string errmsg) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + done(); + + if (c) + c->fail(errmsg); +} + +mail::pop3::CheckNewMailTask::sortNewMail +::sortNewMail(map<string, int> &umapArg) + : umap(umapArg) +{ +} + +mail::pop3::CheckNewMailTask::sortNewMail::~sortNewMail() +{ +} + +bool mail::pop3::CheckNewMailTask::sortNewMail::operator()(string a, + string b) +{ + map<string, int>::iterator ap, bp; + + ap=umap.find(a); + bp=umap.find(b); + + return ((ap == umap.end() ? 0:ap->second) < + (bp == umap.end() ? 0:bp->second)); +} + + +void mail::pop3::CheckNewMailTask::doNextDownload() +{ + std::map<int, unsigned long>::iterator p= + myserver->listMap.find(next2Download); + + bytesCompleted=0; + bytesEstimatedTotal=0; + + if (next2Download > 200 || + // Grab max 200 msgs at a time to prevent out of file descriptors + p == myserver->listMap.end()) + { + string errmsg=myserver->commitDownloadedMsgs(); + + if (errmsg.size() > 0) + { + mail::callback *c=callbackPtr; + callbackPtr=NULL; + done(); + if (c) + c->fail(errmsg.c_str()); + return; + } + if (callbackPtr) + callbackPtr->reportProgress(0, 0, + messagesEstimatedTotal, + messagesEstimatedTotal); + doDeleteDownloaded(); + return; + } + + + downloadMsg=myserver->newDownloadMsg(); + + if (!downloadMsg) + { + mail::callback *c=callbackPtr; + callbackPtr=NULL; + done(); + if (c) + c->fail(strerror(errno)); + return; + } + downloadMsg->messageInfo.unread=true; /* New message */ + + currentHandler=&CheckNewMailTask::retrHandler; + + ostringstream o; + + bytesCompleted=0; + + if ((size_t)next2Download > messagesEstimatedTotal) + messagesEstimatedTotal=next2Download; + + map<int, unsigned long>::iterator s= + myserver->listMap.find(next2Download); + + bytesEstimatedTotal=s == myserver->listMap.end() ? 0: + s->second; + + if (callbackPtr) + callbackPtr->reportProgress(0, + bytesEstimatedTotal, + next2Download-1, + messagesEstimatedTotal); + o << "RETR " << next2Download << "\r\n"; + + myserver->socketWrite(o.str()); + ++next2Download; +} + + +void mail::pop3::CheckNewMailTask::retrHandler(const char *message) +{ + if (*message != '+') + { + mail::callback *c=callbackPtr; + callbackPtr=NULL; + done(); + if (c) + c->fail(message); + return; + } + + currentHandler=&CheckNewMailTask::retrBodyHandler; +} + +void mail::pop3::CheckNewMailTask::retrBodyHandler(const char *message) +{ + if (strcmp(message, ".")) + { + if (*message == '.') + ++message; + + bytesCompleted += strlen(message)+2; + if (bytesCompleted > bytesEstimatedTotal) + bytesCompleted=bytesEstimatedTotal; + if (callbackPtr) + callbackPtr->reportProgress(bytesCompleted, + bytesEstimatedTotal, + next2Download-1, + messagesEstimatedTotal); + downloadMsg->saveMessageContents(string(message)+"\n"); + return; + } + + doNextDownload(); +} + +void mail::pop3::CheckNewMailTask::doDeleteDownloaded() +{ + if (--next2Download <= 0) + { + willReconnectFlag=true; + currentHandler=&CheckNewMailTask::quitHandler; + myserver->socketWrite("QUIT\r\n"); + return; + } + + currentHandler=&CheckNewMailTask::deleHandler; + + ostringstream o; + + o << "DELE " << next2Download << "\r\n"; + myserver->socketWrite(o.str()); +} + +void mail::pop3::CheckNewMailTask::deleHandler(const char *message) +{ + doDeleteDownloaded(); +} + +void mail::pop3::CheckNewMailTask::quitHandler(const char *message) +{ + if (myserver->socketEndEncryption()) + return; // Catch me on the rebound. + + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + myserver->socketDisconnect(); + myserver->orderlyShutdown=true; + myserver->disconnect(""); + + if (c) + c->success("Ok"); +} + + +bool mail::pop3::CheckNewMailTask::willReconnect() +{ + return willReconnectFlag; +} + +void mail::pop3::CheckNewMailTask::done() +{ + if (!willReconnectFlag) + { + LoggedInTask::done(); + return; + } + + mail::callback *c=callbackPtr; + callbackPtr=NULL; + LoggedInTask::done(); + if (c) + c->success("Ok."); +} + +///////////////////////////////////////////////////////////////////////////// + +class mail::pop3::LogoutTask : public mail::pop3::Task { + + void fail(const char *reason); + + void serverResponse(const char *message); + + void installedTask(); + bool isLogoutTask(); + + mail::callback *disconnectCallback; + + bool willReconnectFlag; + +public: + LogoutTask(mail::pop3 &server, mail::callback *callbackArg, + bool willReconnectFlagArg=false); + ~LogoutTask(); + + bool willReconnect(); +}; + +mail::pop3::LogoutTask::LogoutTask(mail::pop3 &server, + mail::callback *callbackArg, + bool willReconnectFlagArg) + : Task(callbackArg, server), + disconnectCallback(NULL), + willReconnectFlag(willReconnectFlagArg) +{ +} + +bool mail::pop3::LogoutTask::willReconnect() +{ + return willReconnectFlag; +} + +mail::pop3::LogoutTask::~LogoutTask() +{ + myserver->socketDisconnect(); + if (disconnectCallback) + disconnectCallback->success("OK"); +} + +bool mail::pop3::LogoutTask::isLogoutTask() +{ + return true; +} + +void mail::pop3::LogoutTask::installedTask() +{ + Task::installedTask(); + + if (myserver->getfd() < 0) + { + serverResponse("OK"); // Already logged out. + return; + } + + myserver->socketWrite("QUIT\r\n"); +} + +void mail::pop3::LogoutTask::serverResponse(const char *message) +{ + const char *p=strchr(message, ' '); + + if (p) + message=p+1; + + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + disconnectCallback=c; + + if (myserver->socketEndEncryption()) + return; // Catch me on the rebound. + + myserver->socketDisconnect(); + myserver->orderlyShutdown=true; + myserver->disconnect(""); +} + +///////////////////////////////////////////////////////////////////////////// + +class mail::pop3::ForceCheckNewMailTask : public mail::callback { + + mail::callback *realCallback; + mail::pop3 *server; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + ForceCheckNewMailTask(mail::callback *realCallbackArg, + mail::pop3 *serverArg); + ~ForceCheckNewMailTask(); + + void success(string); + void fail(string); +}; + +mail::pop3::ForceCheckNewMailTask +::ForceCheckNewMailTask(mail::callback *realCallbackArg, + mail::pop3 *serverArg) + : realCallback(realCallbackArg), server(serverArg) +{ +} + +mail::pop3::ForceCheckNewMailTask::~ForceCheckNewMailTask() +{ +} + +void mail::pop3::ForceCheckNewMailTask +::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (realCallback) + realCallback->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +// +// checkNewMail installed a LogoutTask. LogoutTask's destructor ends up +// calling the logout callback function, which is me. +// LogoutTask is destroyed by LogoutTask::Task::done()'s delete this. +// +// Install the CheckNewMailTask at the beginning of the task queue, so +// Task::done() ends up calling CheckNewMailTask's installedTask. + +void mail::pop3::ForceCheckNewMailTask::success(string message) +{ + CheckNewMailTask *cn=new CheckNewMailTask(*server, *realCallback); + + if (!cn) + realCallback->fail(strerror(errno)); + + try { + cn->forceSaveSnapshot=true; + server->lastTaskCompleted=0; + server->tasks.insert(server->tasks.begin(), cn); + delete this; + } catch (...) { + delete cn; + realCallback->fail(message); + delete this; + } +} + +void mail::pop3::ForceCheckNewMailTask::fail(string errmsg) +{ + success(errmsg); +} + +///////////////////////////////////////////////////////////////////////////// +// +// EXPUNGE - sent all the DELEs, then QUIT out of the folder, then reopen it. + +class mail::pop3::UpdateTask : public mail::pop3::LoggedInTask { + + size_t updateIndex; + +#if 0 + class LogoutCallback : public mail::callback { + mail::ptr<mail::pop3> myserver; + mail::callback *callback; + public: + LogoutCallback(mail::pop3 *serverArg, + mail::callback *callbackArg); + ~LogoutCallback(); + + void success(string); + void fail(string); + }; +#endif + +public: + UpdateTask(mail::pop3 &serverArg, mail::callback &callbackArg); + ~UpdateTask(); + + void loggedIn(); + void loginFailed(string errmsg); + + void serverResponse(const char *message); +}; + +mail::pop3::UpdateTask::UpdateTask(mail::pop3 &serverArg, + mail::callback &callbackArg) + : LoggedInTask(&callbackArg, serverArg), + updateIndex(0) +{ +} + +mail::pop3::UpdateTask::~UpdateTask() +{ +} + +void mail::pop3::UpdateTask::loggedIn() +{ + serverResponse("+OK"); +} + +void mail::pop3::UpdateTask::loginFailed(string errmsg) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + done(); + + if (c) + c->fail(errmsg); +} + +void mail::pop3::UpdateTask::serverResponse(const char *message) +{ + if (*message != '+') + { + loginFailed(message); + return; + } + + // Check for deleted messages. + + while (updateIndex < myserver->currentFolderIndex.size()) + { + string uid; + + if (myserver->currentFolderIndex[updateIndex].deleted && + myserver->uidlMap.count(uid=myserver->currentFolderIndex + [updateIndex].uid) > 0) + { + int msgNum=myserver->uidlMap.find(uid)->second; + + string buffer; + { + ostringstream o; + + o << msgNum; + buffer=o.str(); + } + + ++updateIndex; + myserver->socketWrite(string("DELE ") + buffer + + "\r\n"); + return; + } + ++updateIndex; + } + + // Do the usual logout logic, which will take care of this. + + ForceCheckNewMailTask *l= + new ForceCheckNewMailTask(callbackPtr, myserver); + + if (!l) + { + loginFailed("Out of memory."); + return; + } + + callbackPtr=NULL; + + if ( (*myserver->tasks.begin()) != this) + kill(getpid(), SIGABRT); + + try { + LogoutTask *lo=new LogoutTask( *myserver, l, true); + + if (!lo) + strerror(errno); + + try { + myserver->tasks.insert(++myserver->tasks.begin(), + lo); + } catch (...) { + delete lo; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + } catch (...) { + delete l; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + done(); +} + +#if 0 +mail::pop3::UpdateTask::LogoutCallback::LogoutCallback(mail::pop3 *serverArg, + mail::callback + *callbackArg) + : myserver(serverArg), callback(callbackArg) +{ +} + +mail::pop3::UpdateTask::LogoutCallback::~LogoutCallback() +{ +} + +void mail::pop3::UpdateTask::LogoutCallback::success(string message) +{ + mail::callback *c=callback; + mail::pop3 *s=myserver; + + callback=NULL; + delete this; + + if (!s) + c->success(message); + else try { + s->checkNewMail(*c); // Immediately check for new mail. + } catch (...) { + c->success(message); + } +} + +void mail::pop3::UpdateTask::LogoutCallback::fail(string message) +{ + success(message); // I'm an optimist. +} +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// Generic message read. + +class mail::pop3::ReadMessageTask : public mail::pop3::LoggedInTask { + + string uid; + size_t messageNum; + bool peek; + mail::readMode readType; + + bool seenHeaders; + string currentHeader; + bool expectingStatus; + + size_t expected_cnt; + size_t received_cnt; + + mail::callback::message *msgCallback; + +public: + ReadMessageTask(mail::pop3 &serverArg, + mail::callback::message &callbackArg, + string uidArg, + size_t messageNumArg, + bool peekArg, + mail::readMode readTypeArg); + ~ReadMessageTask(); + + void loggedIn(); + void loginFailed(string errmsg); + + void serverResponse(const char *message); +}; + +mail::pop3::ReadMessageTask::ReadMessageTask(mail::pop3 &serverArg, + mail::callback::message + &callbackArg, + string uidArg, + size_t messageNumArg, + bool peekArg, + mail::readMode readTypeArg) + : LoggedInTask(&callbackArg, serverArg), + uid(uidArg), + messageNum(messageNumArg), + peek(peekArg), + readType(readTypeArg), + msgCallback(&callbackArg) +{ +} + +mail::pop3::ReadMessageTask::~ReadMessageTask() +{ +} + +void mail::pop3::ReadMessageTask::loggedIn() +{ + map<string, int>::iterator p=myserver->uidlMap.find(uid); + + if (p == myserver->uidlMap.end()) + { + loginFailed("Message was deleted by the POP3 server."); + return; + } + + int msgNum=p->second; + + seenHeaders=false; + currentHeader=""; + expectingStatus=true; + + if (!fixMessageNumber(myserver, uid, messageNum)) + { + expectingStatus=false; + serverResponse("."); + return; + } + + string buffer; + + { + ostringstream o; + + o << msgNum; + buffer=o.str(); + } + + string cmd; + + expected_cnt=0; + received_cnt=0; + + if (readType == mail::readContents || + readType == mail::readBoth) + { + cmd=string("RETR ") + buffer + "\r\n"; + + map<int, unsigned long>::iterator p + =myserver->listMap.find(msgNum); + + if (p != myserver->listMap.end()) + expected_cnt=p->second; + } + else + cmd=string("TOP ") + buffer + " 0\r\n"; + + myserver->socketWrite(cmd); +} + +void mail::pop3::ReadMessageTask::loginFailed(string errmsg) +{ + mail::callback *c=callbackPtr; + + callbackPtr=NULL; + + done(); + + if (c) + c->fail(errmsg); +} + +void mail::pop3::ReadMessageTask::serverResponse(const char *message) +{ + if (expectingStatus) + { + expectingStatus=false; + if (*message != '+') + loginFailed(message); + return; + } + + time(&defaultTimeout); + defaultTimeout += myserver->timeoutSetting; + // Some activity -- don't time out + + if (strcmp(message, ".") == 0) + { + mail::callback::message *c=msgCallback; + + mail::ptr<mail::pop3> ptr= myserver; + + bool peekFlag=peek; + + if (!fixMessageNumber(myserver, uid, messageNum)) + peekFlag=true; + + if (callbackPtr == NULL) + c=NULL; + + callbackPtr=NULL; + msgCallback=NULL; + + if (currentHeader.size() > 0) + { + if (c) + c->messageTextCallback(0, + currentHeader + "\n"); + } + + size_t n=messageNum; + + received_cnt=expected_cnt; + if (c) + c->reportProgress(expected_cnt, expected_cnt, 1, 1); + + done(); + + if (c) + c->success("OK"); + + if (!peekFlag && !ptr.isDestroyed()) + { + ptr->currentFolderIndex[n].unread=false; + if (ptr->folderCallback) + ptr->folderCallback + ->messageChanged(n); + + if (!ptr.isDestroyed() && ptr->folderCallback) + ptr->folderCallback->saveSnapshot("POP3"); + } + + return; + } + + if (*message == '.') + ++message; + + received_cnt += strlen(message)+2; + + if (callbackPtr && msgCallback) + msgCallback->reportProgress(received_cnt, expected_cnt, 0, 1); + + + if (readType == mail::readHeadersFolded) + // Reformat headers, one per line + { + if (!*message) // End of headers + { + string s=currentHeader; + + currentHeader=""; + + if (s.size() > 0 && callbackPtr && msgCallback) + msgCallback->messageTextCallback(0, s + "\n"); + return; + } + + if (unicode_isspace((unsigned char)*message)) + { + while (*message && + unicode_isspace((unsigned char)*message)) + ++message; + + if (currentHeader.size() > 0) + currentHeader += " "; + currentHeader += message; + return; + } + + string s=currentHeader; + currentHeader=message; + + if (callbackPtr && msgCallback && s.size() > 0) + msgCallback->messageTextCallback(0, s + "\n"); + return; + } + + if (readType == mail::readContents && !seenHeaders) + { + if (!*message) + seenHeaders=true; + return; // Eat the headers + } + + string line=message; + + line += "\n"; + + if (callbackPtr && msgCallback) + msgCallback->messageTextCallback(0, line); +} + + +///////////////////////////////////////////////////////////////////////////// + +class mail::pop3::CacheMessageTask : public mail::callback::message { + + mail::ptr<mail::pop3> myserver; + + mail::callback *callback; + + string uid; + + int *fdptr; + struct rfc2045 **rfcptr; + + FILE *tmpfilefp; + struct rfc2045 *rfc; + + void reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal); + +public: + CacheMessageTask(mail::pop3 *myserverArg, + mail::callback *callbackArg, + string uidArg, + int *fdArg, + struct rfc2045 **rfcArg); + ~CacheMessageTask(); + + bool init(); + + void success(string); + void fail(string); + + void messageTextCallback(size_t n, string text); +}; + +mail::pop3::CacheMessageTask::CacheMessageTask(mail::pop3 *myserverArg, + mail::callback *callbackArg, + string uidArg, + int *fdArg, + struct rfc2045 **rfcArg) + : myserver(myserverArg), + callback(callbackArg), + uid(uidArg), + fdptr(fdArg), + rfcptr(rfcArg), + tmpfilefp(NULL), + rfc(NULL) +{ +} + +mail::pop3::CacheMessageTask::~CacheMessageTask() +{ + if (tmpfilefp) + fclose(tmpfilefp); + + if (rfc) + rfc2045_free(rfc); +} + +void mail::pop3 +::CacheMessageTask::reportProgress(size_t bytesCompleted, + size_t bytesEstimatedTotal, + + size_t messagesCompleted, + size_t messagesEstimatedTotal) +{ + if (callback) + callback->reportProgress(bytesCompleted, + bytesEstimatedTotal, + messagesCompleted, + messagesEstimatedTotal); +} + +void mail::pop3::CacheMessageTask::success(string message) +{ + if (myserver.isDestroyed()) + { + fail("POP3 server connection aborted."); + return; + } + + if (fflush(tmpfilefp) || ferror(tmpfilefp)) + { + fail(strerror(errno)); + return; + } + + if (myserver->genericTmpFd >= 0) + close(myserver->genericTmpFd); + + rfc2045_parse_partial(rfc); + + if (myserver->genericTmpRfcp) + { + rfc2045_free(myserver->genericTmpRfcp); + myserver->genericTmpRfcp=NULL; + } + + myserver->cachedUid=uid; + + if ((myserver->genericTmpFd=dup(fileno(tmpfilefp))) < 0) + { + fail(strerror(errno)); + return; + } + + fcntl(myserver->genericTmpFd, F_SETFD, FD_CLOEXEC); + + if (fdptr) + *fdptr=myserver->genericTmpFd; + + myserver->genericTmpRfcp=rfc; + + if (rfcptr) + *rfcptr=rfc; + + rfc=NULL; + + mail::callback *c=callback; + + callback=NULL; + + delete this; + c->success(message); +} + +void mail::pop3::CacheMessageTask::fail(string message) +{ + mail::callback *c=callback; + + callback=NULL; + + delete this; + c->fail(message); +} + +bool mail::pop3::CacheMessageTask::init() +{ + if ((tmpfilefp=tmpfile()) == NULL || + (rfc=rfc2045_alloc()) == NULL) + { + fail(strerror(errno)); + return false; + } + + return true; +} + +void mail::pop3::CacheMessageTask::messageTextCallback(size_t n, string text) +{ + if (fwrite(text.c_str(), text.size(), 1, tmpfilefp) != 1) + ; // Ignore gcc warning + rfc2045_parse(rfc, text.c_str(), text.size()); +} + + +///////////////////////////////////////////////////////////////////////////// + +#if 0 +mail::pop3::AutologoutCallback::AutologoutCallback() +{ +} + +mail::pop3::AutologoutCallback::~AutologoutCallback() +{ +} + +void mail::pop3::AutologoutCallback::success(string message) +{ + delete this; +} + +void mail::pop3::AutologoutCallback::fail(string message) +{ + delete this; +} +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// A NOOP msg - keep the server from timing out. + +class mail::pop3::NoopTask : public mail::pop3::LoggedInTask { +public: + NoopTask(mail::pop3 &myseverArg); + ~NoopTask(); + void serverResponse(const char *message); + void loggedIn(); + void loginFailed(string errmsg); +}; + +mail::pop3::NoopTask::NoopTask(mail::pop3 &serverArg) + : LoggedInTask(NULL, serverArg) +{ +} + +mail::pop3::NoopTask::~NoopTask() +{ +} + +void mail::pop3::NoopTask::loginFailed(string errmsg) +{ +} + +void mail::pop3::NoopTask::loggedIn() +{ + myserver->socketWrite("NOOP\r\n"); +} + +void mail::pop3::NoopTask::serverResponse(const char *message) +{ + done(); +} + + +///////////////////////////////////////////////////////////////////////////// + + +mail::pop3::pop3MessageInfo::pop3MessageInfo() +{ +} + +mail::pop3::pop3MessageInfo::~pop3MessageInfo() +{ +} + +///////////////////////////////////////////////////////////////////////////// + + +mail::pop3::pop3(string url, string passwd, + std::vector<std::string> &certificates, + mail::loginCallback *callback_func, + mail::callback &callback, + mail::callback::disconnect &disconnectCallback) + : mail::fd(disconnectCallback, certificates), + calledDisconnected(false), + orderlyShutdown(false), + lastTaskCompleted(0), + inbox(this), folderCallback(NULL), + genericTmpFd(-1), genericTmpRfcp(NULL) +{ + savedLoginInfo.callbackPtr=NULL; + savedLoginInfo.loginCallbackFunc=callback_func; + + if (!loginUrlDecode(url, savedLoginInfo)) + { + callback.fail("Invalid POP3 URL."); + return; + } + + timeoutSetting=getTimeoutSetting(savedLoginInfo, "timeout", 60, + 30, 600); + noopSetting=getTimeoutSetting(savedLoginInfo, "noop", 60, + 5, 1800); + + savedLoginInfo.use_ssl=savedLoginInfo.method == "pop3s"; + if (passwd.size() > 0) + savedLoginInfo.pwd=passwd; + + installTask(new CheckNewMailTask(*this, callback)); +} + +void mail::pop3::resumed() +{ + if (!tasks.empty()) + (*tasks.begin())->resetTimeout(); +} + +void mail::pop3::handler(vector<pollfd> &fds, int &ioTimeout) +{ + int fd_save=getfd(); + + if (!tasks.empty()) + { + int t=(*tasks.begin())->getTimeout() * 1000; + + if (t < ioTimeout) + ioTimeout=t; + } + + if (fd_save >= 0 && lastTaskCompleted) + { + time_t now; + + time(&now); + + if (now < lastTaskCompleted) + lastTaskCompleted=now; + + if (now >= lastTaskCompleted + noopSetting) + { + lastTaskCompleted=0; + + installTask(new NoopTask(*this)); + } + else + { + if (ioTimeout >= + (lastTaskCompleted + noopSetting - now) * 1000) + { + ioTimeout=(lastTaskCompleted + noopSetting + - now) * 1000; + } + } + } + + MONITOR(mail::account); + + if (process(fds, ioTimeout) < 0) + return; + + if (DESTROYED()) + ioTimeout=0; + + if (DESTROYED() || 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; + } + } + } +} + +int mail::pop3::socketRead(const string &readbuffer) +{ + mail::ptr<mail::pop3> myself=this; + + size_t n=0; + + string::const_iterator b=readbuffer.begin(), e=readbuffer.end(), c; + + c=b; + + while (b != e) + { + if (*b != '\n') + { + b++; + continue; + } + + string l=string(c, b++); + + n += b-c; + c=b; + + size_t i; + + while ((i=l.find('\r')) != std::string::npos) + l=l.substr(0, i) + l.substr(i+1); + + if (!tasks.empty()) + (*tasks.begin())->serverResponse(l.c_str()); + + break; + } + + if (myself.isDestroyed()) + return n; + + if (n + 16000 < readbuffer.size()) + n=readbuffer.size() - 16000; + // SANITY CHECK - DON'T LET HOSTILE SERVER DOS us + + return n; +} + +void mail::pop3::disconnect(const char *reason) +{ + if (!tasks.empty()) + { + if ( (*tasks.begin())->willReconnect()) + { + socketDisconnect(); + (*tasks.begin())->done(); + return; + } + } + + if (genericTmpFd >= 0) + { + close(genericTmpFd); + genericTmpFd= -1; + } + + if (genericTmpRfcp != NULL) + { + rfc2045_free(genericTmpRfcp); + genericTmpRfcp=NULL; + } + + string errmsg=reason ? reason:""; + + if (getfd() >= 0) + { + string errmsg2=socketDisconnect(); + + if (errmsg2.size() > 0) + errmsg=errmsg2; + + if (orderlyShutdown) + errmsg=""; + else if (errmsg.size() == 0) + errmsg="Connection closed by remote host."; + } + + while (!tasks.empty()) + (*tasks.begin()) + ->disconnected(errmsg.size() > 0 ? + errmsg.c_str() + : "POP3 server connection closed."); + + if (!calledDisconnected) + { + calledDisconnected=true; + disconnect_callback.disconnected(""); + } + +} + +void mail::pop3::installTask(Task *t) +{ + if (!t) + LIBMAIL_THROW("Out of memory."); + + bool wasEmpty; + + lastTaskCompleted=0; + + try { + wasEmpty=tasks.empty(); + + tasks.insert(tasks.end(), t); + } catch (...) { + delete t; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + + if (wasEmpty) + (*tasks.begin())->installedTask(); +} + +mail::pop3::~pop3() +{ + disconnect("POP3 server connection aborted."); + currentFolderIndex.clear(); +} + +void mail::pop3::logout(mail::callback &callback) +{ + installTask(new LogoutTask(*this, &callback)); +} + +void mail::pop3::checkNewMail(mail::callback &callback) +{ + ForceCheckNewMailTask *reloginTask= + new ForceCheckNewMailTask(&callback, this); + + if (!reloginTask) + callback.fail("Out of memory"); + + try { + installTask(new LogoutTask(*this, reloginTask, true)); + } catch (...) { + delete reloginTask; + + callback.fail("Out of memory."); + } +} + +bool mail::pop3::hasCapability(string capability) +{ + if (capability == LIBMAIL_SINGLEFOLDER) + return true; + + return capabilities.count(capability) > 0; +} + +string mail::pop3::getCapability(string capability) +{ + upper(capability); + + if (capability == LIBMAIL_SERVERTYPE) + { + return "pop3"; + } + + map<string, string>::iterator p; + + if (capability == LIBMAIL_SERVERDESCR) + { + p=capabilities.find("IMPLEMENTATION"); + + return (p == capabilities.end() ? + (ispop3maildrop() ? "POP3 maildrop": + "POP3 account") + : + (ispop3maildrop() ? "POP3 maildrop - ":"POP3 - ") + + p->second); + } + + p=capabilities.find(capability); + + if (p == capabilities.end()) + return ""; + + return p->second; +} + +mail::folder *mail::pop3::folderFromString(string string) +{ + return inbox.clone(); +} + +void mail::pop3::readTopLevelFolders(mail::callback::folderList &callback1, + mail::callback &callback2) +{ + vector<const mail::folder *> folders; + + folders.push_back(&inbox); + + callback1.success(folders); + callback2.success("OK"); +} + +void mail::pop3::findFolder(string folder, + mail::callback::folderList &callback1, + mail::callback &callback2) +{ + return readTopLevelFolders(callback1, callback2); +} + +void mail::pop3::genericMessageRead(string uid, + size_t messageNumber, + bool peek, + mail::readMode readType, + mail::callback::message &callback) +{ + if (fixMessageNumber(this, uid, messageNumber) && + genericCachedUid(uid)) + { + // Optimum path + + vector<size_t> vec; + vec.push_back(messageNumber); + + readMessageContent(vec, peek, readType, callback); + return; + } + + installTask(new ReadMessageTask(*this, callback, + uid, + messageNumber, + peek, + readType)); +} + +void mail::pop3::genericMessageSize(string uid, + size_t messageNumber, + mail::callback::message &callback) +{ + if (uidlMap.count(uid) > 0) + { + int messageNum=uidlMap.find(uid)->second; + + if (listMap.count(messageNum) > 0 && + fixMessageNumber(this, uid, messageNumber)) + { + callback.messageSizeCallback(messageNumber, + listMap.find(messageNum) + ->second); + } + } + callback.success("OK"); +} + +void mail::pop3::readMessageAttributes(const vector<size_t> &messages, + MessageAttributes attributes, + mail::callback::message &callback) +{ + mail::generic::genericAttributes(this, this, messages, + attributes, + callback); +} + +void mail::pop3::genericGetMessageFd(string uid, + size_t messageNumber, + bool peek, + int &fdRet, + mail::callback &callback) +{ + if (genericTmpFd >= 0 && cachedUid == uid) + { + fdRet=genericTmpFd; + callback.success("OK"); + return; + } + + if (uidlMap.count(uid) == 0) + { + callback.fail("Message removed from the POP3 server."); + return; + } + + CacheMessageTask *t= + new CacheMessageTask(this, &callback, uid, &fdRet, NULL); + + if (!t || !t->init()) + { + if (t) + delete t; + + callback.fail("Out of memory."); + } + + try { + genericMessageRead(uid, messageNumber, peek, + mail::readBoth, *t); + } catch (...) { + delete t; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } +} + + +void mail::pop3::genericGetMessageStruct(string uid, + size_t messageNumber, + struct rfc2045 *&structRet, + mail::callback &callback) +{ + if (genericTmpRfcp && cachedUid == uid) + { + structRet=genericTmpRfcp; + callback.success("OK"); + return; + } + + if (uidlMap.count(uid) == 0) + { + callback.fail("Message removed from the POP3 server."); + return; + } + + CacheMessageTask *t= + new CacheMessageTask(this, &callback, uid, NULL, &structRet); + + if (!t || !t->init()) + { + if (t) + delete t; + + callback.fail("Out of memory."); + } + + try { + genericMessageRead(uid, messageNumber, true, + mail::readBoth, *t); + } catch (...) { + delete t; + LIBMAIL_THROW(LIBMAIL_THROW_EMPTY); + } + +} + +bool mail::pop3::genericCachedUid(string uid) +{ + return genericTmpRfcp && cachedUid == uid; +} + +void mail::pop3::readMessageContent(const vector<size_t> &messages, + bool peek, + enum mail::readMode readType, + mail::callback::message &callback) +{ + genericReadMessageContent(this, this, messages, peek, readType, + callback); +} + +void mail::pop3::readMessageContent(size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + enum mail::readMode readType, + mail::callback::message &callback) +{ + genericReadMessageContent(this, this, messageNum, + peek, + msginfo, + readType, + callback); +} + +void mail::pop3::readMessageContentDecoded(size_t messageNum, + bool peek, + const mail::mimestruct &msginfo, + mail::callback::message &callback) +{ + genericReadMessageContentDecoded(this, this, messageNum, + peek, msginfo, callback); +} + +size_t mail::pop3::getFolderIndexSize() +{ + return currentFolderIndex.size(); +} + +mail::messageInfo mail::pop3::getFolderIndexInfo(size_t n) +{ + if (n < currentFolderIndex.size()) + return currentFolderIndex[n]; + + return mail::messageInfo(); +} + + +void mail::pop3::saveFolderIndexInfo(size_t messageNum, + const mail::messageInfo &info, + mail::callback &callback) +{ + MONITOR(mail::pop3); + + if (messageNum < currentFolderIndex.size()) + { + +#define DOFLAG(dummy1, field, dummy2) \ + (currentFolderIndex[messageNum].field= info.field) + + LIBMAIL_MSGFLAGS; + +#undef DOFLAG + + } + + callback.success("OK"); + + if (! DESTROYED() && messageNum < currentFolderIndex.size() + && folderCallback) + { + folderCallback->messageChanged(messageNum); + + if (!DESTROYED() && folderCallback) + folderCallback->saveSnapshot("POP3"); + } + +} + +void mail::pop3::genericMarkRead(size_t messageNumber) +{ + if (messageNumber < currentFolderIndex.size() && + currentFolderIndex[messageNumber].unread) + { + MONITOR(mail::pop3); + + currentFolderIndex[messageNumber].unread=false; + if (folderCallback) + folderCallback->messageChanged(messageNumber); + if (! DESTROYED() && folderCallback) + folderCallback->saveSnapshot("POP3"); + + } +} + +void mail::pop3::updateFolderIndexFlags(const vector<size_t> &messages, + bool doFlip, + bool enableDisable, + const mail::messageInfo &flags, + mail::callback &callback) +{ + vector<size_t>::const_iterator b, e; + + b=messages.begin(); + e=messages.end(); + + size_t n=currentFolderIndex.size(); + + while (b != e) + { + size_t i= *b++; + + if (i < n) + { +#define DOFLAG(dummy, field, dummy2) \ + if (flags.field) \ + { \ + currentFolderIndex[i].field=\ + doFlip ? !currentFolderIndex[i].field\ + : enableDisable; \ + } + + LIBMAIL_MSGFLAGS; +#undef DOFLAG + } + } + + b=messages.begin(); + e=messages.end(); + + MONITOR(mail::pop3); + + while (!DESTROYED() && b != e) + { + size_t i= *b++; + + if (i < n && folderCallback) + folderCallback->messageChanged(i); + } + + if (!DESTROYED()) + folderCallback->saveSnapshot("POP3"); + + callback.success("OK"); +} + +void mail::pop3::getFolderKeywordInfo(size_t n, set<string> &kwSet) +{ + kwSet.clear(); + + if (n < currentFolderIndex.size()) + currentFolderIndex[n].keywords.getFlags(kwSet); +} + +void mail::pop3::updateKeywords(const std::vector<size_t> &messages, + const std::set<std::string> &keywords, + bool setOrChange, + // false: set, true: see changeTo + bool changeTo, + callback &cb) +{ + MONITOR(mail::pop3); + + genericUpdateKeywords(messages, keywords, setOrChange, changeTo, + folderCallback, this, cb); + + if (!DESTROYED()) + folderCallback->saveSnapshot("POP3"); +} + +bool mail::pop3::genericProcessKeyword(size_t messageNumber, + updateKeywordHelper &helper) +{ + if (messageNumber < currentFolderIndex.size()) + helper.doUpdateKeyword(currentFolderIndex[messageNumber] + .keywords, keywordHashtable); + return true; +} + +void mail::pop3::updateFolderIndexInfo(mail::callback &callback) +{ + installTask(new UpdateTask(*this, callback)); +} + +void mail::pop3::removeMessages(const std::vector<size_t> &messages, + callback &cb) +{ + genericRemoveMessages(this, messages, cb); +} + +void mail::pop3::copyMessagesTo(const vector<size_t> &messages, + mail::folder *copyTo, + mail::callback &callback) +{ + mail::copyMessages::copy(this, messages, copyTo, callback); +} + +void mail::pop3::searchMessages(const mail::searchParams &searchInfo, + mail::searchCallback &callback) +{ + switch (searchInfo.criteria) { + case searchParams::larger: + case searchParams::smaller: + case searchParams::body: + case searchParams::text: + callback.fail("Not implemented."); + return; + default: + break; + } + mail::searchMessages::search(callback, searchInfo, this); +} + +void mail::pop3Folder::getParentFolder(callback::folderList &callback1, + callback &callback2) const +{ + vector<const mail::folder *> dummy; + + dummy.push_back(this); + + callback1.success(dummy); + callback2.success("OK"); +} + +void mail::pop3::readFolderInfo( mail::callback::folderInfo &callback1, + mail::callback &callback2) +{ + callback1.messageCount=currentFolderIndex.size(); + callback1.unreadCount=0; + + size_t i=0; + + for (i=0; i<callback1.messageCount; i++) + if (currentFolderIndex[i].unread) + ++callback1.unreadCount; + + callback1.success(); + callback2.success("OK"); +} + +void mail::pop3::open(mail::callback &openCallback, + mail::callback::folder &folderCallbackArg, + mail::snapshot *restoreSnapshot) + +{ + + if (restoreSnapshot) + { + string snapshotId; + size_t nmessages; + + folderCallback=NULL; + + restoreSnapshot->getSnapshotInfo(snapshotId, nmessages); + + if (snapshotId.size() > 0) + { + currentFolderIndex.clear(); + currentFolderIndex.resize(nmessages); + restoreAborted=false; + restoreSnapshot->restoreSnapshot(*this); + + if (!restoreAborted) + { + vector<pop3MessageInfo>::iterator b, e; + + b=currentFolderIndex.begin(); + e=currentFolderIndex.end(); + + // Sanity check: + + while (b != e) + { + if ( b->uid.size() == 0) + { + restoreAborted=true; + break; + } + b++; + } + } + + if (!restoreAborted) + { + folderCallback= &folderCallbackArg; + reconcileFolderIndex(); + openCallback.success("OK"); + return; + } + + // Rebuild folder index. + + currentFolderIndex.clear(); + reconcileFolderIndex(); + } + } + + folderCallback= &folderCallbackArg; + openCallback.success("OK"); +} + +void mail::pop3::restoreIndex(size_t msgNum, + const mail::messageInfo &info) +{ + if (msgNum < currentFolderIndex.size()) + (static_cast<mail::messageInfo &> + (currentFolderIndex[msgNum]))=info; +} + +void mail::pop3::restoreKeywords(size_t msgNum, + const std::set<std::string> &kwSet) +{ + if (msgNum < currentFolderIndex.size()) + currentFolderIndex[msgNum].keywords + .setFlags(keywordHashtable, kwSet); +} + +void mail::pop3::abortRestore() +{ + restoreAborted=true; +} + + + +// +// Reconcile the virtual folder index to reflect what's actually on the +// server. +// + +bool mail::pop3::reconcileFolderIndex() +{ + set<size_t> messagesSeen; + + map<string, int>::iterator ub, ue; + + size_t i; + + bool needSnapshot=false; + + // Make a list of all msgs that exist (and don't exist any more) + + mail::expungeList removedList; + + for (i=currentFolderIndex.size(); i; ) + { + string uid=currentFolderIndex[--i].uid; + + if (uidlMap.count( uid ) == 0) + { + removedList << i; + needSnapshot=true; + } + else + messagesSeen.insert(uidlMap.find(uid)->second); + } + + // Send expunge notices + + MONITOR(mail::pop3); + + mail::expungeList::iterator expungeIterator; + + for (expungeIterator=removedList.end(); + expungeIterator != removedList.begin(); ) + { + --expungeIterator; + currentFolderIndex.erase(currentFolderIndex.begin() + + expungeIterator->first, + currentFolderIndex.begin() + + expungeIterator->second+1); + } + + if (folderCallback) + removedList >> folderCallback; + + // Now check for new messages. + + if (!DESTROYED()) + { + vector<string> newUids; + + ub=uidlMap.begin(); + ue=uidlMap.end(); + + // sortNewMail() will end up sorting newUids chronologically. + + while (ub != ue) + { + if (messagesSeen.count(ub->second) == 0) + newUids.push_back(ub->first); + ub++; + } + sort(newUids.begin(), newUids.end(), + CheckNewMailTask::sortNewMail(uidlMap)); + + vector<string>::iterator b, e; + + b=newUids.begin(), e=newUids.end(); + + while (b != e) + { + pop3MessageInfo newInfo; + + // We'll have to assume that all msgs are unread. + + newInfo.unread=true; + newInfo.uid= *b++; + currentFolderIndex.push_back(newInfo); + } + + if (newUids.size() > 0 && folderCallback) + // Note folderCallback is NULL on account open! + { + needSnapshot=true; + folderCallback->newMessages(); + } + } + return needSnapshot; +} + +////////////////////// +// +// Stubs, overridden by mail::pop3maildrop::pop3acct + +bool mail::pop3::ispop3maildrop() +{ + return false; +} + +void mail::pop3::pop3maildropreset() +{ +} + +mail::addMessage *mail::pop3::newDownloadMsg() +{ + return NULL; +} + +string mail::pop3::commitDownloadedMsgs() +{ + return ""; +} |
