summaryrefslogtreecommitdiff
path: root/src/maildir.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/maildir.cc')
-rw-r--r--src/maildir.cc839
1 files changed, 839 insertions, 0 deletions
diff --git a/src/maildir.cc b/src/maildir.cc
new file mode 100644
index 0000000..11f472c
--- /dev/null
+++ b/src/maildir.cc
@@ -0,0 +1,839 @@
+/** --------------------------------------------------------------------
+ * @file maildir.cc
+ * @brief Implementation of the Maildir class.
+ * @author Andreas Aardal Hanssen
+ * @date 2002-2005
+ * ----------------------------------------------------------------- **/
+#include <iostream>
+#include <iomanip>
+#include <algorithm>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "convert.h"
+#include "iodevice.h"
+#include "iofactory.h"
+#include "maildir.h"
+#include "maildirmessage.h"
+#include "pendingupdates.h"
+#include "session.h"
+#include "status.h"
+#include "globals.h"
+
+using namespace ::std;
+using namespace Binc;
+
+// Used to generate the unique names for Maildir delivery
+static int numDeliveries = 0;
+
+//------------------------------------------------------------------------
+Maildir::iterator::iterator(void)
+{
+}
+
+//------------------------------------------------------------------------
+Maildir::iterator::iterator(Maildir *home,
+ MessageMap::iterator it,
+ const SequenceSet &_bset,
+ unsigned int _mod)
+ : BaseIterator(1), mailbox(home), bset(_bset), mod(_mod), i(it)
+{
+ uidmax = home->getMaxUid();
+ sqnrmax = home->getMaxSqnr();
+}
+
+//------------------------------------------------------------------------
+Maildir::iterator::iterator(const iterator &copy)
+ : BaseIterator(copy.sqnr), mailbox(copy.mailbox),
+ bset(copy.bset), mod(copy.mod), i(copy.i), uidmax(copy.uidmax),
+ sqnrmax(copy.sqnrmax)
+{
+}
+
+//------------------------------------------------------------------------
+Maildir::iterator &Maildir::iterator::operator =(const iterator &copy)
+{
+ sqnr = copy.sqnr;
+ mailbox = copy.mailbox;
+ bset = copy.bset;
+ mod = copy.mod;
+ i = copy.i;
+ uidmax = copy.uidmax;
+ sqnrmax = copy.sqnrmax;
+ return *this;
+}
+
+//------------------------------------------------------------------------
+Maildir::iterator::~iterator(void)
+{
+}
+
+//------------------------------------------------------------------------
+MaildirMessage &Maildir::iterator::curMessage(void)
+{
+ return i->second;
+}
+
+//------------------------------------------------------------------------
+Message &Maildir::iterator::operator *(void)
+{
+ return curMessage();
+}
+
+//------------------------------------------------------------------------
+void Maildir::iterator::operator ++(void)
+{
+ ++i;
+ ++sqnr;
+ reposition();
+}
+
+//------------------------------------------------------------------------
+bool Maildir::iterator::operator ==(const BaseIterator &a) const
+{
+ const iterator *b = dynamic_cast<const iterator *>(&a);
+ return b ? (i == b->i) : false;
+}
+
+//------------------------------------------------------------------------
+bool Maildir::iterator::operator !=(const BaseIterator &a) const
+{
+ return !((*this) == a);
+}
+
+//------------------------------------------------------------------------
+void Maildir::iterator::reposition(void)
+{
+ for (;;) {
+ if (i == mailbox->messages.end())
+ break;
+
+ Message &message = curMessage();
+ if ((mod & SKIP_EXPUNGED) && message.isExpunged()) {
+ ++i;
+ continue;
+ }
+
+ bool inset = false;
+ if (mod & SQNR_MODE) {
+ if (bset.isInSet(sqnr) || (!bset.isLimited() && sqnr == sqnrmax))
+ inset = true;
+ } else {
+ if (bset.isInSet(message.getUID()) || (!bset.isLimited() && message.getUID() == uidmax))
+ inset = true;
+ }
+
+ if (!inset) {
+ ++i;
+ if (!message.isExpunged())
+ ++sqnr;
+ continue;
+ }
+
+ break;
+ }
+}
+
+//------------------------------------------------------------------------
+Mailbox::iterator Maildir::begin(const SequenceSet &bset,
+ unsigned int mod) const
+{
+ beginIterator = iterator((Maildir *)this, messages.begin(), bset, mod);
+ beginIterator.reposition();
+
+ return Mailbox::iterator(beginIterator);
+}
+
+//------------------------------------------------------------------------
+Mailbox::iterator Maildir::end(void) const
+{
+ endIterator = iterator((Maildir *)this, messages.end(),
+ endIterator.bset, endIterator.mod);
+ return Mailbox::iterator(endIterator);
+}
+
+//------------------------------------------------------------------------
+void Maildir::iterator::erase(void)
+{
+ MessageMap::iterator iter = i;
+ ++iter;
+
+ MaildirMessageCache::getInstance().removeStatus(&curMessage());
+ mailbox->index.remove(i->second.getUnique());
+ mailbox->messages.erase(i);
+
+ i = iter;
+ reposition();
+}
+
+//------------------------------------------------------------------------
+Maildir::Maildir(void) : Mailbox()
+{
+ firstscan = true;
+ cacheRead = false;
+ uidvalidity = 0;
+ uidnext = 1;
+ selected = false;
+ oldrecent = 0;
+ oldexists = 0;
+}
+
+//------------------------------------------------------------------------
+Maildir::~Maildir(void)
+{
+}
+
+//------------------------------------------------------------------------
+void Maildir::setPath(const string &path_in)
+{
+ path = path_in;
+}
+
+//------------------------------------------------------------------------
+bool Maildir::getUpdates(bool doscan, unsigned int type,
+ PendingUpdates &updates, bool forceScan)
+{
+ if (doscan && scan(forceScan) != Success)
+ return false;
+
+ unsigned int exists = 0;
+ unsigned int recent = 0;
+ bool displayExists = false;
+
+ // count messages, find recent
+ if (!readOnly && (type & PendingUpdates::EXPUNGE)) {
+ Mailbox::iterator i = begin(SequenceSet::all(),
+ INCLUDE_EXPUNGED | SQNR_MODE);
+
+ while (i != end()) {
+ Message &message = *i;
+
+ if (message.isExpunged()) {
+ updates.addExpunged(i.getSqnr());
+ i.erase();
+ mailboxchanged = true;
+ displayExists = true;
+ } else
+ ++i;
+ }
+ }
+
+ Mailbox::iterator i = begin(SequenceSet::all(),
+ INCLUDE_EXPUNGED | SQNR_MODE);
+ for (; i != end(); ++i) {
+ Message & message = *i;
+ // at this point, there is a message that is not expunged
+ ++exists;
+ if (message.getStdFlags() & Message::F_RECENT) ++recent;
+ }
+
+ if (displayExists || exists != oldexists)
+ updates.setExists(oldexists = exists);
+
+ if (recent != oldrecent)
+ updates.setRecent(oldrecent = recent);
+
+ if (type & PendingUpdates::FLAGS) {
+ Mailbox::iterator i = begin(SequenceSet::all(), SQNR_MODE);
+ for (; i != end(); ++i) {
+ Message &message = *i;
+ if (message.hasFlagsChanged()) {
+ int flags = message.getStdFlags();
+
+ updates.addFlagUpdates(i.getSqnr(), message.getUID(), flags,
+ message.getCustomFlags());
+
+ message.setFlagsUnchanged();
+ }
+ }
+ }
+
+ return true;
+}
+
+//------------------------------------------------------------------------
+bool Maildir::isMailbox(const std::string &s_in) const
+{
+ struct stat mystat;
+
+ return ((stat((s_in + "/cur").c_str(), &mystat) == 0
+ && S_ISDIR(mystat.st_mode))
+ && (stat((s_in + "/new").c_str(), &mystat) == 0
+ && S_ISDIR(mystat.st_mode))
+ && (stat((s_in + "/tmp").c_str(), &mystat) == 0
+ && S_ISDIR(mystat.st_mode)));
+}
+
+//------------------------------------------------------------------------
+const std::string Maildir::getTypeName(void) const
+{
+ return "Maildir";
+}
+
+//------------------------------------------------------------------------
+void Maildir::bumpUidValidity(const string &s_in) const
+{
+ unlink((s_in + "/bincimap-uidvalidity").c_str());
+ unlink((s_in + "/bincimap-cache").c_str());
+}
+
+//------------------------------------------------------------------------
+bool Maildir::isMarked(const std::string &s_in) const
+{
+ DIR *dirp = opendir((s_in + "/new").c_str());
+ if (dirp == 0) return false;
+
+ struct dirent *direntp;
+ while ((direntp = readdir(dirp)) != 0) {
+ string s = direntp->d_name;
+ if (s[0] != '.'
+ && s.find('/') == string::npos
+ && s.find(':') == string::npos) {
+ closedir(dirp);
+ return true;
+ }
+ }
+
+ closedir(dirp);
+ return false;
+}
+
+//------------------------------------------------------------------------
+unsigned int Maildir::getStatusID(const string &path) const
+{
+ unsigned int statusid = 0;
+ struct stat mystat;
+ if (stat((path + "/new/").c_str(), &mystat) == 0)
+ statusid = mystat.st_ctime;
+
+ if (stat((path + "/cur/").c_str(), &mystat) == 0)
+ statusid += mystat.st_ctime;
+
+ if (stat((path + "/bincimap-cache").c_str(), &mystat) == 0)
+ statusid += mystat.st_ctime;
+
+ return statusid;
+}
+
+//------------------------------------------------------------------------
+bool Maildir::getStatus(const string &path, Status &s) const
+{
+ unsigned int messages = 0;
+ unsigned int unseen = 0;
+ unsigned int recent = 0;
+
+ unsigned int readUidValidity;
+ unsigned int readUidNext = 1;
+ map<string, bool> mincache;
+
+ const string cachefilename = path + "/bincimap-cache";
+ FILE *fp = fopen(cachefilename.c_str(), "r");
+ if (fp) do {
+ char inputBuffer[512];
+ if (!fgets(inputBuffer, sizeof(inputBuffer), fp)) {
+ fclose(fp);
+ return false;
+ }
+
+ // terminate the buffer
+ inputBuffer[sizeof(inputBuffer) - 1] = '\0';
+
+ char cacheFileVersionBuffer[512];
+
+ if (sscanf(inputBuffer, "%s %u %u", cacheFileVersionBuffer,
+ &readUidValidity, &readUidNext) != 3
+ || strcmp(cacheFileVersionBuffer, BINC_CACHE) != 0) {
+ fclose(fp);
+ readUidValidity = 0;
+ readUidNext = 1;
+ mincache.clear();
+ break;
+ }
+
+ unsigned int readUID;
+ unsigned int readSize;
+ unsigned int readInternalDate;
+ char readUnique[512];
+ while (fgets(inputBuffer, sizeof(inputBuffer), fp)) {
+ inputBuffer[sizeof(inputBuffer) - 1] = '\0';
+ if (sscanf(inputBuffer, "%u %u %u %s", &readUID,
+ &readInternalDate, &readSize, readUnique) != 4) {
+ fclose(fp);
+ readUidValidity = 0;
+ readUidNext = 1;
+ mincache.clear();
+ break;
+ }
+
+ mincache[readUnique] = true;
+ }
+
+ fclose(fp);
+
+ s.setUidValidity(readUidValidity < 1 ? time(0) : readUidValidity);
+ } while (0); else {
+ s.setUidValidity(time(0));
+ }
+
+ // Scan new
+ DIR *dirp = opendir((path + "/new").c_str());
+ if (dirp == 0) return false;
+
+ struct dirent *direntp;
+ while ((direntp = readdir(dirp)) != 0) {
+ const string filename = direntp->d_name;
+ if (filename[0] == '.'
+ || filename.find(':') != string::npos
+ || filename.find('/') != string::npos)
+ continue;
+
+ ++recent;
+ ++readUidNext;
+ ++unseen;
+ ++messages;
+ }
+
+ closedir(dirp);
+
+ // Scan cur
+ if ((dirp = opendir((path + "/cur").c_str())) == 0)
+ return false;
+
+ while ((direntp = readdir(dirp)) != 0) {
+ const string dname = direntp->d_name;
+ if (dname[0] == '.')
+ continue;
+
+ ++messages;
+
+ // Add to unseen if it doesn't have the seen flag or if it has no
+ // flags.
+ const string::size_type pos = dname.find(':');
+ if (pos != string::npos) {
+ if (mincache.find(dname.substr(0, pos)) == mincache.end()) {
+ ++recent;
+ ++readUidNext;
+ }
+
+ if (dname.substr(pos).find('S') == string::npos)
+ ++unseen;
+ } else {
+ if (mincache.find(dname) == mincache.end()) {
+ ++recent;
+ ++readUidNext;
+ }
+
+ ++unseen;
+ }
+ }
+
+ closedir(dirp);
+
+ s.setRecent(recent);
+ s.setMessages(messages);
+ s.setUnseen(unseen);
+ s.setUidNext(uidnext);
+
+ return true;
+}
+
+//------------------------------------------------------------------------
+unsigned int Maildir::getMaxUid(void) const
+{
+ MessageMap::const_iterator i = messages.end();
+ if (i == messages.begin())
+ return 0;
+
+ --i;
+ for (;;) {
+ const MaildirMessage &message = i->second;
+ if (message.isExpunged()) {
+ if (i == messages.begin())
+ return 0;
+ --i;
+ } else {
+ return message.getUID();
+ }
+ }
+
+ return 0;
+}
+
+//------------------------------------------------------------------------
+unsigned int Maildir::getMaxSqnr(void) const
+{
+ int sqnr = messages.size();
+ MessageMap::const_iterator i = messages.end();
+ if (i == messages.begin())
+ return 0;
+
+ --i;
+ for (;;) {
+ const MaildirMessage &message = i->second;
+ if (message.isExpunged()) {
+ if (i == messages.begin())
+ return 0;
+ --sqnr;
+ --i;
+ } else {
+ return sqnr;
+ }
+ }
+
+ return 0;
+}
+
+//------------------------------------------------------------------------
+unsigned int Maildir::getUidValidity(void) const
+{
+ return uidvalidity;
+}
+
+//------------------------------------------------------------------------
+unsigned int Maildir::getUidNext(void) const
+{
+ return uidnext;
+}
+
+//------------------------------------------------------------------------
+Message *Maildir::createMessage(const string &mbox, time_t idate)
+{
+ string sname = mbox + "/tmp/bincimap-XXXXXX";
+ char *safename = strdup(sname.c_str());
+
+ int fd = mkstemp(safename);
+ if (fd == -1) {
+ setLastError("Unable to create safe name.");
+ return 0;
+ }
+
+ string safenameStr = safename;
+ delete[] safename;
+
+ MaildirMessage message(*this);
+
+ message.setFile(fd);
+ message.setSafeName(safenameStr);
+ message.setInternalDate(idate);
+
+ newMessages.push_back(message);
+ vector<MaildirMessage>::iterator i = newMessages.end();
+ --i;
+ return &(*i);
+}
+
+//------------------------------------------------------------------------
+bool Maildir::commitNewMessages(const string &mbox)
+{
+ Session &session = Session::getInstance();
+
+ vector<MaildirMessage>::iterator i = newMessages.begin();
+ map<MaildirMessage *, string> committedMessages;
+
+ struct timeval youngestFile = {0, 0};
+
+ bool abort = false;
+ while (!abort && i != newMessages.end()) {
+ MaildirMessage &m = *i;
+
+ if (m.getInternalFlags() & MaildirMessage::Committed) {
+ ++i;
+ continue;
+ }
+
+ m.setInternalFlag(MaildirMessage::Committed);
+
+ string safeName = m.getSafeName();
+
+ for (int attempt = 0; !abort && attempt < 1000; ++attempt) {
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ youngestFile = tv;
+
+ // Generate Maildir conformant file name
+ BincStream ssid;
+ ssid << (int) tv.tv_sec << "."
+ << "R" << (int) rand()
+ << "M" << (int) tv.tv_usec
+ << "P" << (int) session.getPid()
+ << "Q" << numDeliveries++
+ << "." << session.getEnv("TCPLOCALHOST");
+
+ BincStream ss;
+ ss << mbox << "/new/" << ssid.str();
+
+ string fileName = ss.str();
+
+ if (link(safeName.c_str(), fileName.c_str()) == 0) {
+ unlink(safeName.c_str());
+ m.setInternalDate(tv.tv_sec);
+ m.setUnique(ssid.str());
+ m.setUID(0);
+ committedMessages[&m] = fileName;
+ break;
+ }
+
+ if (errno == EEXIST)
+ continue;
+
+ bincWarning << "link(" << toImapString(safeName) << ", "
+ << toImapString(fileName) << ") failed: "
+ << strerror(errno) << endl;
+
+ session.setResponseCode("TRYCREATE");
+ session.setLastError("failed, internal error.");
+ abort = true;
+ break;
+ }
+
+ ++i;
+ }
+
+ // abort means to make an attempt to restore the mailbox to
+ // its original state.
+ if (abort) {
+ // Fixme: Messages that are in committedMessages should be skipped
+ // here.
+ for (i = newMessages.begin(); i != newMessages.end(); ++i)
+ unlink((*i).getSafeName().c_str());
+
+ map<MaildirMessage *, string>::const_iterator j
+ = committedMessages.begin();
+ while (j != committedMessages.end()) {
+ if (unlink(j->second.c_str()) != 0) {
+ if (errno == ENOENT) {
+ // FIXME: The message was probably moves away from new/ by
+ // another IMAP session.
+ bincWarning << "error rollbacking after failed commit to "
+ << toImapString(mbox) << ", failed to unlink "
+ << toImapString(j->second)
+ << ": " << strerror(errno) << endl;
+ } else {
+ bincWarning << "error rollbacking after failed commit to "
+ << toImapString(mbox) << ", failed to unlink "
+ << toImapString(j->second)
+ << ": " << strerror(errno) << endl;
+ newMessages.clear();
+ return false;
+ }
+ }
+
+ ++j;
+ }
+
+ newMessages.clear();
+ return false;
+ }
+
+ // cover the extremely unlikely event that another concurrent
+ // Maildir accessor has just made a file with the same timestamp and
+ // random number by spinning until the timestamp has changed before
+ // moving the message into cur.
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ while (tv.tv_sec == youngestFile.tv_sec
+ && tv.tv_usec == youngestFile.tv_usec) {
+ gettimeofday(&tv, 0);
+ }
+
+ map<MaildirMessage *, string>::const_iterator j
+ = committedMessages.begin();
+ for (;j != committedMessages.end(); ++j) {
+ string basename = j->second.substr(j->second.rfind('/') + 1);
+
+ int flags = j->first->getStdFlags();
+ string flagStr;
+ if (flags & Message::F_DRAFT) flagStr += "D";
+ if (flags & Message::F_FLAGGED) flagStr += "F";
+ if (flags & Message::F_ANSWERED) flagStr += "R";
+ if (flags & Message::F_SEEN) flagStr += "S";
+ if (flags & Message::F_DELETED) flagStr += "T";
+
+ string dest = mbox + "/cur/" + basename + ":2," + flagStr;
+ if (rename(j->second.c_str(), dest.c_str()) == 0)
+ continue;
+
+ if (errno != ENOENT)
+ bincWarning << "when setting flags on: " << j->second
+ << ": " << strerror(errno) << endl;
+ }
+
+ committedMessages.clear();
+ return true;
+}
+
+//------------------------------------------------------------------------
+bool Maildir::rollBackNewMessages(void)
+{
+ vector<MaildirMessage>::const_iterator i = newMessages.begin();
+ // Fixme: Messages that are in committedMessages should be skipped
+ // here.
+ for (; i != newMessages.end(); ++i)
+ unlink((*i).getSafeName().c_str());
+
+ newMessages.clear();
+ return true;
+}
+
+//------------------------------------------------------------------------
+bool Maildir::fastCopy(Message &m, Mailbox &desttype,
+ const std::string &destname)
+{
+ // At this point, fastCopy is broken because the creation time is
+ // equal for the two clones. The new clone must have a new creation
+ // time. Symlinks are a possibility, but they break if other Maildir
+ // accessors rename mailboxes.
+ // return false;
+
+ Session &session = Session::getInstance();
+
+ MaildirMessage *message = dynamic_cast<MaildirMessage *>(&m);
+ if (!message)
+ return false;
+
+ string mfilename = message->getFileName();
+ if (mfilename == "")
+ return false;
+
+ Maildir *mailbox = dynamic_cast<Maildir *>(&desttype);
+ if (!mailbox)
+ return false;
+
+ for (int attempt = 0; attempt < 1000; ++attempt) {
+
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+
+ // Generate Maildir conformant file name
+ BincStream ssid;
+ ssid << (int) tv.tv_sec << "."
+ << "R" << (int) rand()
+ << "M" << (int) tv.tv_usec
+ << "P" << (int) session.getPid()
+ << "Q" << numDeliveries++
+ << "." << session.getEnv("TCPLOCALHOST");
+
+ BincStream ss;
+ ss << destname << "/tmp/" << ssid.str();
+
+ string fileName = ss.str();
+
+ if (link((path + "/cur/" + mfilename).c_str(), fileName.c_str()) == 0) {
+ MaildirMessage newmess = *message;
+ newmess.setSafeName(fileName);
+ newmess.setUnique(ssid.str());
+ newmess.setInternalDate((time_t) tv.tv_sec);
+ newmess.setUID(0);
+ newMessages.push_back(newmess);
+ break;
+ }
+
+ if (errno == EEXIST)
+ continue;
+
+ bincWarning << "Warning: link("
+ << toImapString(path + "/cur/" + mfilename)
+ << ", " << toImapString(fileName) << ") failed: "
+ << strerror(errno) << endl;
+
+ session.setResponseCode("TRYCREATE");
+ session.setLastError("failed, internal error.");
+ return false;
+ }
+
+ return true;
+}
+
+//------------------------------------------------------------------------
+MaildirMessage *Maildir::get(const std::string &id)
+{
+ MaildirIndexItem *item = index.find(id);
+ if (!item)
+ return 0;
+
+ unsigned int uid = item->uid;
+ if (messages.find(uid) == messages.end())
+ return 0;
+
+ return &messages.find(uid)->second;
+}
+
+//------------------------------------------------------------------------
+void Maildir::add(MaildirMessage &m)
+{
+ MessageMap::iterator it = messages.find(m.getUID());
+ if (it != messages.end())
+ messages.erase(it);
+ messages.insert(make_pair(m.getUID(), m));
+ index.insert(m.getUnique(), m.getUID());
+}
+
+//------------------------------------------------------------------------
+unsigned int MaildirIndex::getSize(void) const
+{
+ return idx.size();
+}
+
+//------------------------------------------------------------------------
+void MaildirIndex::insert(const string &unique, unsigned int uid,
+ const string &fileName)
+{
+ if (idx.find(unique) == idx.end()) {
+ MaildirIndexItem item;
+ item.uid = uid;
+ item.fileName = fileName;
+ idx[unique] = item;
+ } else {
+ MaildirIndexItem &item = idx[unique];
+ if (uid != 0) item.uid = uid;
+ if (fileName != "") item.fileName = fileName;
+ }
+}
+
+//------------------------------------------------------------------------
+void MaildirIndex::remove(const string &unique)
+{
+ map<string, MaildirIndexItem>::iterator it = idx.find(unique);
+ if (it != idx.end())
+ idx.erase(it);
+}
+
+//------------------------------------------------------------------------
+MaildirIndexItem *MaildirIndex::find(const string &unique)
+{
+ map<string, MaildirIndexItem>::iterator it = idx.find(unique);
+ if (it != idx.end())
+ return &it->second;
+
+ return 0;
+}
+
+//------------------------------------------------------------------------
+void MaildirIndex::clear(void)
+{
+ idx.clear();
+}
+
+//------------------------------------------------------------------------
+void MaildirIndex::clearUids(void)
+{
+ map<string, MaildirIndexItem>::iterator it = idx.begin();
+ for (; it != idx.end(); ++it)
+ it->second.uid = 0;
+}
+
+//------------------------------------------------------------------------
+void MaildirIndex::clearFileNames(void)
+{
+ map<string, MaildirIndexItem>::iterator it = idx.begin();
+ for (; it != idx.end(); ++it)
+ it->second.fileName = "";
+}