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