1 /*
2 ** Copyright 2002-2004, Double Precision Inc.
3 **
4 ** See COPYING for distribution information.
5 */
6 #include "libmail_config.h"
7 #include "driver.H"
8 #include "file.H"
9 #include "misc.H"
10 #include "mbox.H"
11 #include "mboxopen.H"
12 #include "mboxread.H"
13 #include "mboxexpunge.H"
14 #include "mboxgetmessage.H"
15 #include "search.H"
16 #include "copymessage.H"
17 #include "mboxmultilock.H"
18 #include "liblock/config.h"
19 #include "liblock/mail.h"
20 
21 #include "rfc822/rfc822.h"
22 #include "rfc2045/rfc2045.h"
23 
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <pwd.h>
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <time.h>
30 #include <errno.h>
31 #include <signal.h>
32 #include <ctype.h>
33 
34 using namespace std;
35 
36 /////////////////////////////////////////////////////////////////////////////
37 
38 LIBMAIL_START
39 
open_inbox(mail::account * & accountRet,mail::account::openInfo & oi,mail::callback & callback,mail::callback::disconnect & disconnectCallback)40 static bool open_inbox(mail::account *&accountRet,
41 		       mail::account::openInfo &oi,
42 		       mail::callback &callback,
43 		       mail::callback::disconnect &disconnectCallback)
44 {
45 	if (oi.url.substr(0, 6) != "inbox:")
46 		return false;
47 
48 	accountRet=new mail::mbox(true, oi.url.substr(6),
49 				  callback,
50 				  disconnectCallback);
51 
52 	return true;
53 }
54 
open_mbox(mail::account * & accountRet,mail::account::openInfo & oi,mail::callback & callback,mail::callback::disconnect & disconnectCallback)55 static bool open_mbox(mail::account *&accountRet,
56 		      mail::account::openInfo &oi,
57 		      mail::callback &callback,
58 		      mail::callback::disconnect &disconnectCallback)
59 {
60 	if (oi.url.substr(0, 5) != "mbox:")
61 		return false;
62 
63 	accountRet=new mail::mbox(false, oi.url.substr(5),
64 				  callback,
65 				  disconnectCallback);
66 
67 	return true;
68 }
69 
mbox_remote(string url,bool & flag)70 static bool mbox_remote(string url, bool &flag)
71 {
72 	if (url.substr(0, 6) == "inbox:" ||
73 	    url.substr(0, 5) == "mbox:")
74 	{
75 		flag=false;
76 		return true;
77 	}
78 
79 	return false;
80 }
81 
82 driver inbox_driver= { &open_inbox, &mbox_remote };
83 driver mbox_driver= { &open_mbox, &mbox_remote };
84 
85 LIBMAIL_END
86 
87 /////////////////////////////////////////////////////////////////////////////
88 
task(mail::mbox & mboxArg)89 mail::mbox::task::task(mail::mbox &mboxArg)
90 	: mboxAccount(mboxArg)
91 {
92 }
93 
~task()94 mail::mbox::task::~task()
95 {
96 }
97 
done()98 void mail::mbox::task::done()
99 {
100 	if (mboxAccount.tasks.front() != this)
101 		LIBMAIL_THROW("Assertion failed: mail::mbox::task::done");
102 
103 	mboxAccount.tasks.pop();
104 	delete this;
105 }
106 
107 /////////////////////////////////////////////////////////////////////////////
108 
lock()109 mail::mbox::lock::lock()
110 	: ll(NULL), fd(-1), readOnlyLock(false)
111 {
112 }
113 
lock(string filename)114 mail::mbox::lock::lock(string filename)
115 	: ll(ll_mail_alloc(filename.c_str())), fd(-1), readOnlyLock(false)
116 {
117 	if (!ll)
118 		LIBMAIL_THROW("Out of memory.");
119 }
120 
copy()121 mail::mbox::lock *mail::mbox::lock::copy()
122 {
123 	lock *c=new lock();
124 
125 	if (!c)
126 		LIBMAIL_THROW(strerror(errno));
127 
128 	c->ll=ll;
129 	c->fd=fd;
130 	c->readOnlyLock=readOnlyLock;
131 
132 	ll=NULL;
133 	fd=-1;
134 
135 	return c;
136 }
137 
~lock()138 mail::mbox::lock::~lock()
139 {
140 	if (fd >= 0)
141 		close(fd);
142 	if (ll)
143 		ll_mail_free(ll);
144 }
145 
operator()146 bool mail::mbox::lock::operator()(bool readOnly)
147 {
148 	if (fd >= 0)
149 		return true;
150 
151 	if (!ll)
152 	{
153 		errno=ENOENT;
154 		return false;
155 	}
156 
157 	// Create a dot-lock file, first.
158 
159 	if (ll_mail_lock(ll) < 0 && !readOnly)
160 		return false;
161 
162 	// Now, open the file.
163 
164 	fd=readOnly ? ll_mail_open_ro(ll):ll_mail_open(ll);
165 	readOnlyLock=readOnly;
166 
167 	return fd >= 0;
168 }
169 
170 /////////////////////////////////////////////////////////////////////////////
171 
172 
TimedTask(mail::mbox & mboxArg,mail::callback & callbackArg,int timeoutArg)173 mail::mbox::TimedTask::TimedTask(mail::mbox &mboxArg,
174 				 mail::callback &callbackArg,
175 				 int timeoutArg)
176 	: task(mboxArg), timeout(time(NULL) + timeoutArg), nextTry(0),
177 	  callback(callbackArg)
178 {
179 }
180 
~TimedTask()181 mail::mbox::TimedTask::~TimedTask()
182 {
183 }
184 
doit(int & timeout)185 void mail::mbox::TimedTask::doit(int &timeout)
186 {
187 	time_t now=time(NULL);
188 
189 	if (nextTry && now < nextTry)
190 	{
191 		int nms=(nextTry - now) * 1000;
192 
193 		if (timeout > nms)
194 			timeout=nms;
195 		return;
196 	}
197 
198 	timeout=0;
199 
200 	if (doit())
201 		return;
202 
203 	nextTry = now + 5; // Try again in 5 seconds.
204 
205 	if (now >= timeout)
206 		timedOut();
207 }
208 
fail(string errmsg)209 void mail::mbox::TimedTask::fail(string errmsg)
210 {
211 	callback.fail(errmsg);
212 	done();
213 }
214 
timedOut()215 void mail::mbox::TimedTask::timedOut()
216 {
217 	callback.fail("Operation timed out - mail folder in use.");
218 	done();
219 }
220 
disconnected()221 void mail::mbox::TimedTask::disconnected()
222 {
223 	callback.fail("Operation cancelled.");
224 }
225 
226 /////////////////////////////////////////////////////////////////////////////
227 
resumed()228 void mail::mbox::resumed()
229 {
230 }
231 
handler(vector<pollfd> & fds,int & timeout)232 void mail::mbox::handler(vector<pollfd> &fds, int &timeout)
233 {
234 	// Our job is to do the first task at hand, as simple as that.
235 
236 	if (!tasks.empty())
237 		tasks.front()->doit(timeout);
238 }
239 
installTask(task * t)240 void mail::mbox::installTask(task *t)
241 {
242 	if (t == NULL)
243 		LIBMAIL_THROW("Out of memory.");
244 
245 	try {
246 		tasks.push(t);
247 	} catch (...) {
248 		delete t;
249 		LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
250 	}
251 }
252 
mbox(bool magicInboxArg,string folderRoot,mail::callback & callback,mail::callback::disconnect & disconnect_callback)253 mail::mbox::mbox(bool magicInboxArg,
254 		       string folderRoot, mail::callback &callback,
255 		       mail::callback::disconnect &disconnect_callback)
256 	: mail::account(disconnect_callback),
257 	  calledDisconnected(false),
258 	  magicInbox(magicInboxArg),
259 	  inboxFolder("INBOX", *this),
260 	  hierarchyFolder("", *this),
261 	  currentFolderReadOnly(false),
262 	  folderSavedSize(0),
263 	  folderSavedTimestamp(0),
264 	  multiLockLock(NULL),
265 	  folderDirty(false),
266 	  newMessages(false),
267 	  cachedMessageRfcp(NULL),
268 	  cachedMessageFp(NULL),
269 	  currentFolderCallback(NULL)
270 {
271 	sigset_t ss;
272 
273 	// Ignore SIGUSR2 from c-client
274 
275 	sigemptyset(&ss);
276 	sigaddset(&ss, SIGUSR2);
277 	sigprocmask(SIG_BLOCK, &ss, NULL);
278 
279 	const char *m=getenv("MAIL");
280 
281 	string h=mail::homedir();
282 	struct passwd *pw=getpwuid(getuid());
283 
284 	if (!pw || h.size() == 0)
285 	{
286 		callback.fail("Cannot find my home directory!");
287 		return;
288 	}
289 
290 
291 	inboxMailboxPath=h + "/Inbox";
292 
293 	// Figure out the mail spool directory.
294 
295 	if (m && *m)
296 		inboxSpoolPath=m;
297 	else
298 	{
299 		static const char *spools[]={"/var/spool/mail", "/var/mail",
300 					     "/usr/spool/mail", "/usr/mail",
301 					     0};
302 
303 		size_t i;
304 
305 		for (i=0; spools[i]; i++)
306 			if (access(spools[i], X_OK) == 0)
307 			{
308 				inboxSpoolPath=string(spools[i]) + "/"
309 					+ pw->pw_name;
310 				break;
311 			}
312 
313 		if (!spools[i])
314 		{
315 			callback.fail("Cannot determine your system mailbox location,");
316 			return;
317 		}
318 	}
319 
320 	if (folderRoot.size() > 0 && folderRoot[0] != '/')
321 		folderRoot=h + "/" + folderRoot;
322 
323 	rootPath=folderRoot;
324 
325 	// Initialize the top level folder.
326 
327 	hierarchyFolder.path=folderRoot;
328 	hierarchyFolder.name=folder::defaultName(folderRoot);
329 
330 	// First time through, create the top level folder directory, if
331 	// necessary.
332 
333 	if (magicInboxArg)
334 		mkdir(folderRoot.c_str(), 0700);
335 
336 	struct stat stat_buf;
337 
338 	if (stat(folderRoot.c_str(), &stat_buf) == 0 &&
339 	    S_ISDIR(stat_buf.st_mode))
340 	{
341 		hierarchyFolder.hasMessages(false);
342 		hierarchyFolder.hasSubFolders(true);
343 	}
344 	else
345 	{
346 		hierarchyFolder.hasMessages(true);
347 		hierarchyFolder.hasSubFolders(false);
348 	}
349 
350 	callback.success("Mail folder opened.");
351 }
352 
~mbox()353 mail::mbox::~mbox()
354 {
355 	resetFolder();
356 
357 	while (!tasks.empty())
358 	{
359 		tasks.front()->disconnected();
360 		tasks.front()->done();
361 	}
362 
363 	if (!calledDisconnected)
364 	{
365 		calledDisconnected=true;
366 		disconnect_callback.disconnected("");
367 	}
368 }
369 
370 //
371 // General cleanup when the current folder is closed.
372 //
373 
resetFolder()374 void mail::mbox::resetFolder()
375 {
376 	if (multiLockLock)
377 	{
378 		delete multiLockLock;
379 		multiLockLock=NULL;
380 	}
381 
382 	if (cachedMessageRfcp)
383 	{
384 		rfc2045_free(cachedMessageRfcp);
385 		cachedMessageRfcp=NULL;
386 	}
387 
388 	if (cachedMessageFp)
389 	{
390 		fclose(cachedMessageFp);
391 		cachedMessageFp=NULL;
392 	}
393 
394 	cachedMessageUid="";
395 
396 	folderMessageIndex.clear();
397 	uidmap.clear();
398 	folderDirty=false;
399 	newMessages=false;
400 }
401 
logout(mail::callback & callback)402 void mail::mbox::logout(mail::callback &callback)
403 {
404 	if (!folderDirty)
405 	{
406 		callback.success("Mail folder closed.");
407 		return;
408 	}
409 
410 	// Something dirty needs to be saved.
411 
412 	installTask(new ExpungeTask(*this, callback, false, NULL));
413 }
414 
checkNewMail(class mail::callback & callback)415 void mail::mbox::checkNewMail(class mail::callback &callback)
416 {
417 	if (currentFolder.size() == 0)
418 	{
419 		callback.success("OK");	// Nothing's opened.
420 		return;
421 	}
422 
423 	installTask(new CheckNewMailTask(*this, currentFolder,
424 					 callback, NULL));
425 }
426 
checkNewMail()427 void mail::mbox::checkNewMail()
428 {
429 	// The real folder contents have been updated, now compare against
430 	// the folder contents seen by the app, and create entries for
431 	// new msgs
432 
433 	set<string> uidSet;
434 
435 	// Create an index of all existing msgs seen by the app.
436 
437 	{
438 		vector<mail::messageInfo>::iterator b, e;
439 
440 		b=index.begin();
441 		e=index.end();
442 
443 		while (b != e)
444 			uidSet.insert( (*b++).uid );
445 	}
446 
447 	// Step through the real folder index, see what's new.
448 
449 	{
450 		vector<mboxMessageIndex>::iterator b, e;
451 
452 		b=folderMessageIndex.begin();
453 		e=folderMessageIndex.end();
454 
455 		while (b != e)
456 		{
457 			mail::messageInfo i=(*b++).tag.getMessageInfo();
458 
459 			if (uidSet.count(i.uid))
460 				continue;
461 
462 			index.push_back(i);
463 			newMessages=true;
464 		}
465 	}
466 }
467 
hasCapability(string capability)468 bool mail::mbox::hasCapability(string capability)
469 {
470 	if (capability == LIBMAIL_SINGLEFOLDER)
471 		return hierarchyFolder.hasMessages();
472 
473 	return false;
474 }
475 
getCapability(string name)476 string mail::mbox::getCapability(string name)
477 {
478 	mail::upper(name);
479 
480 	if (name == LIBMAIL_SERVERTYPE)
481 	{
482 		return "mbox";
483 	}
484 
485 	if (name == LIBMAIL_SERVERDESCR)
486 	{
487 		return "Local mail folder";
488 	}
489 
490 	if (name == LIBMAIL_SINGLEFOLDER)
491 		return hierarchyFolder.hasMessages() ? "1":"";
492 
493 	return "";
494 }
495 
folderFromString(string s)496 mail::folder *mail::mbox::folderFromString(string s)
497 {
498 	string name="";
499 
500 	string::iterator b=s.begin(), e=s.end();
501 
502 	while (b != e)
503 	{
504 		if (*b == '\\')
505 		{
506 			b++;
507 
508 			if (b == e)
509 				break;
510 		} else if (*b == ':')
511 		{
512 			b++;
513 			break;
514 		}
515 
516 		name += *b++;
517 	}
518 
519 	// If the path component is relative, prepend home dir.
520 
521 	string path=string(b, e);
522 
523 	if (path.size() == 0)
524 		path=rootPath;
525 	else if (path[0] != '/' && path != "INBOX")
526 		path=rootPath + "/" + path;
527 
528 	return new folder(path, *this);
529 }
530 
readTopLevelFolders(mail::callback::folderList & callback1,class mail::callback & callback2)531 void mail::mbox::readTopLevelFolders(mail::callback::folderList &callback1,
532 				     class mail::callback &callback2)
533 {
534 	vector<const mail::folder *> folder_list;
535 
536 	if (magicInbox)
537 		folder_list.push_back( &inboxFolder );
538 
539 	if (rootPath.size() > 0)
540 		folder_list.push_back( &hierarchyFolder );
541 
542 	callback1.success(folder_list);
543 	callback2.success("OK");
544 }
545 
findFolder(string path,mail::callback::folderList & callback1,mail::callback & callback2)546 void mail::mbox::findFolder(string path,
547 			    mail::callback::folderList &callback1,
548 			    mail::callback &callback2)
549 {
550 	if (path.size() == 0)
551 		path=rootPath;
552 	else if (path[0] != '/' && path != "INBOX")
553 		path=rootPath + "/" + path;
554 
555 	folder *f=new folder(path, *this);
556 
557 	if (!f)
558 	{
559 		callback2.fail(path + ": " + strerror(errno));
560 		return;
561 	}
562 
563 	try {
564 		vector<const mail::folder *> folder_list;
565 
566 		folder_list.push_back(f);
567 
568 		callback1.success(folder_list);
569 		callback2.success("OK");
570 		delete f;
571 	} catch (...) {
572 		delete f;
573 		LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
574 	}
575 }
576 
readMessageAttributes(const vector<size_t> & messages,MessageAttributes attributes,mail::callback::message & callback)577 void mail::mbox::readMessageAttributes(const vector<size_t> &messages,
578 				       MessageAttributes attributes,
579 				       mail::callback::message
580 				       &callback)
581 {
582 	vector<size_t> messageCpy=messages;
583 
584 	// ARRIVALDATE can be handled here, for everything else use the
585 	// generic methods.
586 
587 	if (attributes & mail::account::ARRIVALDATE)
588 	{
589 		attributes &= ~mail::account::ARRIVALDATE;
590 
591 		vector<size_t>::iterator b=messageCpy.begin(),
592 			e=messageCpy.end();
593 
594 		MONITOR(mail::mbox);
595 
596 		while (b != e && !DESTROYED())
597 		{
598 			size_t n= *b++;
599 
600 			if (n >= index.size())
601 				continue;
602 
603 			string uid=index[n].uid;
604 
605 			callback
606 				.messageArrivalDateCallback(n,
607 							    uidmap.count(uid)
608 							    == 0 ? 0:
609 							    folderMessageIndex
610 							    [ uidmap.find(uid)
611 							      ->second]
612 							    .internalDate);
613 		}
614 
615 		if (DESTROYED() || attributes == 0)
616 		{
617 			callback.success("OK");
618 			return;
619 		}
620 	}
621 
622 	MultiLockRelease *mlock=new MultiLockRelease(*this, callback);
623 
624 	if (!mlock)
625 	{
626 		callback.fail(strerror(errno));
627 		return;
628 	}
629 
630 	try {
631 		MultiLockGenericAttributes *mr=
632 			new MultiLockGenericAttributes(*this,
633 							messageCpy,
634 							attributes,
635 							*mlock);
636 		if (!mr)
637 			LIBMAIL_THROW((strerror(errno)));
638 
639 		try {
640 			installTask(new MultiLock( *this, *mr ));
641 		} catch (...) {
642 			delete mr;
643 		}
644 	} catch (...) {
645 		delete mlock;
646 		LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
647 	}
648 }
649 
readMessageContent(const vector<size_t> & messages,bool peek,enum mail::readMode readType,mail::callback::message & callback)650 void mail::mbox::readMessageContent(const vector<size_t> &messages,
651 				    bool peek,
652 				    enum mail::readMode readType,
653 				    mail::callback::message &callback)
654 {
655 	MultiLockRelease *mlock=new MultiLockRelease(*this, callback);
656 
657 	if (!mlock)
658 	{
659 		callback.fail(strerror(errno));
660 		return;
661 	}
662 
663 	try {
664 		MultiLockGenericMessageRead *mr=
665 			new MultiLockGenericMessageRead(*this,
666 							messages,
667 							peek,
668 							readType,
669 							*mlock);
670 		if (!mr)
671 			LIBMAIL_THROW((strerror(errno)));
672 
673 		try {
674 			installTask(new MultiLock( *this, *mr ));
675 		} catch (...) {
676 			delete mr;
677 		}
678 	} catch (...) {
679 		delete mlock;
680 		LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
681 	}
682 }
683 
readMessageContent(size_t messageNum,bool peek,const mail::mimestruct & msginfo,enum mail::readMode readType,mail::callback::message & callback)684 void mail::mbox::readMessageContent(size_t messageNum,
685 				    bool peek,
686 				    const mail::mimestruct &msginfo,
687 				    enum mail::readMode readType,
688 				    mail::callback::message
689 				    &callback)
690 {
691 	genericReadMessageContent(this, this, messageNum, peek,
692 				  msginfo, readType,
693 				  callback);
694 }
695 
readMessageContentDecoded(size_t messageNum,bool peek,const mail::mimestruct & msginfo,mail::callback::message & callback)696 void mail::mbox::readMessageContentDecoded(size_t messageNum,
697 					   bool peek,
698 					   const mail::mimestruct &msginfo,
699 					   mail::callback::message
700 					   &callback)
701 {
702 	genericReadMessageContentDecoded(this, this, messageNum, peek,
703 					 msginfo,
704 					 callback);
705 }
706 
getFolderIndexSize()707 size_t mail::mbox::getFolderIndexSize()
708 {
709 	return index.size();
710 }
711 
getFolderIndexInfo(size_t n)712 mail::messageInfo mail::mbox::getFolderIndexInfo(size_t n)
713 {
714 	return n < index.size() ? index[n]:mail::messageInfo();
715 }
716 
saveFolderIndexInfo(size_t messageNum,const mail::messageInfo & info,mail::callback & callback)717 void mail::mbox::saveFolderIndexInfo(size_t messageNum,
718 				     const mail::messageInfo &info,
719 				     mail::callback &callback)
720 {
721 	MONITOR(mail::mbox);
722 
723 	if (messageNum < index.size())
724 	{
725 		folderDirty=true;
726 
727 #define DOFLAG(dummy1, field, dummy2) \
728 		(index[messageNum].field= info.field)
729 
730 		LIBMAIL_MSGFLAGS;
731 
732 #undef DOFLAG
733 
734 	}
735 
736 	callback.success(currentFolderReadOnly ?
737 			 "Folder opened in read-only mode.":
738 			 "Message updated.");
739 
740 	if (! DESTROYED() && messageNum < index.size()
741 	    && currentFolderCallback)
742 		currentFolderCallback->messageChanged(messageNum);
743 }
744 
updateFolderIndexFlags(const vector<size_t> & messages,bool doFlip,bool enableDisable,const mail::messageInfo & flags,mail::callback & callback)745 void mail::mbox::updateFolderIndexFlags(const vector<size_t> &messages,
746 					bool doFlip,
747 					bool enableDisable,
748 					const mail::messageInfo &flags,
749 					mail::callback &callback)
750 {
751 	vector<size_t>::const_iterator b, e;
752 
753 	b=messages.begin();
754 	e=messages.end();
755 
756 	size_t n=index.size();
757 
758 	while (b != e)
759 	{
760 		size_t i= *b++;
761 
762 		if (i < n)
763 		{
764 #define DOFLAG(dummy, field, dummy2) \
765 			if (flags.field) \
766 			{ \
767 				index[i].field=\
768 					doFlip ? !index[i].field\
769 					       : enableDisable; \
770 			}
771 
772 			LIBMAIL_MSGFLAGS;
773 #undef DOFLAG
774 		}
775 	}
776 
777 	folderDirty=true;
778 	b=messages.begin();
779 	e=messages.end();
780 
781 	MONITOR(mail::mbox);
782 
783 	while (!DESTROYED() && b != e)
784 	{
785 		size_t i= *b++;
786 
787 		if (i < n && currentFolderCallback)
788 			currentFolderCallback->messageChanged(i);
789 	}
790 
791 	callback.success(!DESTROYED() && currentFolderReadOnly ?
792 			 "Folder opened in read-only mode.":
793 			 "Message updated.");
794 }
795 
genericMarkRead(size_t messageNumber)796 void mail::mbox::genericMarkRead(size_t messageNumber)
797 {
798 	if (messageNumber < index.size() && index[messageNumber].unread)
799 	{
800 		index[messageNumber].unread=false;
801 		folderDirty=true;
802 		if (currentFolderCallback)
803 			currentFolderCallback->messageChanged(messageNumber);
804 	}
805 }
806 
updateFolderIndexInfo(mail::callback & callback)807 void mail::mbox::updateFolderIndexInfo(mail::callback &callback)
808 {
809 	if (currentFolder.size() == 0)
810 	{
811 		callback.success("Mail folder updated");
812 		return;
813 	}
814 
815 	installTask(new ExpungeTask(*this, callback, true, NULL));
816 }
817 
getFolderKeywordInfo(size_t messageNumber,set<string> & keywords)818 void mail::mbox::getFolderKeywordInfo(size_t messageNumber,
819 				      set<string> &keywords)
820 {
821 	keywords.clear();
822 
823 	if (messageNumber < index.size())
824 	{
825 		map<string, size_t>::iterator p=
826 			uidmap.find(index[messageNumber].uid);
827 
828 		if (p != uidmap.end())
829 		{
830 			folderMessageIndex[p->second].tag.getKeywords()
831 				.getFlags(keywords);
832 		}
833 	}
834 }
835 
836 
updateKeywords(const std::vector<size_t> & messages,const std::set<std::string> & keywords,bool setOrChange,bool changeTo,callback & cb)837 void mail::mbox::updateKeywords(const std::vector<size_t> &messages,
838 				const std::set<std::string> &keywords,
839 				bool setOrChange,
840 				// false: set, true: see changeTo
841 				bool changeTo,
842 				callback &cb)
843 {
844 	genericUpdateKeywords(messages, keywords,
845 			      setOrChange, changeTo, currentFolderCallback,
846 			      this, cb);
847 }
848 
genericProcessKeyword(size_t messageNumber,generic::updateKeywordHelper & helper)849 bool mail::mbox::genericProcessKeyword(size_t messageNumber,
850 				       generic::updateKeywordHelper &helper)
851 {
852 	if (messageNumber < index.size())
853 	{
854 		map<string, size_t>::iterator p=
855 			uidmap.find(index[messageNumber].uid);
856 
857 		if (p != uidmap.end())
858 		{
859 			folderDirty=true;
860 			return helper
861 				.doUpdateKeyword(folderMessageIndex[p->second]
862 						 .tag.getKeywords(),
863 						 keywordHashtable);
864 		}
865 	}
866 	return true;
867 }
868 
removeMessages(const std::vector<size_t> & messages,callback & cb)869 void mail::mbox::removeMessages(const std::vector<size_t> &messages,
870 				callback &cb)
871 {
872 	installTask(new ExpungeTask(*this, cb, true, &messages));
873 }
874 
copyMessagesTo(const vector<size_t> & messages,mail::folder * copyTo,mail::callback & callback)875 void mail::mbox::copyMessagesTo(const vector<size_t> &messages,
876 				mail::folder *copyTo,
877 				mail::callback &callback)
878 {
879 	mail::copyMessages::copy(this, messages, copyTo, callback);
880 }
881 
searchMessages(const class mail::searchParams & searchInfo,mail::searchCallback & callback)882 void mail::mbox::searchMessages(const class mail::searchParams &searchInfo,
883 				mail::searchCallback &callback)
884 {
885 	mail::searchMessages::search(callback, searchInfo, this);
886 }
887 
888 /////////////////////////////////////////////////////////////////////////////
889 //
890 // Generic message access implementation
891 
genericMessageRead(string uid,size_t messageNumber,bool peek,mail::readMode readType,mail::callback::message & callback)892 void mail::mbox::genericMessageRead(string uid,
893 				    size_t messageNumber,
894 				    bool peek,
895 				    mail::readMode readType,
896 				    mail::callback::message &callback)
897 {
898 	installTask(new GenericReadTask(*this, callback, uid, messageNumber,
899 					peek, readType));
900 }
901 
verifyUid(string uid,size_t & messageNumber,mail::callback & callback)902 bool mail::mbox::verifyUid(string uid, size_t &messageNumber,
903 			   mail::callback &callback)
904 {
905 	if (uidmap.count(uid) > 0)
906 	{
907 		if (messageNumber < index.size() &&
908 		    index[messageNumber].uid == uid)
909 			return true;
910 
911 		size_t n=index.size();
912 
913 		while (n)
914 		{
915 			if (index[--n].uid == uid)
916 			{
917 				messageNumber=n;
918 				return true;
919 			}
920 		}
921 	}
922 	callback.fail("Message no longer exists in the folder.");
923 	return false;
924 }
925 
genericMessageSize(string uid,size_t messageNumber,mail::callback::message & callback)926 void mail::mbox::genericMessageSize(string uid,
927 				    size_t messageNumber,
928 				    mail::callback::message &callback)
929 {
930 	if (!verifyUid(uid, messageNumber, callback))
931 		return;
932 
933 	size_t n=uidmap.find(uid)->second;
934 
935 	off_t s=folderMessageIndex[n].startingPos;
936 
937 	off_t e=n + 1 >= folderMessageIndex.size()
938 		? folderSavedSize:folderMessageIndex[n+1].startingPos;
939 
940 	callback.messageSizeCallback( messageNumber, e > s ? e-s:0);
941 	callback.success("OK");
942 }
943 
genericGetMessageFd(string uid,size_t messageNumber,bool peek,int & fdRet,mail::callback & callback)944 void mail::mbox::genericGetMessageFd(string uid,
945 				     size_t messageNumber,
946 				     bool peek,
947 				     int &fdRet,
948 				     mail::callback &callback)
949 {
950 	if (uid == cachedMessageUid && cachedMessageFp)
951 	{
952 		fdRet=fileno(cachedMessageFp);
953 		callback.success("OK");
954 		return;
955 	}
956 
957 	installTask(new GenericGetMessageTask(*this, callback,
958 					      uid, messageNumber,
959 					      peek,
960 					      &fdRet, NULL));
961 }
962 
genericGetMessageStruct(string uid,size_t messageNumber,struct rfc2045 * & structRet,mail::callback & callback)963 void mail::mbox::genericGetMessageStruct(string uid,
964 					 size_t messageNumber,
965 					 struct rfc2045 *&structRet,
966 					 mail::callback &callback)
967 {
968 	if (uid == cachedMessageUid && cachedMessageRfcp)
969 	{
970 		structRet=cachedMessageRfcp;
971 		callback.success("OK");
972 		return;
973 	}
974 
975 	installTask(new GenericGetMessageTask(*this, callback,
976 					      uid, messageNumber,
977 					      true,
978 					      NULL, &structRet));
979 }
980 
genericGetMessageFdStruct(string uid,size_t messageNumber,bool peek,int & fdRet,struct rfc2045 * & structret,mail::callback & callback)981 void mail::mbox::genericGetMessageFdStruct(string uid,
982 					   size_t messageNumber,
983 					   bool peek,
984 					   int &fdRet,
985 					   struct rfc2045 *&structret,
986 					   mail::callback &callback)
987 {
988 	if (uid == cachedMessageUid && cachedMessageRfcp &&
989 	    cachedMessageFp)
990 	{
991 		structret=cachedMessageRfcp;
992 		fdRet=fileno(cachedMessageFp);
993 		callback.success("OK");
994 		return;
995 	}
996 
997 	installTask(new GenericGetMessageTask(*this, callback,
998 					      uid, messageNumber,
999 					      peek,
1000 					      &fdRet, &structret));
1001 }
1002 
genericCachedUid(string uid)1003 bool mail::mbox::genericCachedUid(string uid)
1004 {
1005 	return uid == cachedMessageUid && cachedMessageRfcp;
1006 }
1007 
1008 /*
1009 ** Hack away at ctime ("Wed Sep 01 13:58:06 2002")
1010 ** in the From_ header until we end up with a timestamp
1011 */
1012 
fromCtime(string hdr)1013 static time_t fromCtime(string hdr)
1014 {
1015 	char mon[4];
1016 	int dom, h, m, s, y;
1017 	char tempbuf[100];
1018 
1019 	struct tm *tmptr;
1020 	time_t tv;
1021 	int mnum;
1022 
1023 	const char *p=hdr.c_str();
1024 
1025 	while (*p && !unicode_isspace((unsigned char)*p))
1026 		p++;
1027 
1028 	p++;
1029 
1030 	while (*p && !unicode_isspace((unsigned char)*p))
1031 		p++;
1032 
1033 
1034 	if (sscanf(p, "%*s %3s %d %d:%d:%d %d", mon, &dom,
1035 		   &h, &m, &s, &y)!=6)
1036 		return 0;
1037 
1038 	/* Some hackery to get the month number */
1039 
1040 	sprintf(tempbuf, "15 %s %d 12:00:00", mon, y);
1041 
1042 	if (rfc822_parsedate_chk(tempbuf, &tv))
1043 		return 0;
1044 
1045 	tmptr=localtime(&tv);
1046 	mnum=tmptr->tm_mon;
1047 
1048 	/* For real, this time */
1049 
1050 	sprintf(tempbuf, "%d %s %d 00:00:00", dom, mon, y);
1051 
1052 	if (rfc822_parsedate_chk(tempbuf, &tv))
1053 		return 0;
1054 
1055 	tmptr=localtime(&tv);
1056 
1057 	while ((tmptr=localtime(&tv))->tm_year + 1900 < y ||
1058 	       (tmptr->tm_year + 1900 == y && tmptr->tm_mon < mnum) ||
1059 	       (tmptr->tm_year + 1900 == y && tmptr->tm_mon == mnum &&
1060 		tmptr->tm_mday < dom))
1061 		tv += 60 * 60;
1062 
1063 	while ((tmptr=localtime(&tv))->tm_year + 1900 > y ||
1064 	       (tmptr->tm_year + 1900 == y && tmptr->tm_mon > mnum) ||
1065 	       (tmptr->tm_year + 1900 == y && tmptr->tm_mon == mnum &&
1066 		tmptr->tm_mday > dom))
1067 		tv -= 60 * 60;
1068 
1069 	while ((tmptr=localtime(&tv))->tm_mday == dom &&
1070 	       tmptr->tm_hour < h)
1071 		tv += 60 * 60;
1072 
1073 	while ((tmptr=localtime(&tv))->tm_mday == dom &&
1074 	       tmptr->tm_hour > h)
1075 		tv -= 60 * 60;
1076 
1077 	tv += m * 60 + s;
1078 
1079 	return tv;
1080 }
1081 
1082 /////////////////////////////////////////////////////////////////////////////
1083 //
1084 // scan() is where all the exciting stuff happens.  scan() reads a file with
1085 // messages; potentially copies the messages to another file.
1086 //
1087 // scan() is invoked in the following situations, where it acts as follows:
1088 //
1089 // A.  OPENING A NEW FOLDER
1090 //
1091 //     saveFile=NULL, reopening=false, deleteMsgs=NULL, rewriting=false
1092 //
1093 //     folderMessageIndex and uidmap are created based on the messages in
1094 //     the file.  folderDirty is set to true if there are messages in the
1095 //     file without an existing UID (in which case each message is assigned
1096 //     a new UID).
1097 //
1098 // B.  NEW MAIL CHECK FROM SYSTEM MAILBOX
1099 //
1100 //     saveFile=not NULL, reopening=false, deleteMsgs=NULL, rewriting=false
1101 //
1102 //     Previously, scan() was called in situation A, to open $HOME/Inbox.
1103 //     This time, saveFile is $HOME/Inbox, and scanFile is the existing file
1104 //     is the spoolfile (/var/spool/something, usually).
1105 //
1106 //     If any messages are found in the spoolfile, they are copied to saveFile,
1107 //     and are added to folderMessageIndex and uidmap.  Any existing UIDs in
1108 //     the new messages are ignored, each new message is assigned a new UID,
1109 //     and folderDirty is set to true.
1110 //
1111 // C.  REOPENING A FOLDER
1112 //
1113 //     saveFile=NULL, reopening=true, deleteMsgs=NULL, rewriting=false
1114 //
1115 //     If we believe that the file has not been touched by another process
1116 //     (timestamp and size have not been changed), everything is left alone
1117 //     the way it is.  Otherwise;
1118 //
1119 //     folderMessageIndex and uidmap are created based on the messages in
1120 //     the file.  folderDirty is set to true if there are messages in the
1121 //     file without an existing UID (in which case each message is assigned
1122 //     a new UID).
1123 //
1124 // D.  EXPUNGING THE FOLDER
1125 //
1126 //     saveFile=new file, reopening=true, deleteMsgs != NULL, rewriting=true
1127 //
1128 //     Messages are copied to saveFile, except ones that are marked as
1129 //     deleted.  folderMessageIndex and uidmap are updated accordingly.
1130 //
1131 // E.  CLOSING THE FOLDER
1132 //
1133 //     saveFile=new file, reopening=true, deleteMsgs=NULL, rewriting=true
1134 //
1135 //     All messages are copied to saveFile, with any updated flags and uids.
1136 //
1137 
scan(mail::file & scanFile,mail::file * saveFile,bool reopening,set<string> * deleteMsgs,bool rewriting,mail::callback * progress)1138 bool mail::mbox::scan(mail::file &scanFile,
1139 		      mail::file *saveFile,
1140 		      bool reopening,
1141 		      set<string> *deleteMsgs,
1142 		      bool rewriting,
1143 		      mail::callback *progress)
1144 {
1145 	struct stat stat_buf;
1146 
1147 	set<string> deleted; // All deleted UIDs
1148 
1149 	map<string, mail::messageInfo *> updatedFlags;
1150 	// List of all updated flags
1151 
1152 	vector<mboxMessageIndex> newMessageIndex;
1153 	// All new UIDs that were created for messages without UIDs.
1154 
1155 	if (reopening)
1156 	{
1157 		// Initialize the list of all updated message flags,
1158 		// as well as the deleted messages that should not be copied
1159 
1160 		vector<mail::messageInfo>::iterator b=index.begin(),
1161 			e=index.end();
1162 
1163 		while (b != e)
1164 		{
1165 			mail::messageInfo &i=*b++;
1166 
1167 			if (deleteMsgs != NULL)
1168 			{
1169 				if (deleteMsgs->count(i.uid) > 0)
1170 					deleted.insert(i.uid);
1171 			}
1172 
1173 			updatedFlags.insert(make_pair(i.uid, &i));
1174 		}
1175 	}
1176 
1177 	// Read the folder file, line by line.
1178 	// Keep track of the starting points of each message.
1179 
1180 
1181 	bool skipping=false;   // Not copying the current message
1182 
1183 	bool copying=false;    // Message copy in progress
1184 
1185 	bool seenFrom=false;   // Previous line was a From_ line.
1186 
1187 	string fromhdr;
1188 
1189 	stat_buf.st_size=0;
1190 
1191 	off_t nextUpdatePos=0;
1192 
1193 	off_t fromPos=0;
1194 	size_t rewriteIndex=0;
1195 
1196 	string fromLine;
1197 
1198 	scanFile.seeked();
1199 
1200 	while (!feof(scanFile))
1201 	{
1202 		off_t pos;	// Current line starts here
1203 
1204 		if ((pos=scanFile.tell()) < 0)
1205 		{
1206 			return false;
1207 		}
1208 
1209 		// Every 10k bytes send an update.
1210 
1211 		if (progress && pos >= nextUpdatePos)
1212 		{
1213 			nextUpdatePos=pos + 10000;
1214 			progress->reportProgress(pos, stat_buf.st_size, 0, 1);
1215 		}
1216 
1217 		string line=scanFile.getline();
1218 
1219 		if (strncmp(line.c_str(), "From ", 5) == 0)
1220 		{
1221 			fromhdr=line;
1222 
1223 			// copying is false if this is the first message.
1224 
1225 			if (!copying &&
1226 			    saveFile)	// Copying messages.
1227 			{
1228 				// We're copying messages, and this is the
1229 				// first message.  A couple of things must
1230 				// be done:
1231 
1232 				// Remember how big the saveFile was,
1233 				// originally.
1234 
1235 				if (fstat(fileno(static_cast<FILE *>
1236 						 (*saveFile)),
1237 					  // fileno may be a macro
1238 					  &stat_buf) < 0)
1239 				{
1240 					return false;
1241 				}
1242 
1243 				// Make sure From of the first new
1244 				// message begins on a new line
1245 
1246 				if (stat_buf.st_size > 0)
1247 				{
1248 					if (fseek(*saveFile, -1L, SEEK_END)
1249 					    < 0)
1250 					{
1251 						return false;
1252 					}
1253 
1254 					int c=getc(*saveFile);
1255 
1256 					if (fseek(*saveFile, 0L, SEEK_END) < 0)
1257 					{
1258 						return false;
1259 					}
1260 
1261 					if (c != '\n')
1262 					{
1263 						stat_buf.st_size++;
1264 						putc('\n', *saveFile);
1265 					}
1266 				}
1267 			}
1268 
1269 			if (!copying && // First message
1270 
1271 			    !saveFile && reopening && !rewriting) // REOPENING
1272 			{
1273 				// Potential short cut.
1274 
1275 				if (fstat(fileno(static_cast<FILE *>(scanFile)),
1276 					  &stat_buf) < 0)
1277 				{
1278 					return false;
1279 				}
1280 
1281 				if (stat_buf.st_size == folderSavedSize &&
1282 				    stat_buf.st_mtime ==
1283 				    folderSavedTimestamp)
1284 				{
1285 					return true;
1286 				}
1287 			}
1288 
1289 			if (!copying &&	// First message
1290 			    !saveFile)	// NOT READING NEW MAIL
1291 				resetFolder();
1292 
1293 			copying=true;
1294 			fromLine=line;
1295 
1296 			if (saveFile)
1297 			{
1298 				if ((pos=ftell(*saveFile)) < 0)
1299 				{
1300 					return false;
1301 				}
1302 			}
1303 
1304 			fromPos=pos;
1305 			seenFrom=true;
1306 			continue;
1307 		}
1308 
1309 		if (seenFrom) // Previous line was the From_ line.
1310 		{
1311 			seenFrom=false;
1312 
1313 			skipping=false;
1314 
1315 			// Does the first line of the message has our magic
1316 			// tag?
1317 
1318 			mail::mboxMagicTag tag(line, keywordHashtable);
1319 
1320 			if (tag.good()) // Yes.
1321 			{
1322 				string uid=tag.getMessageInfo().uid;
1323 
1324 				if (deleted.count(uid) > 0)
1325 					// This one's deleted.
1326 				{
1327 					skipping=true;
1328 					rewriteIndex++;
1329 					continue;
1330 				}
1331 
1332 				if (saveFile)
1333 					// Flags in the file might be stale,
1334 					// make sure the current flags are
1335 					// written out later.
1336 				{
1337 					if (updatedFlags.count(uid) > 0)
1338 					{
1339 						mail::messageInfo *i=
1340 							updatedFlags
1341 							.find(uid)->second;
1342 
1343 						map<string, size_t>
1344 							::iterator kw=
1345 							uidmap.find(uid);
1346 
1347 						tag=mail::mboxMagicTag(uid,
1348 								       *i,
1349 								       kw ==
1350 								       uidmap.end() ?
1351 								       tag.getKeywords():
1352 								       folderMessageIndex[kw->second].tag.getKeywords());
1353 					}
1354 					else
1355 						// READING NEW MAIL -- ignore
1356 						// existing tags.
1357 
1358 						tag=mail::mboxMagicTag();
1359 
1360 					fprintf(*saveFile, "%s\n%s\n",
1361 						fromLine.c_str(),
1362 						tag.toString().c_str());
1363 				}
1364 			}
1365 			else if (rewriteIndex < folderMessageIndex.size() &&
1366 				 deleted.count(folderMessageIndex[rewriteIndex]
1367 					       .tag.getMessageInfo().uid) > 0)
1368 			{
1369 				// The folder was opened, this was a new
1370 				// message without a tag.  Subsequently the
1371 				// message was marked deleted.  We can assume
1372 				// that folderMessageIndex is valid at this
1373 				// point in time because the mailbox file is
1374 				// locked.
1375 
1376 				skipping=true;
1377 				rewriteIndex++;
1378 				continue;
1379 			}
1380 			else
1381 			{
1382 				folderDirty=true;
1383 
1384 				if (rewriting &&
1385 				    rewriteIndex < folderMessageIndex.size())
1386 				{
1387 					// See the previous comment, except
1388 					// for the part where the message was
1389 					// marked as deleted.
1390 
1391 					tag=folderMessageIndex[rewriteIndex]
1392 						.tag;
1393 
1394 					string uid=tag.getMessageInfo().uid;
1395 
1396 					if (updatedFlags.count(uid) > 0)
1397 					{
1398 						mail::messageInfo *i=
1399 							updatedFlags
1400 							.find(uid)->second;
1401 
1402 						tag=mail::mboxMagicTag(uid,
1403 								       *i,
1404 								       tag.getKeywords());
1405 					}
1406 				}
1407 				else	// Yes, it's really a new message.
1408 
1409 					tag=mail::mboxMagicTag();
1410 
1411 				if (saveFile)
1412 				{
1413 					fprintf(*saveFile, "%s\n%s\n%s\n",
1414 						fromLine.c_str(),
1415 						tag.toString().c_str(),
1416 						line.c_str());
1417 				}
1418 			}
1419 
1420 			rewriteIndex++;
1421 
1422 			mail::messageInfo info=tag.getMessageInfo();
1423 
1424 
1425 			mboxMessageIndex newIndex;
1426 
1427 			newIndex.startingPos=fromPos;
1428 			newIndex.tag=tag;
1429 			if ((newIndex.internalDate=fromCtime(fromhdr)) == 0)
1430 				newIndex.internalDate=time(NULL);
1431 
1432 			newMessageIndex.push_back(newIndex);
1433 		}
1434 		else // Message contents
1435 		{
1436 			if (!copying)
1437 			{
1438 				// If we get here, this is the first
1439 				// non-empty line in the file, and it is
1440 				// not a From_ line.
1441 
1442 				if (line.size() == 0)
1443 					continue;
1444 
1445 				errno=EINVAL;
1446 				return false;
1447 			}
1448 
1449 			if (line.size() == 0 && feof(scanFile))
1450 				break;
1451 
1452 			if (!skipping && saveFile)
1453 				fprintf(*saveFile, "%s\n", line.c_str());
1454 		}
1455 	}
1456 
1457 	// Ok, we've created newMessageIndex, and we have an existing index
1458 	// what now?
1459 	//
1460 	// Blow away the existing index if:
1461 	//    1. Rewriting or closing the folder
1462 	//    2. If we're opening or reopening the folder, an the file did
1463 	//       not have any messages (resetFolder() was never called inside
1464 	//       the loop - see above).
1465 	//
1466 	if ((!copying && !saveFile) || rewriting)
1467 		resetFolder();
1468 
1469 	// At this point now we'll append newMessageIndex to whatever's in
1470 	// folderMessageIndex, and update uidmap accordingly.
1471 
1472 	vector<mboxMessageIndex>::iterator b, e;
1473 
1474 	b=newMessageIndex.begin();
1475 	e=newMessageIndex.end();
1476 
1477 	size_t n=folderMessageIndex.size();
1478 
1479 	while (b != e)
1480 	{
1481 		mboxMessageIndex &i= *b++;
1482 
1483 		mail::messageInfo info=i.tag.getMessageInfo();
1484 
1485 		if (uidmap.count(info.uid) > 0) // SHOULD NOT HAPPEN
1486 		{
1487 			info.uid=mail::mboxMagicTag().getMessageInfo().uid;
1488 			i.tag=mail::mboxMagicTag(info.uid, info,
1489 						 i.tag.getKeywords());
1490 			folderDirty=true;
1491 		}
1492 
1493 		folderMessageIndex.insert(folderMessageIndex.end(), i);
1494 
1495 		try {
1496 			uidmap.insert(make_pair(info.uid, n));
1497 		} catch (...) {
1498 			folderMessageIndex.erase(folderMessageIndex.begin()+n);
1499 			LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
1500 		}
1501 		n++;
1502 	}
1503 
1504 	// Cleanup
1505 
1506 	if (progress)
1507 		progress->reportProgress(stat_buf.st_size, stat_buf.st_size,
1508 					 0, 1);
1509 
1510 	if (saveFile && (ferror(*saveFile) || fflush(*saveFile) < 0))
1511 	{
1512 		return false;
1513 	}
1514 
1515 	if (ferror(scanFile))
1516 	{
1517 		return false;
1518 	}
1519 
1520 	return true;
1521 }
1522 
translatePath(string path)1523 string mail::mbox::translatePath(string path)
1524 {
1525 	return translatePathCommon(path, "/");
1526 }
1527