1 /* $Id$ */
2 /* Copyright (c) 2006-2013 Pierre Pronchery <khorben@defora.org> */
3 /* This file is part of DeforaOS Desktop Mailer */
4 /* This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, version 3 of the License.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
15 
16 
17 
18 #include <stdarg.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <System.h>
25 #include "folder.h"
26 #include "mailer.h"
27 #include "message.h"
28 #include "account.h"
29 #include "../config.h"
30 
31 
32 /* constants */
33 #ifndef PREFIX
34 # define PREFIX		"/usr/local"
35 #endif
36 #ifndef LIBDIR
37 # define LIBDIR		PREFIX "/lib"
38 #endif
39 
40 #define ACCOUNT		"account"
41 
42 
43 /* Account */
44 /* private */
45 /* types */
46 struct _Account
47 {
48 	Mailer * mailer;
49 
50 	char * type;
51 	char * title;
52 	GtkTreeStore * store;
53 	GtkTreeRowReference * row;
54 	Plugin * plugin;
55 	AccountPluginDefinition * definition;
56 	AccountPlugin * account;
57 
58 	int enabled;
59 	AccountIdentity * identity;
60 	AccountPluginHelper helper;
61 };
62 
63 
64 /* prototypes */
65 /* accessors */
66 static gboolean _account_get_iter(Account * account, GtkTreeIter * iter);
67 static SSL_CTX * _account_helper_get_ssl_context(Account * account);
68 
69 /* useful */
70 static int _account_helper_error(Account * account, char const * message,
71 		int ret);
72 static void _account_helper_event(Account * account, AccountEvent * event);
73 static char * _account_helper_authenticate(Account * account,
74 		char const * message);
75 static int _account_helper_confirm(Account * account, char const * message);
76 static Folder * _account_helper_folder_new(Account * account,
77 		AccountFolder * folder, Folder * parent, FolderType type,
78 		char const * name);
79 static void _account_helper_folder_delete(Folder * folder);
80 static Message * _account_helper_message_new(Account * account, Folder * folder,
81 		AccountMessage * message);
82 static void _account_helper_message_delete(Message * message);
83 static int _account_helper_message_set_body(Message * message, char const * buf,
84 		size_t cnt, int append);
85 
86 
87 /* constants */
88 static const AccountPluginHelper _account_plugin_helper =
89 {
90 	NULL,
91 	_account_helper_get_ssl_context,
92 	_account_helper_error,
93 	_account_helper_event,
94 	_account_helper_authenticate,
95 	_account_helper_confirm,
96 	_account_helper_folder_new,
97 	_account_helper_folder_delete,
98 	_account_helper_message_new,
99 	_account_helper_message_delete,
100 	message_set_flag,
101 	message_set_header,
102 	_account_helper_message_set_body
103 };
104 
105 
106 /* public */
107 /* functions */
108 /* account_new */
account_new(Mailer * mailer,char const * type,char const * title,GtkTreeStore * store)109 Account * account_new(Mailer * mailer, char const * type, char const * title,
110 		GtkTreeStore * store)
111 {
112 	Account * account;
113 
114 #ifdef DEBUG
115 	fprintf(stderr, "DEBUG: %s(%p, \"%s\", \"%s\", %p)\n", __func__,
116 			(void *)mailer, type, title, (void *)store);
117 #endif
118 	if(type == NULL)
119 	{
120 		error_set_code(1, "%s", strerror(EINVAL));
121 		return NULL;
122 	}
123 	if((account = object_new(sizeof(*account))) == NULL)
124 		return NULL;
125 	memset(account, 0, sizeof(*account));
126 	account->mailer = mailer;
127 	account->type = string_new(type);
128 	if(title != NULL)
129 		account->title = string_new(title);
130 	account->plugin = plugin_new(LIBDIR, PACKAGE, "account", type);
131 	account->definition = (account->plugin != NULL)
132 		? plugin_lookup(account->plugin, "account_plugin") : NULL;
133 	/* check for errors */
134 	if(account->type == NULL || account->plugin == NULL
135 			|| (title != NULL && account->title == NULL)
136 			|| account->definition == NULL
137 			|| account->definition->init == NULL
138 			|| account->definition->destroy == NULL
139 			|| account->definition->get_config == NULL)
140 	{
141 		account_delete(account);
142 		error_set_code(1, "%s%s", "Invalid plug-in ", type);
143 		return NULL;
144 	}
145 	if(store != NULL)
146 		account_store(account, store);
147 	memcpy(&account->helper, &_account_plugin_helper,
148 			sizeof(account->helper));
149 	account->helper.account = account;
150 	account->enabled = 1;
151 	account->identity = NULL;
152 	return account;
153 }
154 
155 
156 /* account_delete */
account_delete(Account * account)157 void account_delete(Account * account)
158 {
159 	if(account->row != NULL)
160 		gtk_tree_row_reference_free(account->row);
161 	account_quit(account);
162 	string_delete(account->title);
163 	string_delete(account->type);
164 	if(account->plugin != NULL)
165 		plugin_delete(account->plugin);
166 	object_delete(account);
167 }
168 
169 
170 /* accessors */
171 /* account_get_config */
account_get_config(Account * account)172 AccountConfig const * account_get_config(Account * account)
173 {
174 	if(account->account == NULL)
175 		return account->definition->config;
176 	return account->definition->get_config(account->account);
177 }
178 
179 
180 /* account_get_enabled */
account_get_enabled(Account * account)181 int account_get_enabled(Account * account)
182 {
183 	return account->enabled;
184 }
185 
186 
187 /* account_get_folders */
account_get_folders(Account * account)188 GtkTreeStore * account_get_folders(Account * account)
189 {
190 	return account->store;
191 }
192 
193 
194 /* account_get_name */
account_get_name(Account * account)195 char const * account_get_name(Account * account)
196 {
197 	return account->definition->name;
198 }
199 
200 
201 /* account_get_title */
account_get_title(Account * account)202 char const * account_get_title(Account * account)
203 {
204 	return account->title;
205 }
206 
207 
208 /* account_get_type */
account_get_type(Account * account)209 char const * account_get_type(Account * account)
210 {
211 	return account->type;
212 }
213 
214 
215 /* account_set_enabled */
account_set_enabled(Account * account,int enabled)216 void account_set_enabled(Account * account, int enabled)
217 {
218 	account->enabled = enabled ? 1 : 0;
219 }
220 
221 
222 /* account_set_title */
account_set_title(Account * account,char const * title)223 int account_set_title(Account * account, char const * title)
224 {
225 	if(account->title != NULL)
226 		free(account->title);
227 	if((account->title = strdup(title != NULL ? title : "")) == NULL)
228 		return mailer_error(NULL, "strdup", 1);
229 	return 0;
230 }
231 
232 
233 /* useful */
234 /* account_config_load */
account_config_load(Account * account,Config * config)235 int account_config_load(Account * account, Config * config)
236 {
237 	AccountConfig * p = account_get_config(account);
238 	char const * value;
239 	char * q;
240 	long l;
241 
242 #ifdef DEBUG
243 	fprintf(stderr, "DEBUG: %s(\"%s\", %p)\n", __func__, account->title,
244 			(void *)config);
245 #endif
246 	if(p == NULL || account->title == NULL)
247 		return 0;
248 	for(; p->name != NULL; p++)
249 	{
250 		if((value = config_get(config, account->title, p->name))
251 				== NULL)
252 			continue;
253 		switch(p->type)
254 		{
255 			case ACT_PASSWORD:
256 				/* FIXME unscramble */
257 			case ACT_FILE:
258 			case ACT_STRING:
259 				free(p->value);
260 				p->value = strdup(value);
261 				break;
262 			case ACT_UINT16:
263 				l = strtol(value, &q, 0);
264 				if(value[0] != '\0' && *q == '\0')
265 					p->value = (void *)l;
266 				break;
267 			case ACT_BOOLEAN:
268 				p->value = (strcmp(value, "yes") == 0
269 						|| strcmp(value, "1") == 0)
270 					? (void *)1 : NULL;
271 				break;
272 			case ACT_NONE:
273 			case ACT_SEPARATOR:
274 				break;
275 		}
276 	}
277 	return 0;
278 }
279 
280 
281 /* account_config_save */
account_config_save(Account * account,Config * config)282 int account_config_save(Account * account, Config * config)
283 {
284 	AccountConfig * p = account_get_config(account);
285 	uint16_t u16;
286 	char buf[6];
287 
288 #ifdef DEBUG
289 	fprintf(stderr, "DEBUG: %s(\"%s\", %p)\n", __func__, account->title,
290 			(void *)config);
291 #endif
292 	if(account->title == NULL)
293 		return 0;
294 	if(config_set(config, account->title, "type", account->type) != 0)
295 		return 1;
296 	if(p == NULL)
297 		return 0;
298 	for(; p->name != NULL; p++)
299 	{
300 		switch(p->type)
301 		{
302 			case ACT_PASSWORD:
303 				/* FIXME scramble */
304 			case ACT_FILE:
305 			case ACT_STRING:
306 				if(config_set(config, account->title, p->name,
307 							p->value) != 0)
308 					return 1;
309 				break;
310 			case ACT_UINT16:
311 				u16 = (uint16_t)p->value;
312 				snprintf(buf, sizeof(buf), "%hu", u16);
313 				if(config_set(config, account->title, p->name,
314 							buf) != 0)
315 					return 1;
316 				break;
317 			case ACT_BOOLEAN:
318 				if(config_set(config, account->title, p->name,
319 							(p->value != NULL) ? "1"
320 							: "0") != 0)
321 					return 1;
322 				break;
323 			case ACT_NONE:
324 			case ACT_SEPARATOR:
325 				break;
326 		}
327 	}
328 	return 0;
329 }
330 
331 
332 /* account_init */
account_init(Account * account)333 int account_init(Account * account)
334 {
335 #ifdef DEBUG
336 	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, account->title);
337 #endif
338 	if(account->account != NULL)
339 		return 0;
340 	account->account = account->definition->init(&account->helper);
341 	return (account->account != NULL) ? 0 : -1;
342 }
343 
344 
345 /* account_quit */
account_quit(Account * account)346 int account_quit(Account * account)
347 {
348 	if(account->definition != NULL && account->account != NULL)
349 		account->definition->destroy(account->account);
350 	account->account = NULL;
351 	return 0;
352 }
353 
354 
355 /* account_refresh */
account_refresh(Account * account)356 void account_refresh(Account * account)
357 {
358 	account_stop(account);
359 	account_start(account);
360 }
361 
362 
363 /* account_select */
account_select(Account * account,Folder * folder,Message * message)364 GtkTextBuffer * account_select(Account * account, Folder * folder,
365 		Message * message)
366 {
367 	AccountFolder * af;
368 	AccountMessage * am = NULL;
369 
370 #ifdef DEBUG
371 	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", %p)\n", __func__,
372 			account_get_name(account), folder_get_name(folder),
373 			(void *)message);
374 #endif
375 	if((af = folder_get_data(folder)) == NULL)
376 		return NULL;
377 	if(message != NULL && (am = message_get_data(message)) == NULL)
378 		return NULL;
379 	if(account->definition->refresh != NULL
380 			&& account->definition->refresh(account->account, af,
381 				am) != 0)
382 		return NULL;
383 	return (message != NULL) ? message_get_body(message) : NULL;
384 }
385 
386 
387 /* account_select_source */
account_select_source(Account * account,Folder * folder,Message * message)388 GtkTextBuffer * account_select_source(Account * account, Folder * folder,
389 		Message * message)
390 {
391 	GtkTextBuffer * ret;
392 	char * p;
393 
394 #ifdef DEBUG
395 	fprintf(stderr, "DEBUG: %s(\"%s\", %p)\n", __func__,
396 			folder_get_name(folder), (void *)message);
397 #endif
398 	if(account->definition->get_source == NULL)
399 		return NULL;
400 	ret = gtk_text_buffer_new(NULL);
401 	if((p = account->definition->get_source(account->account,
402 					folder_get_data(folder),
403 					message_get_data(message))) != NULL)
404 	{
405 		gtk_text_buffer_set_text(ret, p, -1);
406 		free(p);
407 	}
408 	return ret;
409 }
410 
411 
412 /* account_start */
account_start(Account * account)413 int account_start(Account * account)
414 {
415 #ifdef DEBUG
416 	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, account->title);
417 #endif
418 	if(account->account == NULL
419 			&& account_init(account) != 0)
420 		return -1;
421 	if(account->definition->start == NULL)
422 		return 0;
423 	return account->definition->start(account->account);
424 }
425 
426 
427 /* account_stop */
account_stop(Account * account)428 void account_stop(Account * account)
429 {
430 	if(account->definition->stop == NULL)
431 		return;
432 	account->definition->stop(account->account);
433 }
434 
435 
436 /* account_store */
account_store(Account * account,GtkTreeStore * store)437 void account_store(Account * account, GtkTreeStore * store)
438 {
439 	GtkIconTheme * theme;
440 	GdkPixbuf * pixbuf;
441 	GtkTreeIter iter;
442 	GtkTreePath * path;
443 
444 	if(account->store != NULL)
445 		return;
446 	account->store = store;
447 	theme = gtk_icon_theme_get_default();
448 	pixbuf = gtk_icon_theme_load_icon(theme, "mailer-accounts", 16, 0,
449 			NULL);
450 	gtk_tree_store_append(store, &iter, NULL);
451 	gtk_tree_store_set(store, &iter, MFC_ACCOUNT, account, MFC_ICON, pixbuf,
452 			MFC_NAME, account->title, -1);
453 	path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
454 	account->row = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), path);
455 	gtk_tree_path_free(path);
456 }
457 
458 
459 /* private */
460 /* functions */
461 /* accessors */
462 /* account_get_iter */
_account_get_iter(Account * account,GtkTreeIter * iter)463 static gboolean _account_get_iter(Account * account, GtkTreeIter * iter)
464 {
465 	GtkTreePath * path;
466 
467 	if(account->row == NULL || (path = gtk_tree_row_reference_get_path(
468 					account->row)) == NULL)
469 		return FALSE;
470 	return gtk_tree_model_get_iter(GTK_TREE_MODEL(account->store), iter,
471 			path);
472 }
473 
474 
475 /* account_helper_get_ssl_context */
_account_helper_get_ssl_context(Account * account)476 static SSL_CTX * _account_helper_get_ssl_context(Account * account)
477 {
478 	return mailer_get_ssl_context(account->mailer);
479 }
480 
481 
482 /* useful */
483 /* account_helper_error */
_account_helper_error(Account * account,char const * message,int ret)484 static int _account_helper_error(Account * account, char const * message,
485 		int ret)
486 {
487 	Mailer * mailer = (account != NULL) ? account->mailer : NULL;
488 	size_t len;
489 	char * p;
490 
491 	if(account != NULL)
492 	{
493 		len = strlen(account->title) + strlen(message) + 3;
494 		if((p = malloc(len)) != NULL)
495 		{
496 			snprintf(p, len, "%s: %s", account->title, message);
497 			mailer_set_status(mailer, p);
498 			free(p);
499 			return ret;
500 		}
501 	}
502 	return mailer_error(mailer, message, ret);
503 }
504 
505 
506 /* account_helper_event */
507 static void _helper_event_status(Account * account, AccountEvent * event);
508 
_account_helper_event(Account * account,AccountEvent * event)509 static void _account_helper_event(Account * account, AccountEvent * event)
510 {
511 	switch(event->type)
512 	{
513 		case AET_STARTED:
514 		case AET_STOPPED:
515 			/* FIXME forward this information */
516 			break;
517 		case AET_STATUS:
518 			_helper_event_status(account, event);
519 			break;
520 	}
521 }
522 
_helper_event_status(Account * account,AccountEvent * event)523 static void _helper_event_status(Account * account, AccountEvent * event)
524 {
525 	Mailer * mailer = account->mailer;
526 	char const * message = event->status.message;
527 
528 	if(message == NULL)
529 		switch(event->status.status)
530 		{
531 			case AS_IDLE:
532 				message = "Ready";
533 				break;
534 			default:
535 				break;
536 		}
537 	if(message == NULL)
538 		return;
539 	mailer_set_status(mailer, message);
540 }
541 
542 
543 /* account_helper_authenticate */
_account_helper_authenticate(Account * account,char const * message)544 static char * _account_helper_authenticate(Account * account,
545 		char const * message)
546 {
547 	char * ret = NULL;
548 	GtkWidget * dialog;
549 	GtkWidget * vbox;
550 	GtkWidget * widget;
551 
552 	dialog = gtk_dialog_new();
553 	/* XXX translate this, enumerate the methods available */
554 	gtk_window_set_title(GTK_WINDOW(dialog), "Authentication");
555 #if GTK_CHECK_VERSION(2, 14, 0)
556 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
557 #else
558 	vbox = GTK_DIALOG(dialog)->vbox;
559 #endif
560 	widget = gtk_label_new(message);
561 	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
562 	widget = gtk_entry_new();
563 	gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
564 	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
565 	gtk_dialog_add_buttons(GTK_DIALOG(dialog),
566 			GTK_STOCK_OK, GTK_RESPONSE_OK,
567 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
568 	gtk_widget_show_all(vbox);
569 	if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
570 		ret = strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
571 	gtk_widget_destroy(dialog);
572 	return ret;
573 }
574 
575 
576 /* account_helper_confirm */
_account_helper_confirm(Account * account,char const * message)577 static int _account_helper_confirm(Account * account, char const * message)
578 {
579 	int ret;
580 	GtkWidget * dialog;
581 
582 	/* XXX set mailer's main window as the parent? */
583 	dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
584 			GTK_BUTTONS_YES_NO,
585 #if GTK_CHECK_VERSION(2, 6, 0)
586 			"%s", "Confirm");
587 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
588 #endif
589 			"%s", message);
590 	/* XXX translate this */
591 	gtk_window_set_title(GTK_WINDOW(dialog), "Confirm");
592 	ret = (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) ? 0 : 1;
593 	gtk_widget_destroy(dialog);
594 	return ret;
595 }
596 
597 
598 /* account_helper_folder_new */
_account_helper_folder_new(Account * account,AccountFolder * folder,Folder * parent,FolderType type,char const * name)599 static Folder * _account_helper_folder_new(Account * account,
600 		AccountFolder * folder, Folder * parent, FolderType type,
601 		char const * name)
602 {
603 	Folder * ret = NULL;
604 	GtkTreeModel * model = GTK_TREE_MODEL(account->store);
605 	GtkTreeIter aiter;
606 	GtkTreeIter * paiter = NULL;
607 	GtkTreeIter piter;
608 	GtkTreeIter * ppiter = NULL;
609 	GtkTreeIter siter;
610 	GtkTreeIter * psiter = NULL;
611 	GtkTreeIter iter;
612 	gint i;
613 
614 #ifdef DEBUG
615 	fprintf(stderr, "DEBUG: %s(\"%s\", %p, %p, %u, \"%s\")\n", __func__,
616 			account->title, (void *)folder, (void *)parent, type,
617 			name);
618 #endif
619 	if(account->row == NULL)
620 		return NULL;
621 	/* lookup the account */
622 	if(_account_get_iter(account, &aiter) == TRUE)
623 		paiter = &aiter;
624 	/* lookup the parent folder */
625 	if(parent != NULL && folder_get_iter(parent, &piter) == TRUE)
626 		ppiter = &piter;
627 	else
628 		ppiter = paiter;
629 	/* lookup the following folder in sort order */
630 	if(ppiter != NULL)
631 		for(i = 0; gtk_tree_model_iter_nth_child(model, &siter, ppiter,
632 					i) != FALSE; i++)
633 		{
634 			psiter = &siter;
635 			gtk_tree_model_get(model, &siter, MFC_FOLDER, &ret, -1);
636 			if(type == FT_INBOX && folder_get_type(ret) != FT_INBOX)
637 				break;
638 			if(type < folder_get_type(ret))
639 				break;
640 			if(folder_get_type(ret) == type && strcmp(name,
641 						folder_get_name(ret)) < 0)
642 				break;
643 			psiter = NULL;
644 		}
645 	/* insert the folder in the model */
646 	gtk_tree_store_insert_before(account->store, &iter, ppiter, psiter);
647 	/* actually register the folder */
648 	if((ret = folder_new(folder, type, name, account->store, &iter))
649 			== NULL)
650 		gtk_tree_store_remove(account->store, &iter);
651 	else
652 		gtk_tree_store_set(account->store, &iter, MFC_ACCOUNT, account,
653 				-1);
654 	return ret;
655 }
656 
657 
658 /* account_helper_folder_delete */
_account_helper_folder_delete(Folder * folder)659 static void _account_helper_folder_delete(Folder * folder)
660 {
661 	/* FIXME remove from the account */
662 	folder_delete(folder);
663 }
664 
665 
666 /* account_helper_message_new */
_account_helper_message_new(Account * account,Folder * folder,AccountMessage * message)667 static Message * _account_helper_message_new(Account * account, Folder * folder,
668 		AccountMessage * message)
669 {
670 	Message * ret;
671 	GtkTreeStore * store;
672 	GtkTreeIter iter;
673 
674 #ifdef DEBUG
675 	fprintf(stderr, "DEBUG: %s()\n", __func__);
676 #endif
677 	if(folder == NULL)
678 		return message_new(message, NULL, NULL);
679 	store = folder_get_messages(folder);
680 	gtk_tree_store_append(store, &iter, NULL);
681 	if((ret = message_new(message, store, &iter)) == NULL)
682 		gtk_tree_store_remove(store, &iter);
683 	else
684 	{
685 		gtk_tree_store_set(store, &iter, MHC_ACCOUNT, account,
686 				MHC_FOLDER, folder, -1);
687 		mailer_set_status(account->mailer, NULL);
688 	}
689 	return ret;
690 }
691 
692 
693 /* account_helper_message_delete */
_account_helper_message_delete(Message * message)694 static void _account_helper_message_delete(Message * message)
695 {
696 	GtkTreeStore * store;
697 	GtkTreeIter iter;
698 
699 	if((store = message_get_store(message)) != NULL
700 			&& message_get_iter(message, &iter) != FALSE)
701 		gtk_tree_store_remove(store, &iter);
702 	message_delete(message);
703 }
704 
705 
706 /* account_helper_message_set_body */
_account_helper_message_set_body(Message * message,char const * buf,size_t cnt,int append)707 static int _account_helper_message_set_body(Message * message, char const * buf,
708 		size_t cnt, int append)
709 {
710 	return message_set_body(message, buf, cnt, (append != 0) ? TRUE
711 			: FALSE);
712 }
713