1 /*
2 ** Copyright 2002-2004, Double Precision Inc.
3 **
4 ** See COPYING for distribution information.
5 */
6 #include "mail.H"
7 #include "sync.H"
8
9 #include "driver.H"
10 #include "mbox.H"
11
12 #include "runlater.H"
13 #include "logininfo.H"
14 #include <iostream>
15 #include <errno.h>
16 #include <netinet/in.h>
17 #include <unistd.h>
18 #include <pwd.h>
19 #include <arpa/inet.h>
20 #include "rfc822/rfc822.h"
21 #include <courier-unicode.h>
22 #include "maildir/maildiraclt.h"
23 #include "misc.H"
24 #include <fstream>
25
26 using namespace std;
27
28 list<mail::account *> mail::account::mailaccount_list;
29
30 //
31 // Initialize mail::folder's default. mail::folder is a superclass for
32 // mail folder objects. Each mail account type is expected to use a
33 // subclass for the actual implementation.
34 //
35 // Each mail::folder object is associated with a corresponding mail::account
36 // object. The constructor saves a pointer, and provides methods that
37 // the subclass can use to determine whether the mail::account object still exists
38
folder(mail::account * ptr)39 mail::folder::folder(mail::account *ptr) : myServer(ptr)
40 {
41 }
42
~folder()43 mail::folder::~folder()
44 {
45 }
46
47 //
48 // The first order of business for any mail::folder method call is to
49 // determine whether the mail::account object still exists. This method is
50 // a shortcut for most methods that receive a mail::callback object.
51 // If the mail::account object was destroyed, callback's fail method is called
52 // appropriately.
53
isDestroyed(mail::callback & callback)54 bool mail::folder::isDestroyed(mail::callback &callback) const
55 {
56 if (myServer.isDestroyed())
57 {
58 callback.fail("Server connection closed.");
59 return true;
60 }
61
62 return false;
63 }
64
isDestroyed()65 bool mail::folder::isDestroyed() const
66 {
67 return myServer.isDestroyed();
68 }
69
isNewsgroup()70 string mail::folder::isNewsgroup() const
71 {
72 return "";
73 }
74
getMyRights(mail::callback & getCallback,std::string & rights)75 void mail::folder::getMyRights(mail::callback &getCallback,
76 std::string &rights) const
77 {
78 getCallback.fail("This server does not implement access control lists.");
79
80 }
81
getRights(mail::callback & getCallback,std::list<std::pair<std::string,std::string>> & rights)82 void mail::folder::getRights(mail::callback &getCallback,
83 std::list<std::pair<std::string, std::string> >
84 &rights) const
85 {
86 rights.clear();
87 getCallback.fail("This server does not implement access control lists.");
88 }
89
setRights(mail::callback & setCallback,string & errorIdentifier,vector<std::string> & errorRights,string identifier,string rights)90 void mail::folder::setRights(mail::callback &setCallback,
91 string &errorIdentifier,
92 vector<std::string> &errorRights,
93 string identifier,
94 string rights) const
95 {
96 errorIdentifier="";
97 errorRights.clear();
98 setCallback.fail("This server does not implement access control lists.");
99 }
100
delRights(mail::callback & setCallback,string & errorIdentifier,vector<std::string> & errorRights,std::string identifier)101 void mail::folder::delRights(mail::callback &setCallback,
102 string &errorIdentifier,
103 vector<std::string> &errorRights,
104 std::string identifier) const
105 {
106 errorIdentifier="";
107 errorRights.clear();
108 setCallback.fail("This server does not implement access control lists.");
109 }
110
111 //
112 // callback::folder miscellania
113
folder()114 mail::callback::folder::folder()
115 {
116 }
117
~folder()118 mail::callback::folder::~folder()
119 {
120 }
121
saveSnapshot(std::string snapshotId)122 void mail::callback::folder::saveSnapshot(std::string snapshotId)
123 {
124 }
125
126 // mail::callback::folderList miscellania
127
folderList()128 mail::callback::folderList::folderList()
129 {
130 }
131
~folderList()132 mail::callback::folderList::~folderList()
133 {
134 }
135
136 // mail::callback::folderInfo miscellania
137
folderInfo()138 mail::callback::folderInfo::folderInfo()
139 : messageCount(0), unreadCount(0), fastInfo(false)
140 {
141 }
142
~folderInfo()143 mail::callback::folderInfo::~folderInfo()
144 {
145 }
146
success()147 void mail::callback::folderInfo::success()
148 {
149 }
150
151 // messageInfo miscellania
152
messageInfo()153 mail::messageInfo::messageInfo()
154 {
155 uid="";
156 draft=replied=marked=deleted=unread=recent=false;
157 }
158
messageInfo(string s)159 mail::messageInfo::messageInfo(string s)
160 {
161 uid="";
162 draft=replied=marked=deleted=unread=recent=false;
163
164 string::iterator b=s.begin(), e=s.end();
165
166 while (b != e)
167 {
168 switch (*b++) {
169 case 'D':
170 draft=true;
171 break;
172 case 'R':
173 replied=true;
174 break;
175 case 'M':
176 marked=true;
177 break;
178 case 'T':
179 deleted=true;
180 break;
181 case 'U':
182 unread=true;
183 break;
184 case 'C':
185 recent=true;
186 break;
187 case ':':
188 uid=string(b, e);
189 b=e;
190 break;
191 }
192 }
193 }
194
~messageInfo()195 mail::messageInfo::~messageInfo()
196 {
197 }
198
string()199 mail::messageInfo::operator string() const
200 {
201 string s;
202
203 if (draft) s += "D";
204 if (replied) s += "R";
205 if (marked) s += "M";
206 if (deleted) s += "T";
207 if (unread) s += "U";
208 if (recent) s += "C";
209
210 return s + ":" + uid;
211 }
212
213
214 //
215 // mail::account superclass constructor. The main task at hand is to save
216 // a reference to the disconnect callback, for later use, and to initialize
217 // the default character set.
218 //
219
account(mail::callback::disconnect & callbackArg)220 mail::account::account(mail::callback::disconnect &callbackArg)
221 : disconnect_callback(callbackArg)
222 {
223 my_mailaccount_p=mailaccount_list.insert(mailaccount_list.end(), this);
224 }
225
~account()226 mail::account::~account()
227 {
228 // We can't just remove (this) from mailaccount_list, because we
229 // might be iterating mail:;account::process().
230
231 *my_mailaccount_p=NULL;
232 }
233
234 //////////////////////////////////////////////////////////////////////////////
235 //
236 // Utility functions.
237 //
238 //////////////////////////////////////////////////////////////////////////////
239
240 LIBMAIL_START
241
hostname()242 string hostname()
243 {
244 char h[512];
245
246 h[sizeof(h)-1]=0;
247
248 if (gethostname(h, sizeof(h)-1) < 0)
249 strcpy(h, "localhost");
250 return h;
251 }
252
homedir()253 string homedir()
254 {
255 const char *h=getenv("HOME");
256
257 struct passwd *pw=getpwuid(getuid());
258
259 if (!pw)
260 return "";
261
262 if (h == NULL || !*h)
263 h=pw->pw_dir;
264
265 return h;
266 }
267
upper(string & w)268 void upper(string &w)
269 {
270 for (string::iterator b=w.begin(), e=w.end(); b != e; ++b)
271 if (*b >= 'a' && *b <= 'z')
272 *b += 'A'-'a';
273 }
274 LIBMAIL_END
275
276 static const char x[]="0123456789ABCDEF";
277
encword(string w)278 static string encword(string w)
279 {
280 string ww="";
281
282 string::iterator b=w.begin(), e=w.end(), c;
283
284 while (b != e)
285 {
286 for (c=b; c != e; c++)
287 if ( *c == '@' || *c == '/' || *c == '%' || *c == ':')
288 break;
289
290 ww.append(b, c);
291
292 b=c;
293
294 if (b != e)
295 {
296 ww += '%';
297 ww += x[ (*b / 16) & 15 ];
298 ww += x[ *b & 15];
299 b++;
300 }
301 }
302
303 return ww;
304 }
305
306 LIBMAIL_START
loginUrlEncode(string method,string server,string uid,string pwd)307 string loginUrlEncode(string method, string server, string uid,
308 string pwd)
309 {
310 return (method + "://" +
311 (uid.size() > 0 ? encword(uid)
312 + (pwd.size() > 0 ? ":" + encword(pwd):"") + "@":"")
313 + server);
314 }
315 LIBMAIL_END
316
decword(string w)317 static string decword(string w)
318 {
319 string ww="";
320
321 string::iterator b=w.begin(), e=w.end(), c;
322
323 while (b != e)
324 {
325 for (c=b; c != e; c++)
326 if ( *c == '%' && e-c > 2)
327 break;
328
329 ww.append(b, c);
330
331 b=c;
332
333 if (b != e)
334 {
335 b++;
336
337 const char *c1= strchr(x, *b++);
338 const char *c2= strchr(x, *b++);
339
340 char c=0;
341
342 if (c1) c= (c1-x) * 16;
343 if (c2) c += c2-x;
344
345 ww += c;
346 }
347 }
348
349 return ww;
350 }
351
352 LIBMAIL_START
353
354 // Parse url format: method://uid:pwd@server/option/option...
355
loginUrlDecode(string url,mail::loginInfo & loginInfo)356 bool loginUrlDecode(string url, mail::loginInfo &loginInfo)
357 {
358 size_t n=url.find(':');
359
360 if (n == std::string::npos)
361 return false;
362
363 loginInfo.method=url.substr(0, n);
364
365 if (url.size() - n < 3 || url[n+1] != '/' || url[n+2] != '/')
366 return false;
367
368 string serverStr=url.substr(n+3);
369 string options="";
370
371 n=serverStr.find('/');
372
373 if (n != std::string::npos)
374 {
375 options=serverStr.substr(n+1);
376 serverStr.erase(n);
377 }
378
379 n=serverStr.rfind('@');
380 string uidpwd="";
381
382 if (n != std::string::npos)
383 {
384 uidpwd=serverStr.substr(0, n);
385 serverStr=serverStr.substr(n+1);
386 }
387 loginInfo.server=serverStr;
388
389 n=uidpwd.find(':');
390
391 string pwd="";
392
393 if (n != std::string::npos)
394 {
395 pwd=uidpwd.substr(n+1);
396 uidpwd=uidpwd.substr(0, n);
397 }
398
399 loginInfo.uid=decword(uidpwd);
400 loginInfo.pwd=decword(pwd);
401
402 while (options.size() > 0)
403 {
404 n=options.find('/');
405
406 string option;
407
408 if (n == std::string::npos)
409 {
410 option=options;
411 options="";
412 }
413 else
414 {
415 option=options.substr(0, n);
416 options=options.substr(n+1);
417 }
418
419 string optionVal;
420
421 n=option.find('=');
422
423 if (n != std::string::npos)
424 {
425 optionVal=option.substr(n+1);
426 option=option.substr(0, n);
427 }
428
429 loginInfo.options.insert(make_pair(option, optionVal));
430 }
431 return true;
432 }
433
434 LIBMAIL_END
435
openInfo()436 mail::account::openInfo::openInfo() : loginCallbackObj(NULL)
437 {
438 }
439
~openInfo()440 mail::account::openInfo::~openInfo()
441 {
442 }
443
open(mail::account::openInfo oi,mail::callback & callback,mail::callback::disconnect & disconnectCallback)444 mail::account *mail::account::open(mail::account::openInfo oi,
445 mail::callback &callback,
446 mail::callback::disconnect
447 &disconnectCallback)
448 {
449 mail::driver **l=mail::driver::get_driver_list();
450
451 while (*l)
452 {
453 mail::account *a;
454
455 if ( (*(*l)->open_func)(a, oi, callback, disconnectCallback))
456 {
457 if (!a)
458 callback.fail(strerror(errno));
459 return a;
460 }
461
462 l++;
463 }
464
465 callback.fail("Invalid mail account URL.");
466 return NULL;
467 }
468
isRemoteUrl(std::string url)469 bool mail::account::isRemoteUrl(std::string url)
470 {
471 mail::driver **l=mail::driver::get_driver_list();
472
473 while (*l)
474 {
475 bool flag;
476
477 if ( (*(*l)->isRemote_func)(url, flag))
478 return flag;
479
480 l++;
481 }
482
483 return false;
484 }
485
486 // Default implementation of getSendFolder: no such luck.
487
getSendFolder(const class mail::smtpInfo & info,const mail::folder * folder,string & errmsg)488 mail::folder *mail::account::getSendFolder(const class mail::smtpInfo &info,
489 const mail::folder *folder,
490 string &errmsg)
491 {
492 errno=ENOENT;
493 errmsg="Not implemented.";
494 return NULL;
495 }
496
497 //
498 // The main function. Where everything good happens.
499 //
500 // mail::account::process calls each existing mail::account object's handler method.
501 //
process(vector<pollfd> & fds,int & timeout)502 void mail::account::process(vector<pollfd> &fds, int &timeout)
503 {
504 list<mail::account *>::iterator b, e, cur;
505
506 fds.clear();
507 b=mailaccount_list.begin();
508 e=mailaccount_list.end();
509
510 while (b != e)
511 {
512 // mail::account may destroy itself, so increment the iterator
513 // before invoking the method
514
515 cur=b;
516 b++;
517
518 if (*cur == NULL)
519 mailaccount_list.erase(cur);
520 else
521 {
522 (*cur)->handler(fds, timeout);
523 }
524 }
525
526 // Postponed handlers.
527 mail::runLater::checkRunLater(timeout);
528 }
529
530 //
531 // Resume after suspend.
532 //
533
resume()534 void mail::account::resume()
535 {
536 list<mail::account *>::iterator b, e, cur;
537
538 b=mailaccount_list.begin();
539 e=mailaccount_list.end();
540
541 while (b != e)
542 {
543 // mail::account may destroy itself, so increment the iterator
544 // before invoking the method
545
546 cur=b;
547 ++b;
548
549 if (*cur)
550 (*cur)->resumed();
551 }
552 }
553
updateNotify(bool enableDisable,callback & callbackArg)554 void mail::account::updateNotify(bool enableDisable, callback &callbackArg)
555 {
556 callbackArg.success("OK");
557 }
558
moveMessagesTo(const std::vector<size_t> & messages,mail::folder * copyTo,mail::callback & callback)559 void mail::account::moveMessagesTo(const std::vector<size_t> &messages,
560 mail::folder *copyTo,
561 mail::callback &callback)
562 {
563 generic::genericMoveMessages(this, messages, copyTo, callback);
564 }
565
566 #if 0
567 void mail::account::removeMessages(const std::vector<size_t> &messages,
568 callback &cb)
569 {
570 cb.fail("TODO");
571 }
572
573 #endif
574
updateKeywords(const vector<size_t> & messages,const set<string> & keywords,bool setOrChange,bool changeTo,mail::callback & cb)575 void mail::account::updateKeywords(const vector<size_t> &messages,
576 const set<string> &keywords,
577 bool setOrChange,
578 // false: set, true: see changeTo
579 bool changeTo,
580 mail::callback &cb)
581 {
582 cb.fail("Not implemented");
583 }
584
updateKeywords(const vector<size_t> & messages,const vector<string> & keywords,bool setOrChange,bool changeTo,mail::callback & cb)585 void mail::account::updateKeywords(const vector<size_t> &messages,
586 const vector<string> &keywords,
587 bool setOrChange,
588 bool changeTo,
589 mail::callback &cb)
590 {
591 set<string> s;
592
593 s.insert(keywords.begin(), keywords.end());
594
595 updateKeywords(messages, s, setOrChange, changeTo, cb);
596 }
597
598
updateKeywords(const vector<size_t> & messages,const list<string> & keywords,bool setOrChange,bool changeTo,mail::callback & cb)599 void mail::account::updateKeywords(const vector<size_t> &messages,
600 const list<string> &keywords,
601 bool setOrChange,
602 bool changeTo,
603 mail::callback &cb)
604 {
605 set<string> s;
606
607 s.insert(keywords.begin(), keywords.end());
608
609 updateKeywords(messages, s, setOrChange, changeTo, cb);
610 }
611
getFolderKeywordInfo(size_t messageNum,set<string> & keywordRet)612 void mail::account::getFolderKeywordInfo(size_t messageNum,
613 set<string> &keywordRet)
614 {
615 keywordRet.clear();
616 }
617
618
619
620
621 //
622 // Must provide default methods for mail::callback::message, not all
623 // subclasses must implement every method.
624 //
625
message()626 mail::callback::message::message()
627 {
628 }
629
~message()630 mail::callback::message::~message()
631 {
632 }
633
messageEnvelopeCallback(size_t n,const mail::envelope & env)634 void mail::callback::message::messageEnvelopeCallback(size_t n,
635 const mail::envelope
636 &env)
637 {
638 }
639
messageReferencesCallback(size_t n,const vector<string> & references)640 void mail::callback::message::messageReferencesCallback(size_t n,
641 const vector<string>
642 &references)
643 {
644 }
645
messageStructureCallback(size_t n,const mail::mimestruct & mstruct)646 void mail::callback::message::messageStructureCallback(size_t n,
647 const mail::mimestruct
648 &mstruct)
649 {
650 }
651
messageTextCallback(size_t n,string text)652 void mail::callback::message::messageTextCallback(size_t n, string text)
653 {
654 }
655
656 void mail::callback::message
messageArrivalDateCallback(size_t messageNumber,time_t datetime)657 ::messageArrivalDateCallback(size_t messageNumber,
658 time_t datetime)
659 {
660 }
661
messageSizeCallback(size_t messageNumber,unsigned long messagesize)662 void mail::callback::message::messageSizeCallback(size_t messageNumber,
663 unsigned long messagesize)
664 {
665 }
666
667 LIBMAIL_START
668
toutf8(string s)669 string toutf8(string s)
670 {
671 string u;
672
673 char *p=unicode_convert_toutf8(s.c_str(),
674 unicode_default_chset(), NULL);
675
676 try {
677 u=p;
678 free(p);
679 } catch (...) {
680 free(p);
681 LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
682 }
683 return u;
684 }
685
fromutf8(string s)686 string fromutf8(string s)
687 {
688 string u;
689
690 char *p=unicode_convert_fromutf8(s.c_str(), unicode_default_chset(),
691 NULL);
692
693 try {
694 u=p;
695 free(p);
696 } catch (...) {
697 free(p);
698 LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
699 }
700 return u;
701 }
702 LIBMAIL_END
703
704 //
705 // Common code for mbox and maildir
706 //
707
translatePathCommon(string path,const char * sep)708 string mail::mbox::translatePathCommon(string path,
709 const char *sep)
710 {
711 string newpath;
712
713 do
714 {
715 string component;
716
717 size_t n=path.find('/');
718
719 if (n == std::string::npos)
720 {
721 component=path;
722 path="";
723 }
724 else
725 {
726 component=path.substr(0, n);
727 path=path.substr(n+1);
728 }
729
730 u32string ucvec;
731
732 char32_t *uc;
733 size_t ucsize;
734 unicode_convert_handle_t h;
735
736 if ((h=unicode_convert_tou_init(unicode_default_chset(),
737 &uc, &ucsize, 1)) == NULL)
738 {
739 uc=NULL;
740 }
741 else
742 {
743 unicode_convert(h, component.c_str(),
744 component.size());
745
746 if (unicode_convert_deinit(h, NULL))
747 uc=NULL;
748 }
749
750 if (!uc)
751 {
752 errno=EINVAL;
753 return "";
754 }
755
756 try {
757 size_t n;
758
759 for (n=0; uc[n]; n++)
760 ;
761
762 ucvec.insert(ucvec.end(), uc, uc+n);
763
764 for (n=0; n<ucvec.size(); )
765 {
766 if (ucvec[n] == '\\' && n+1 < ucvec.size())
767 {
768 ucvec.erase(ucvec.begin()+n,
769 ucvec.begin()+n+1);
770 n++;
771 continue;
772 }
773 if (ucvec[n] == '%')
774 {
775 char32_t ucnum=0;
776 size_t o=n+1;
777
778 while (o < ucvec.size())
779 {
780 if ((unsigned char)ucvec[o]
781 != ucvec[o])
782 break;
783 if (!isdigit(ucvec[o]))
784 break;
785
786 ucnum=ucnum * 10 +
787 ucvec[o]-'0';
788 ++o;
789 }
790
791 if (o < ucvec.size() &&
792 ucvec[o] == ';')
793 ++o;
794
795 ucvec[n++]=ucnum;
796 ucvec.erase(ucvec.begin()+n,
797 ucvec.begin()+o);
798 continue;
799 }
800 n++;
801 }
802 } catch (...) {
803 free(uc);
804 LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
805 }
806
807 free(uc);
808 std::string p(unicode::iconvert::convert(ucvec,
809 unicode_x_smap_modutf8));
810
811 if (newpath.size() > 0)
812 newpath += sep;
813 newpath += p;
814 } while (path.size() > 0);
815
816 return newpath;
817 }
818