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