1 /*
2 ** Copyright 2003-2011, Double Precision Inc.
3 **
4 ** See COPYING for distribution information.
5 */
6
7 #include "config.h"
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <locale.h>
11 #include <errno.h>
12 #include <pwd.h>
13 #include <time.h>
14 #include <fcntl.h>
15 #include <unistd.h>
16 #include <signal.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <iomanip>
20 #include <sstream>
21
22 #include "curses/cursesbutton.H"
23 #include "curses/cursesdialog.H"
24 #include "curses/curseslabel.H"
25 #include "curses/cursesfield.H"
26 #include "curses/cursesfilereq.H"
27 #include <courier-unicode.h>
28
29 #include "liblock/config.h"
30 #include "liblock/liblock.h"
31 #include "libmail/mail.H"
32 #include "libmail/fd.H"
33 #include "libmail/logininfo.H"
34 #include "libmail/misc.H"
35 #include "messagesize.H"
36 #include "myserver.H"
37 #include "myservercallback.H"
38 #include "myserverpromptinfo.H"
39 #include "myserverlogincallback.H"
40 #include "myserverremoteconfig.H"
41 #include "mainmenu.H"
42 #include "passwordlist.H"
43 #include "typeahead.H"
44 #include "ctrlchandler.H"
45 #include "curseshierarchy.H"
46 #include "cursesindexdisplay.H"
47 #include "cursesmessage.H"
48 #include "cursesmessagedisplay.H"
49 #include "cursesattachmentdisplay.H"
50 #include "spellchecker.H"
51 #include "specialfolder.H"
52 #include "addressbook.H"
53 #include "gettext.H"
54 #include "helpfile.H"
55 #include "colors.H"
56 #include "gpg.H"
57 #include "init.H"
58 #include "macros.H"
59 #include "configscreen.H"
60
61 #include <iostream>
62
63 using namespace std;
64
65 extern struct CustomColor color_misc_titleBar;
66 extern struct CustomColor color_misc_statusBar;
67 extern struct CustomColor color_misc_hotKey;
68 extern struct CustomColor color_misc_hotKeyDescr;
69
70 // Special UTF-8 only chars:
71
72 char ucheck[16];
73 char udelete[16];
74 char unew[16];
75
76 char32_t ularr;
77 char32_t urarr;
78 char32_t ucplus;
79 char32_t ucasterisk;
80 char32_t ucwrap;
81
82 char32_t uchoriz;
83 char32_t ucvert;
84 char32_t ucupright;
85 char32_t ucrighttee;
86 char32_t ucwatch;
87 char32_t ucwatchend;
88
89 static Macros *macroPtr;
90
rfc2045_error(const char * errmsg)91 extern "C" void rfc2045_error(const char *errmsg)
92 {
93 LIBMAIL_THROW(errmsg);
94 }
95
96 //
97 // Login and Cancel buttons on the account add/edit screens
98 //
99
100 class myLoginButton : public CursesButton {
101 public:
102 myLoginButton(const char *name);
103 ~myLoginButton();
104 void clicked();
105 };
106
myLoginButton(const char * name)107 myLoginButton::myLoginButton(const char *name)
108 : CursesButton(NULL, name)
109 {
110 }
111
~myLoginButton()112 myLoginButton::~myLoginButton()
113 {
114 }
115
clicked()116 void myLoginButton::clicked()
117 {
118 Curses::keepgoing=false;
119 }
120
121 class myCancelButton : public CursesButton, public CursesKeyHandler {
122
123 bool processKey(const Curses::Key &key);
124
125 public:
126 bool cancel;
127 myCancelButton();
128 ~myCancelButton();
129
130 void clicked();
131 };
132
133
myCancelButton()134 myCancelButton::myCancelButton() : CursesButton(NULL, _("CANCEL")),
135 CursesKeyHandler(PRI_SCREENHANDLER),
136 cancel(false)
137 {
138 }
139
~myCancelButton()140 myCancelButton::~myCancelButton()
141 {
142 }
143
clicked()144 void myCancelButton::clicked()
145 {
146 cancel=1;
147 Curses::keepgoing=false;
148 }
149
processKey(const Curses::Key & key)150 bool myCancelButton::processKey(const Curses::Key &key)
151 {
152 return false;
153 }
154
155 //
156 // Changing an account name, make sure that it's unique.
157
isDupe(string newName,myServer * oldAcct)158 static bool isDupe(string newName, myServer *oldAcct)
159 {
160 vector<myServer *>::iterator
161 b=myServer::server_list.begin(),
162 e=myServer::server_list.end();
163
164 while (b != e)
165 {
166 if (oldAcct && (*b)->serverName == oldAcct->serverName)
167 {
168 b++;
169 continue;
170 }
171
172 if ((*b)->serverName == newName)
173 {
174 statusBar->clearstatus();
175 statusBar->status(_("Pick a different account name. "
176 "This one's already used."));
177 return true;
178 }
179
180 b++;
181 }
182
183 return false;
184 }
185
186 //
187 // Try to add a new account
188
tryCreateAccount(string account,string url,string password,string certificate)189 myServer *tryCreateAccount(string account, string url, string password,
190 string certificate)
191 {
192 if (isDupe(account, NULL))
193 {
194 statusBar->beepError();
195 return NULL;
196 }
197
198 myServer *ms=new myServer(account, url);
199
200 ms->certificate=certificate;
201
202 mail::loginInfo decodeUrl;
203
204 mail::loginUrlDecode(url, decodeUrl);
205
206 if (decodeUrl.method == "nntp" || decodeUrl.method == "nntps"
207 || decodeUrl.method == "pop3maildrop"
208 || decodeUrl.method == "pop3maildrops")
209 {
210 unsigned i=0;
211
212 // Pick a reasonable filename for a newsrc file or maildir
213
214 for (;;)
215 {
216 ostringstream o;
217
218 string s=decodeUrl.server;
219
220 size_t n=s.find('.');
221
222 if (n != std::string::npos)
223 s=s.substr(n+1);
224
225 n=s.find('.');
226
227 if (n != std::string::npos)
228 s=s.substr(0, n);
229
230 size_t ss;
231
232 for (ss=0; ss<s.size(); ss++)
233 if (strchr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-", s[ss]) == NULL)
234 s[ss]='_';
235
236 o << s;
237
238 if (i > 0)
239 o << setw(4) << setfill('0') << i;
240 ++i;
241
242 o << (decodeUrl.method[0] == 'p' ?
243 ".maildir":".newsrc");
244
245 s=o.str();
246
247 vector<myServer *>::iterator
248 b=myServer::server_list.begin(),
249 e=myServer::server_list.end();
250
251 while (b != e)
252 {
253 if ( (*b)->newsrc == s )
254 break;
255 b++;
256 }
257
258 if (b == e)
259 {
260 ms->newsrc=s;
261 break;
262 }
263 }
264 }
265
266 if (!ms)
267 {
268 statusBar->status(strerror(errno));
269 statusBar->beepError();
270 return NULL;
271 }
272
273 try
274 {
275 if (!ms->login(password))
276 {
277 statusBar->beepError();
278 delete ms;
279 return NULL;
280 }
281 PasswordList::passwordList.save(url, password);
282 // Make sure the password is memorized.
283
284 myServer::Callback callback;
285
286 // Get the server's top level folder list, and save it as the
287 // defaults.
288 ms->server->readTopLevelFolders(ms->topLevelFolders,
289 callback);
290 if (!myServer::eventloop(callback))
291 {
292 statusBar->beepError();
293 delete ms;
294 return NULL;
295 }
296 } catch (...)
297 {
298 delete ms;
299 LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
300 }
301
302 return ms;
303 }
304
305 //
306 // Main add new account screen
307 //
308
addAccountPrompt(MainMenuScreen::AccountType * accountType)309 static myServer *addAccountPrompt(MainMenuScreen::AccountType *accountType)
310 {
311 CursesContainer loginScreen(mainScreen);
312
313 loginScreen.setRow(2);
314 loginScreen.setAlignment(Curses::PARENTCENTER);
315
316 CursesLabel title(&loginScreen,
317 Gettext(_("Add %1% Account")) << accountType->name);
318 title.setAlignment(Curses::PARENTCENTER);
319
320
321 CursesDialog loginDialog(&loginScreen);
322
323 loginDialog.setRow(2);
324
325 CursesLabel account_label(NULL, _("Account name: "));
326 CursesLabel server_label(NULL, _("Server: "));
327 CursesLabel login_label(NULL, _("Login: "));
328 CursesLabel password_label(NULL, _("Password: "));
329
330 CursesField account_field(NULL);
331 CursesField server_field(NULL);
332 CursesField login_field(NULL);
333 CursesField password_field(NULL);
334 CursesButton cram_field(NULL, _("Do not send password in clear text"),
335 1);
336 CursesButton ssl_field(NULL, _("Use an encrypted connection"),
337 1);
338 ConfigScreen::CertificateButton cert_field("");
339
340 myLoginButton login_button(_("LOGIN"));
341 myCancelButton cancel_button;
342
343 struct passwd *pw=getpwuid(getuid());
344
345 if (pw)
346 login_field.setText(pw->pw_name); // Default login id
347
348 password_field.setPasswordChar();
349
350 int row=0;
351
352 loginDialog.addPrompt(&account_label, &account_field, row);
353 loginDialog.addPrompt(&server_label, &server_field, ++row);
354 loginDialog.addPrompt(&login_label, &login_field, ++row);
355 loginDialog.addPrompt(&password_label, &password_field, ++row);
356
357 loginDialog.addPrompt(NULL, &cram_field, row += 2);
358 loginDialog.addPrompt(NULL, &ssl_field, row += 2);
359
360 if (!myServer::certs->certs.empty())
361 loginDialog.addPrompt(NULL, &cert_field, row += 2);
362
363 loginDialog.addPrompt(NULL, &login_button, row += 2);
364 loginDialog.addPrompt(NULL, &cancel_button, row += 2);
365
366 titleBar->setTitles(_("ADD ACCOUNT"), "");
367
368 account_field.requestFocus();
369
370 for (;;)
371 {
372 myServer::eventloop();
373
374 if (cancel_button.cancel)
375 break;
376
377 string account=account_field.getText();
378 string server=server_field.getText();
379 string userid=login_field.getText();
380 string password=password_field.getText();
381
382 if (account.size() == 0)
383 {
384 account_field.requestFocus();
385 account_field.beepError();
386 continue;
387 }
388
389 if (server.size() == 0)
390 {
391 server_field.requestFocus();
392 server_field.beepError();
393 continue;
394 }
395
396 string smethod=accountType->secureMethod;
397 string method=accountType->method;
398
399 string url=mail::loginUrlEncode(ssl_field.getSelected()
400 ? accountType->secureMethod
401 : accountType->method,
402 server +
403 (cram_field.getSelected()
404 ? "/cram":""),
405 userid, "");
406
407 myServer *ms=tryCreateAccount(account, url, password,
408 cert_field.cert);
409
410 if (!ms)
411 continue;
412
413 myServer::saveconfig();
414 return ms;
415 }
416
417 return NULL;
418 }
419
420 //
421 // Main folder list hierarchy screen. When the folder list hierarchy is
422 // opened for the purpose of selecting a folder to copy messages to,
423 // the argument is not null and points to the original folder the messages
424 // are copied from. Otherwise it is NULL.
425
openHierarchyScreen(std::string prompt,PreviousScreen * prevScreen,mail::folder ** selectedFolder,myServer ** selectedServer)426 void openHierarchyScreen(std::string prompt,
427 PreviousScreen *prevScreen,
428 mail::folder **selectedFolder,
429 myServer **selectedServer)
430 {
431 CursesHierarchy hierarchy_screen( &myServer::hierarchy, mainScreen);
432 myServer::setCursesHierarchyPointerForRefreshing(&hierarchy_screen);
433
434 titleBar->setTitles(_("FOLDERS"), "");
435
436 hierarchy_screen.requestFocus();
437
438 if (selectedFolder)
439 {
440 hierarchy_screen.selectingFolder=true;
441 if (prevScreen)
442 prevScreen->screenOpened();
443 statusBar->clearstatus();
444 statusBar->status(prompt);
445 }
446
447 //
448 // On the main folder screen we should have no reason to have any
449 // address book accounts or remote config accounts open.
450 // This is a convenient way to keep these logins from idling for
451 // an excessive period of time.
452
453 AddressBook::closeAllRemote();
454 if (myServer::remoteConfigAccount)
455 myServer::remoteConfigAccount->logout();
456
457 // Update top level folder message accounts.
458
459 vector<myServer *>::iterator
460 b=myServer::server_list.begin(),
461 e=myServer::server_list.end();
462
463 while (b != e)
464 {
465 (*b)->cancelTimer();
466
467 if ((*b)->server)
468 (*b)->alarm();
469 b++;
470 }
471
472 myServer::eventloop();
473
474 if (selectedFolder)
475 *selectedFolder=hierarchy_screen.folderSelected;
476 if (selectedServer)
477 *selectedServer=hierarchy_screen.serverSelected;
478
479 myServer::setCursesHierarchyPointerForRefreshing(NULL);
480 }
481
hierarchyScreen(void * dummy)482 void hierarchyScreen(void *dummy)
483 {
484 openHierarchyScreen("", NULL, NULL, NULL);
485 }
486
addAccountScreen(void * dummy)487 void addAccountScreen(void *dummy)
488 {
489 myServer *s=addAccountPrompt((MainMenuScreen::AccountType *)dummy);
490
491 if (s)
492 s->addHierarchy(true);
493
494 myServer::nextScreen=&hierarchyScreen;
495 }
496
497 /////////////////////////////////////////////////////////////////////
498 //
499 // Edit a remote account.
500 //
501
editAccountScreen(void * voidArg)502 void editAccountScreen(void *voidArg)
503 {
504 myServer *s=(myServer *)voidArg;
505
506 CursesContainer editScreen(mainScreen);
507
508 editScreen.setRow(2);
509 editScreen.setAlignment(Curses::PARENTCENTER);
510
511 CursesLabel title(&editScreen,_("Edit Account"));
512 title.setAlignment(Curses::PARENTCENTER);
513
514 CursesDialog loginDialog(&editScreen);
515
516 loginDialog.setRow(2);
517
518 CursesLabel account_label(NULL, _("Account name: "));
519 CursesLabel server_label(NULL, _("Server: "));
520 CursesLabel login_label(NULL, _("Login: "));
521 CursesButton cram_field(NULL, _("Do not send password in clear text"),
522 1);
523
524 CursesField account_field(NULL);
525 CursesField server_field(NULL);
526 CursesField login_field(NULL);
527 CursesButton ssl_field(NULL, _("Use an encrypted connection"),
528 1);
529 ConfigScreen::CertificateButton cert_field(s->certificate);
530
531 myLoginButton login_button(_("UPDATE"));
532 myCancelButton cancel_button;
533
534 loginDialog.addPrompt(&account_label, &account_field, 0);
535 loginDialog.addPrompt(&server_label, &server_field, 1);
536 loginDialog.addPrompt(&login_label, &login_field, 2);
537
538 int row=2;
539
540 loginDialog.addPrompt(NULL, &cram_field, row += 2);
541 loginDialog.addPrompt(NULL, &ssl_field, row += 2);
542
543 if (!myServer::certs->certs.empty())
544 loginDialog.addPrompt(NULL, &cert_field, row += 2);
545
546 loginDialog.addPrompt(NULL, &login_button, row += 2);
547 loginDialog.addPrompt(NULL, &cancel_button, row += 2);
548
549 titleBar->setTitles(_("EDIT ACCOUNT"), "");
550
551 mail::loginInfo loginInfo;
552
553 account_field.setText(s->serverName);
554
555 //
556 // Decode the login URL and init the option settings.
557 //
558
559 if (mail::loginUrlDecode(s->url, loginInfo))
560 {
561 string server=loginInfo.server;
562
563 login_field.setText(loginInfo.uid);
564
565 if (loginInfo.method.size() > 0 &&
566 loginInfo.method.end()[-1] == 's') // pop3s, imaps, nntps
567 {
568 loginInfo.method=
569 loginInfo.method.substr(0,
570 loginInfo.method.size()
571 -1); // drop the s
572 ssl_field.setToggled(1);
573 }
574
575 // Separate button for the cram field
576
577 map<string, string>::iterator
578 p=loginInfo.options.find("cram"), e;
579
580 if (p != loginInfo.options.end())
581 {
582 cram_field.setToggled(1);
583 loginInfo.options.erase(p);
584 }
585
586 // Put back the remaining options
587
588 for (p=loginInfo.options.begin(),
589 e=loginInfo.options.end(); p != e; p++)
590 {
591 string s=p->first;
592
593 if (p->second.size() > 0)
594 s=s + "=" + p->second;
595
596 server += "/" + s;
597 }
598
599 server_field.setText(server);
600 }
601
602 account_field.requestFocus();
603
604 for (;;)
605 {
606 myServer::eventloop();
607
608 if (cancel_button.cancel)
609 break;
610
611 string account=account_field.getText();
612 string server=server_field.getText();
613 string userid=login_field.getText();
614
615 if (account.size() == 0 || isDupe(account, s))
616 {
617 account_field.requestFocus();
618 account_field.beepError();
619 continue;
620 }
621
622 if (server.size() == 0)
623 {
624 server_field.requestFocus();
625 server_field.beepError();
626 continue;
627 }
628
629 string newUrl
630 =mail::loginUrlEncode(loginInfo.method +
631 (ssl_field.getSelected()
632 ? "s":""),
633 server +
634 (cram_field.getSelected()
635 ? "/cram":""),
636 userid, "");
637
638 s->serverLogout();
639
640 PasswordList::passwordList.remove(s->url,
641 _("Login failed"));
642
643 AddressBook::updateAccount(s->url, newUrl);
644 SpecialFolder::updateAccount(s->url, newUrl);
645
646 s->url=newUrl;
647 s->serverName=account;
648 s->certificate=cert_field.cert;
649 break;
650 }
651
652 myServer::saveconfig();
653 Curses::keepgoing=false;
654 myServer::nextScreen= &hierarchyScreen;
655 myServer::nextScreenArg=NULL;
656 }
657
658 //
659 // Folder index screen.
660 //
661 static void folderIndexScreen(mail::ptr<myFolder> &f,
662 CursesIndexDisplay::exit_action &action);
663
664 static void getMessageNums(mail::account *acct,
665 set<string> &uid_set,
666 vector<size_t> &messageNums);
667
folderIndexScreen(void * dummy)668 void folderIndexScreen(void *dummy)
669 {
670 myFolder *f=(myFolder *)dummy;
671
672 mail::ptr<myFolder> folderPtr(f);
673
674 CursesIndexDisplay::exit_action action;
675
676
677 while (!folderPtr.isDestroyed())
678 {
679 folderIndexScreen(folderPtr, action);
680
681 if (folderPtr.isDestroyed() ||
682 action == CursesIndexDisplay::no_action)
683 break;
684
685 myServer *s=folderPtr->getServer();
686
687 if (s->server == NULL)
688 break;
689
690 // Exited folder index screen by the "Copy" command.
691 // Show the folder listing to select the destination folder,
692 // then, after copying, go back to folder index screen.
693
694 // First, save UIDs to copy.
695
696 set<string> uids;
697
698 size_t n=s->server->getFolderIndexSize();
699 size_t msgNum;
700
701 for (msgNum=0; msgNum < n; msgNum++)
702 {
703 mail::messageInfo i=s->server
704 ->getFolderIndexInfo(msgNum);
705
706 if (i.marked)
707 uids.insert(i.uid);
708 }
709
710 if (action == CursesIndexDisplay::copy_single ||
711 action == CursesIndexDisplay::move_single)
712 uids.clear();
713 // Ignore flagged uids, select UID under the cursor
714
715 // No flagged UIDs? Default to UID under the cursor.
716
717 bool resetFlags=true;
718
719 if (uids.size() == 0 &&
720 folderPtr->getCurrentMessage() < folderPtr->size())
721 {
722 msgNum=folderPtr->getServerIndex(folderPtr->
723 getCurrentMessage());
724 uids.insert(s->server->
725 getFolderIndexInfo(msgNum).uid);
726 resetFlags=false;
727 }
728
729 mail::folder *toFolderPtr;
730
731 openHierarchyScreen(Gettext(
732 action ==
733 CursesIndexDisplay::copy_single ||
734 action ==
735 CursesIndexDisplay::copy_batch ?
736 _("Select the folder to copy "
737 "%1% message(s) to.") :
738 _("Select the folder to move "
739 "%1% message(s) to."))
740 << uids.size(), (myFolder *)folderPtr,
741 &toFolderPtr, NULL);
742
743 if (myServer::nextScreen ||
744 folderPtr.isDestroyed() ||
745 s->server == NULL)
746 break;
747
748 if (toFolderPtr == NULL)
749 {
750 statusBar->clearstatus();
751 statusBar->status(_("Copy/Move cancelled."));
752 continue;
753 }
754
755
756 // Clear the marked flag, so that copied msgs are not marked
757 // in the destination folder.
758
759 mail::ptr<mail::folder> toFolder=toFolderPtr;
760
761 bool rc;
762 string errmsg;
763
764 {
765 vector<size_t> messageNums;
766
767 getMessageNums(s->server, uids, messageNums);
768
769 mail::messageInfo info;
770
771 info.marked=true;
772
773 statusBar->clearstatus();
774 statusBar->status(_("Clearing flags..."));
775
776 myServer::Callback callback;
777
778 s->server->
779 updateFolderIndexFlags(messageNums, false,
780 false,
781 info, callback);
782
783 rc=myServer::eventloop(callback);
784
785 if (s->server == NULL ||
786 folderPtr.isDestroyed() ||
787 toFolder.isDestroyed())
788 break;
789 if (!rc)
790 continue;
791 }
792
793 {
794 vector<size_t> messageNums;
795
796 getMessageNums(s->server, uids, messageNums);
797
798 statusBar->clearstatus();
799 statusBar->status(_("Copying/Moving messages..."));
800
801 myServer::Callback callback;
802
803 if (action == CursesIndexDisplay::copy_single ||
804 action == CursesIndexDisplay::copy_batch)
805 s->server->copyMessagesTo(messageNums,
806 toFolder, callback);
807 else
808 s->server->moveMessagesTo(messageNums,
809 toFolder, callback);
810
811 rc=myServer::eventloop(callback);
812 if (s->server == NULL ||
813 folderPtr.isDestroyed() ||
814 toFolder.isDestroyed())
815 break;
816
817 errmsg=callback.msg;
818 }
819
820 if (resetFlags)
821 {
822 vector<size_t> messageNums;
823
824 getMessageNums(s->server, uids, messageNums);
825
826 mail::messageInfo info;
827
828 info.marked=true;
829
830 statusBar->clearstatus();
831 statusBar->status(_("Resetting flags..."));
832
833 myServer::Callback callback;
834
835 s->server->
836 updateFolderIndexFlags(messageNums, false,
837 true,
838 info, callback);
839
840 if (!myServer::eventloop(callback))
841 {
842 errmsg=callback.msg;
843 rc=false;
844 }
845 if (s->server == NULL ||
846 folderPtr.isDestroyed() ||
847 toFolder.isDestroyed())
848 break;
849 }
850
851 if (!rc)
852 {
853 statusBar->clearstatus();
854 statusBar->status(errmsg);
855 }
856 }
857 }
858
859 // Now, we memorized the messages UIds, convert them to message numbers
860
getMessageNums(mail::account * acct,set<string> & uid_set,vector<size_t> & messageNums)861 static void getMessageNums(mail::account *acct,
862 set<string> &uid_set,
863 vector<size_t> &messageNums)
864 {
865 messageNums.clear();
866
867 size_t n=acct == NULL ? 0:acct->getFolderIndexSize();
868
869 size_t i;
870
871 for (i=0; i<n; i++)
872 if (uid_set.count(acct->getFolderIndexInfo(i).uid) > 0)
873 messageNums.push_back(i);
874 }
875
folderIndexScreen(mail::ptr<myFolder> & folderPtr,CursesIndexDisplay::exit_action & action)876 static void folderIndexScreen(mail::ptr<myFolder> &folderPtr,
877 CursesIndexDisplay::exit_action &action)
878 {
879 myFolder *f=folderPtr;
880 myServer::Callback idleOnCallback;
881
882 myServer *server=f->getServer();
883
884 // Enable immediate update notification, where available.
885
886 if (server && server->server)
887 {
888 idleOnCallback.noreport=true;
889 server->server->updateNotify(true, idleOnCallback);
890 }
891
892 CursesIndexDisplay indexScreen(mainScreen, f);
893
894 mainScreen->setFirstRowShown(f->saveFirstRowShown);
895 indexScreen.requestFocus();
896
897 AddressBook::closeAllRemote(); // Close address book when we're here...
898
899 myServer::eventloop();
900
901 action=indexScreen.action;
902
903 // Save the next screen to go to, while we're cleaning this stuff
904 // up.
905
906 void (*s)(void *)=myServer::nextScreen;
907 void *a=myServer::nextScreenArg;
908
909 if (idleOnCallback.noreport) // Took that if branch, above.
910 {
911 myServer::eventloop(idleOnCallback);
912 // Should be finished by now
913
914 myServer::nextScreen=s;
915 myServer::nextScreenArg=a;
916 }
917
918 if (!folderPtr.isDestroyed())
919 {
920 // If we're still logged on, on exit, disable update notice.
921
922 folderPtr->saveFirstRowShown=mainScreen->getFirstRowShown();
923
924 myServer *server=folderPtr->getServer();
925
926 if (server && server->server)
927 {
928 myServer::Callback offCallback;
929
930 offCallback.noreport=true;
931 server->server->updateNotify(false, offCallback);
932 myServer::eventloop(offCallback);
933 }
934
935 if (!folderPtr.isDestroyed() &&
936 (server=folderPtr->getServer()) && server->server)
937 {
938 myServer::nextScreenArg=a;
939 myServer::nextScreen=s;
940 }
941 }
942 }
943
showCurrentMessage(void * dummy)944 void showCurrentMessage(void *dummy)
945 {
946 CursesMessage *curmsg=(CursesMessage *)dummy;
947
948 curmsg->screenOpened();
949 // This would be the "previous screen",
950 // in case anyone wants to know.
951
952 CursesMessageDisplay messageScreen(mainScreen, curmsg);
953
954 messageScreen.requestFocus();
955 myServer::eventloop();
956 }
957
958 //
959 // Show attachments on a message.
960
showAttachments(void * dummy)961 void showAttachments(void *dummy)
962 {
963 CursesMessage *curmsg=(CursesMessage *)dummy;
964
965 CursesAttachmentDisplay attachmentScreen(mainScreen, curmsg);
966
967 attachmentScreen.requestFocus();
968 myServer::eventloop();
969 }
970
971 //
972 // First time at the plate, figure out where my mail is.
973 //
974
975 extern bool isMaildir(string f);
976
createconfig()977 static void createconfig()
978 {
979 string h=mail::homedir();
980
981 string d;
982 struct stat stat_buf;
983
984 if (isMaildir("Maildir"))
985 {
986 d="maildir:Maildir"; // This system uses maildirs.
987 }
988 else if ( stat((d=h + "/mail/.").c_str(), &stat_buf) == 0)
989 {
990 d="inbox:mail"; // This system uses mboxes.
991 }
992 else
993 {
994 mkdir((d=h + "/Mail").c_str(), 0700);
995 d="inbox:Mail"; // Fallback position.
996 }
997
998 // Initialize default unicode mapping.
999 myServer::setDemoronizationType(myServer::demoronizationType);
1000
1001 // An account for our default mailboxes, and an account that
1002 // points to the folder containing online help text.
1003
1004 if (tryCreateAccount(_("My E-mail"), d, "", ""))
1005 {
1006 myServer *help=tryCreateAccount(_("Online Tutorial"),
1007 "mbox:" HELPFILE, "", "");
1008 if (help)
1009 {
1010 help->logout();
1011 }
1012
1013 myServer::saveconfig();
1014 }
1015 }
1016
cleanup()1017 static void cleanup()
1018 {
1019 CursesMainScreen::Lock logoutLock(mainScreen, true);
1020 // Prevent any screen updates from taking place.
1021
1022 myServer::logout();
1023 AddressBook::closeAll();
1024 while (!myServer::server_list.empty())
1025 delete myServer::server_list.end()[-1];
1026 if (myServer::remoteConfigAccount)
1027 {
1028 myServer::remoteConfigAccount->logout();
1029 delete myServer::remoteConfigAccount;
1030 myServer::remoteConfigAccount=NULL;
1031 }
1032
1033 myServer::closePollForRefreshMessageCount();
1034 }
1035
1036 //
1037 // Graceful logout and termination
1038
quitScreen(void * dummy)1039 void quitScreen(void *dummy)
1040 {
1041 CtrlCHandler::loggingOut=true;
1042
1043 cleanup();
1044
1045 LIBMAIL_THROW(_("Have a nice day."));
1046 }
1047
1048 // Recover old config file
1049
doRecovery()1050 static void doRecovery()
1051 {
1052 vector<string> configFiles;
1053
1054 myServer::getBackupConfigFiles(configFiles);
1055
1056 vector<string>::iterator b=configFiles.begin(), e=configFiles.end();
1057
1058 bool found=false;
1059
1060 while (b != e)
1061 {
1062 struct stat stat_buf;
1063
1064 if (stat(b->c_str(), &stat_buf) < 0)
1065 {
1066 b++;
1067 continue;
1068 }
1069
1070 found=true;
1071 char tbuf[128];
1072
1073 if (strftime(tbuf, sizeof(tbuf)-1, "%F %X",
1074 localtime(&stat_buf.st_mtime)) == 0)
1075 strcpy(tbuf, "N/A");
1076
1077
1078 myServer::promptInfo promptInfo=
1079 myServer
1080 ::prompt(myServer
1081 ::promptInfo( Gettext(_("Recover configuration"
1082 " from %1%? (Y/N) "))
1083 << tbuf).yesno());
1084
1085 if (promptInfo.abortflag)
1086 LIBMAIL_THROW(((const char *)
1087 _("Recovery aborted.")));
1088
1089 if ( (string)promptInfo == "Y")
1090 {
1091 string c=myServer::getConfigFilename();
1092
1093 unlink(c.c_str());
1094 if (link(b->c_str(), c.c_str()) < 0)
1095 LIBMAIL_THROW((strerror(errno)));
1096 return;
1097 }
1098 ++b;
1099 }
1100
1101 LIBMAIL_THROW(found ? (const char *)
1102 _("No more configuration files.")
1103 : (const char *)_("No configuration files found."));
1104 }
1105
1106 extern void version();
1107
resetScreenColors()1108 void resetScreenColors()
1109 {
1110 {
1111 Curses::CursesAttr attr;
1112
1113 attr.setFgColor(color_misc_titleBar.fcolor);
1114 titleBar->setAttribute(attr);
1115 }
1116
1117 {
1118 Curses::CursesAttr attr;
1119
1120 attr.setFgColor(color_misc_statusBar.fcolor);
1121
1122 statusBar->setStatusBarAttr(attr);
1123 }
1124
1125 {
1126 Curses::CursesAttr attr;
1127
1128 attr.setFgColor(color_misc_hotKey.fcolor);
1129 statusBar->setHotKeyAttr(attr);
1130 }
1131
1132 {
1133 Curses::CursesAttr attr;
1134
1135 attr.setFgColor(color_misc_hotKeyDescr.fcolor);
1136 statusBar->setHotKeyDescr(attr);
1137 }
1138 }
1139
getRuntimeMacros()1140 Macros *Macros::getRuntimeMacros()
1141 {
1142 return macroPtr;
1143 }
1144
main(int argc,char * argv[])1145 int main(int argc, char *argv[])
1146 {
1147 int optc;
1148 int recover=0;
1149 Macros macroBuffer;
1150
1151 while ((optc=getopt(argc, argv, "vrCc:m:")) != -1)
1152 {
1153 switch (optc) {
1154 case 'v':
1155 version();
1156 break;
1157 case 'r':
1158 recover=1;
1159 break;
1160 case 'm':
1161 // try to delete if existent, ignore fail
1162 unlink(optarg);
1163 if(mkfifo(optarg, 0644) < 0)
1164 {
1165 perror("Failed to create FIFO");
1166 exit(1);
1167 }
1168 myServer::setPollForRefreshMessageCount(optarg);
1169 break;
1170 case 'c':
1171
1172 myServer::configDir=optarg;
1173 break;
1174 case 'C':
1175
1176 {
1177 ifstream i(myServer::getConfigFilename()
1178 .c_str());
1179
1180 if (!i.is_open())
1181 {
1182 cerr << strerror(errno) << endl;
1183 exit(1);
1184 }
1185
1186 int c;
1187
1188 while ((c=i.get()) != EOF)
1189 {
1190 cout << (char)c;
1191
1192 if (c == '>')
1193 cout << endl;
1194 }
1195 exit(0);
1196 }
1197 case '?':
1198 cerr << "Usage: cone [-c configdir]" << endl;
1199 exit(1);
1200 }
1201 }
1202
1203 signal(SIGPIPE, SIG_IGN);
1204 macroPtr= ¯oBuffer;
1205 init(); // Common initialization between cone and leaf.
1206
1207 // Init special characters if current display can show them.
1208
1209 {
1210 std::u32string ucbuf;
1211
1212 ucbuf.push_back(8594);
1213
1214 bool err;
1215
1216 std::string s;
1217
1218 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1219
1220 if (s.size() > 0 && !err)
1221 ucplus=urarr=ucbuf[0];
1222 else
1223 {
1224 urarr='>';
1225 ucplus='+';
1226 }
1227
1228 ucbuf[0]=8592;
1229
1230 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1231
1232 if (s.size() > 0 && !err)
1233 ularr=ucbuf[0];
1234 else
1235 ularr='<';
1236
1237 ucbuf[0]=8226;
1238
1239 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1240
1241 if (s.size() > 0 && !err)
1242 ucasterisk=ucbuf[0];
1243 else
1244 ucasterisk='*';
1245
1246 ucbuf[0]=8617;
1247
1248 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1249
1250 if (s.size() > 0 && !err)
1251 ucwrap=ucbuf[0];
1252 else
1253 ucwrap='<';
1254
1255 ucbuf[0]=8730;
1256
1257 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1258
1259 if (s.size() > 0 && !err)
1260 strncat(ucheck, s.c_str(), sizeof(ucheck)-1);
1261 else
1262 strcpy(ucheck, "*");
1263
1264 ucbuf[0]=215;
1265
1266 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1267
1268 if (s.size() > 0 && !err)
1269 strncat(udelete, s.c_str(), sizeof(udelete)-1);
1270 else
1271 strcpy(udelete, _("D"));
1272
1273 ucbuf[0]=9830;
1274
1275 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1276
1277 if (s.size() > 0 && !err)
1278 strncat(unew, s.c_str(), sizeof(unew)-1);
1279 else
1280 strcpy(unew, _("N"));
1281
1282 // Line drawing
1283
1284 ucbuf[0]=0x2500;
1285
1286 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1287
1288 if (s.size() == 0 || err)
1289 ucbuf[0]='_';
1290 uchoriz=ucbuf[0];
1291
1292 ucbuf[0]=0x2502;
1293
1294 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1295
1296 if (s.size() == 0 || err)
1297 ucbuf[0]='|';
1298 ucvert=ucbuf[0];
1299
1300 ucbuf[0]=0x2514;
1301
1302 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1303
1304 if (s.size() == 0 || err)
1305 ucbuf[0]='|';
1306 ucupright=ucbuf[0];
1307
1308 ucbuf[0]=0x251C;
1309
1310 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1311
1312 if (s.size() == 0 || err)
1313 ucbuf[0]='|';
1314 ucrighttee=ucbuf[0];
1315
1316 // Watching
1317
1318 ucbuf[0]=0x2261;
1319
1320 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1321
1322 if (s.size() == 0 || err)
1323 ucbuf[0]='o';
1324 ucwatch=ucbuf[0];
1325
1326 ucbuf[0]=0x2022;
1327
1328 s=unicode::iconvert::convert(ucbuf, unicode_default_chset(), err);
1329
1330 if (s.size() == 0 || err)
1331 ucbuf[0]='*';
1332 ucwatchend=ucbuf[0];
1333 }
1334
1335 // Default location of special folders.
1336
1337 SpecialFolder::folders.insert( make_pair(string(DRAFTS),
1338 SpecialFolder(_("Drafts"))));
1339 SpecialFolder::folders.insert( make_pair(string(SENT),
1340 SpecialFolder(_("Outbox"))));
1341
1342 mail::fd::rootCertRequiredErrMsg=
1343 _("Unable to initialize an encrypted connection because root authority certificates are not installed.\n\nIf you would like to proceed without verifying the the server's encryption certificate, append \"/novalidate-cert\" to the server's name and try again.");
1344
1345
1346 {
1347 string homedir=myServer::getConfigDir();
1348 mkdir(homedir.c_str(), 0700);
1349
1350 string lockfile=homedir + "/.lock";
1351
1352 int fd=open(lockfile.c_str(), O_RDWR|O_CREAT|O_TRUNC, 0600);
1353
1354 if (fd < 0 || fcntl(fd, F_SETFD, FD_CLOEXEC) < 0)
1355 {
1356 perror(lockfile.c_str());
1357 exit(1);
1358 }
1359
1360 if (ll_lock_ex_test(fd))
1361 {
1362 std::cerr << _("Another copy of CONE is already running.")
1363 << endl;
1364 exit(1);
1365 }
1366
1367 }
1368
1369 string errmsg="";
1370
1371 try
1372 {
1373 CursesScreen curses_screen;
1374
1375 initColorGroups();
1376
1377 CtrlCHandler ctrl_c_handler;
1378
1379 CursesTitleBar title_bar(&curses_screen, _("CONE"));
1380
1381 CursesStatusBar status_bar(&curses_screen);
1382
1383 CursesMainScreen main_screen(&curses_screen,
1384 &title_bar,
1385 &status_bar);
1386
1387 SpellChecker spell_checker("", "utf-8");
1388
1389 spellCheckerBase= &spell_checker;
1390
1391 Typeahead typeahead_buf;
1392
1393 bool welcome=true;
1394
1395 titleBar= &title_bar;
1396 statusBar= &status_bar;
1397 cursesScreen= &curses_screen;
1398 mainScreen= &main_screen;
1399
1400 resetScreenColors();
1401 if (recover)
1402 doRecovery();
1403
1404 Certificates certs;
1405
1406 myServer::certs= &certs;
1407
1408 // certs.load();
1409
1410 statusBar->status(_("Loading GnuPG encryption keys..."));
1411 statusBar->flush();
1412 GPG::gpg.init();
1413 statusBar->clearstatus();
1414
1415 Curses::setSuspendHook(&mail::account::resume);
1416
1417 // Try to load saved configuration
1418
1419 if (myServer::loadconfig() &&
1420 myServer::server_list.begin()
1421 != myServer::server_list.end())
1422 {
1423 myServer *s= *myServer::server_list.begin();
1424 string pwd;
1425
1426 // Prompt for the first listed server's password,
1427 // then try to log in.
1428
1429 if (!PasswordList::passwordList.check(s->url, pwd) ||
1430 !s->login(pwd, NULL))
1431 {
1432 s->disconnect();
1433 PasswordList::passwordList.remove(s->url,
1434 _("Login failed"));
1435
1436 // Ok, that didn't work, let's try again
1437 // and this time prompt for a password.
1438
1439 myServerLoginCallback loginCallback;
1440
1441 if (!s->login(loginCallback))
1442 {
1443 s->disconnect();
1444 statusBar->beepError();
1445 welcome=false;
1446 }
1447 else
1448 {
1449 PasswordList::passwordList
1450 .save(s->url, s->password);
1451 }
1452 }
1453 resetScreenColors();
1454
1455 } else // First batter up.
1456 {
1457 createconfig();
1458 }
1459
1460 myServer::initializeHierarchy();
1461 AddressBook::init();
1462
1463 if (welcome)
1464 {
1465 statusBar->clearstatus();
1466 statusBar->status(Gettext(_("Welcome to Cone %1% (%2%)")
1467 ) << VERSION
1468 << unicode_default_chset()
1469 << statusBar->NORMAL);
1470 }
1471
1472 hierarchyScreen(NULL); // Initial screen.
1473
1474 while (myServer::nextScreen)
1475 {
1476 void (*s)(void *)=myServer::nextScreen;
1477 void *a=myServer::nextScreenArg;
1478
1479 myServer::nextScreen=NULL;
1480 (*s)(a);
1481 }
1482
1483 cleanup();
1484
1485 } catch (const char *txt)
1486 {
1487 errmsg=txt;
1488 } catch (char *txt)
1489 {
1490 errmsg=txt;
1491 }
1492
1493 if (errmsg == "RESET") // Configuration reset
1494 {
1495 char *dummyargv[2];
1496
1497 dummyargv[0]=argv[0];
1498 dummyargv[1]=NULL;
1499
1500 execvp(argv[0], dummyargv);
1501 perror(argv[0]);
1502 exit(1);
1503 }
1504
1505 if (errmsg.size() > 0)
1506 std::cerr << errmsg << endl;
1507
1508
1509 return 0;
1510 }
1511