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= &macroBuffer;
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