1 /*
2 * e-mail-ui-session.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 *
17 * Authors:
18 * Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 * Copyright (C) 2009 Intel Corporation
22 *
23 */
24
25 /* mail-session.c: handles the session information and resource manipulation */
26
27 #include "evolution-config.h"
28
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include <glib/gi18n.h>
34 #include <glib/gstdio.h>
35
36 #include <gtk/gtk.h>
37
38 #ifdef HAVE_CANBERRA
39 #include <canberra-gtk.h>
40 #endif
41
42 #include <libebackend/libebackend.h>
43
44 #include "e-mail-account-store.h"
45
46 #include "e-util/e-util.h"
47 #include "e-util/e-util-private.h"
48
49 #include "shell/e-shell.h"
50 #include "shell/e-shell-view.h"
51 #include "shell/e-shell-content.h"
52 #include "shell/e-shell-window.h"
53
54 #include "e-mail-ui-session.h"
55 #include "em-composer-utils.h"
56 #include "em-filter-context.h"
57 #include "em-vfolder-editor-context.h"
58 #include "em-filter-rule.h"
59 #include "em-utils.h"
60 #include "mail-send-recv.h"
61
62 #define E_MAIL_UI_SESSION_GET_PRIVATE(obj) \
63 (G_TYPE_INSTANCE_GET_PRIVATE \
64 ((obj), E_TYPE_MAIL_UI_SESSION, EMailUISessionPrivate))
65
66 #ifdef HAVE_CANBERRA
67 static ca_context *cactx = NULL;
68 #endif
69
70 static gint eca_debug = -1;
71
72 typedef struct _SourceContext SourceContext;
73
74 struct _EMailUISessionPrivate {
75 FILE *filter_logfile;
76 ESourceRegistry *registry;
77 EMailAccountStore *account_store;
78 EMailLabelListStore *label_store;
79 EPhotoCache *photo_cache;
80 gboolean check_junk;
81
82 GSList *address_cache; /* data is AddressCacheData struct */
83 GMutex address_cache_mutex;
84 };
85
86 enum {
87 PROP_0,
88 PROP_ACCOUNT_STORE,
89 PROP_CHECK_JUNK,
90 PROP_LABEL_STORE,
91 PROP_PHOTO_CACHE
92 };
93
94 enum {
95 ACTIVITY_ADDED,
96 LAST_SIGNAL
97 };
98
99 static guint signals[LAST_SIGNAL];
100
101 G_DEFINE_TYPE_WITH_CODE (
102 EMailUISession,
103 e_mail_ui_session,
104 E_TYPE_MAIL_SESSION,
105 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
106
107 struct _SourceContext {
108 EMailUISession *session;
109 CamelService *service;
110 };
111
112 typedef struct _AddressCacheData {
113 gchar *email_address;
114 gint64 stamp; /* when it was added to cache, in microseconds */
115 gboolean is_known;
116 } AddressCacheData;
117
118 static void
address_cache_data_free(gpointer pdata)119 address_cache_data_free (gpointer pdata)
120 {
121 AddressCacheData *data = pdata;
122
123 if (data) {
124 g_free (data->email_address);
125 g_free (data);
126 }
127 }
128
129 static GSList *
address_cache_data_remove_old_and_test(GSList * items,const gchar * email_address,gboolean * found,gboolean * is_known)130 address_cache_data_remove_old_and_test (GSList *items,
131 const gchar *email_address,
132 gboolean *found,
133 gboolean *is_known)
134 {
135 gint64 old_when;
136 GSList *iter, *prev = NULL;
137
138 if (!items)
139 return NULL;
140
141 /* let the cache value live for 5 minutes */
142 old_when = g_get_real_time () - 5 * 60 * 1000 * 1000;
143
144 for (iter = items; iter; prev = iter, iter = iter->next) {
145 AddressCacheData *data = iter->data;
146
147 if (!data || data->stamp <= old_when || !data->email_address)
148 break;
149
150 if (g_ascii_strcasecmp (email_address, data->email_address) == 0) {
151 *found = TRUE;
152 *is_known = data->is_known;
153
154 /* a match was found, shorten the list later */
155 return items;
156 }
157 }
158
159 g_slist_free_full (iter, address_cache_data_free);
160 if (prev)
161 prev->next = NULL;
162 else
163 items = NULL;
164
165 return items;
166 }
167
168 /* Support for CamelSession.get_filter_driver () *****************************/
169
170 static CamelFolder *
get_folder(CamelFilterDriver * d,const gchar * uri,gpointer user_data,GError ** error)171 get_folder (CamelFilterDriver *d,
172 const gchar *uri,
173 gpointer user_data,
174 GError **error)
175 {
176 EMailSession *session = E_MAIL_SESSION (user_data);
177
178 /* FIXME Not passing a GCancellable here. */
179 /* FIXME Need a camel_filter_driver_get_session(). */
180 return e_mail_session_uri_to_folder_sync (
181 session, uri, 0, NULL, error);
182 }
183
184 static gboolean
session_play_sound_cb(const gchar * filename)185 session_play_sound_cb (const gchar *filename)
186 {
187 if (eca_debug == -1)
188 eca_debug = g_strcmp0 (g_getenv ("ECA_DEBUG"), "1") == 0 ? 1 : 0;
189
190 #ifdef HAVE_CANBERRA
191 if (filename && *filename) {
192 gint err;
193
194 if (!cactx) {
195 ca_context_create (&cactx);
196 ca_context_change_props (cactx,
197 CA_PROP_APPLICATION_NAME, "Evolution",
198 NULL);
199 }
200
201 err = ca_context_play (
202 cactx, 0,
203 CA_PROP_MEDIA_FILENAME, filename,
204 NULL);
205
206 if (eca_debug) {
207 if (err != 0)
208 e_util_debug_print ("ECA", "Session Play Sound: Failed to play '%s': %s\n", filename, ca_strerror (err));
209 else
210 e_util_debug_print ("ECA", "Session Play Sound: Played file '%s'\n", filename);
211 }
212 } else
213 #else
214 if (eca_debug)
215 e_util_debug_print ("ECA", "Session Play Sound: Cannot play sound, not compiled with libcanberra\n");
216 #endif
217 gdk_display_beep (gdk_display_get_default ());
218
219 return FALSE;
220 }
221
222 static void
session_play_sound(CamelFilterDriver * driver,const gchar * filename,gpointer user_data)223 session_play_sound (CamelFilterDriver *driver,
224 const gchar *filename,
225 gpointer user_data)
226 {
227 g_idle_add_full (
228 G_PRIORITY_DEFAULT_IDLE,
229 (GSourceFunc) session_play_sound_cb,
230 g_strdup (filename), (GDestroyNotify) g_free);
231 }
232
233 static void
session_system_beep(CamelFilterDriver * driver,gpointer user_data)234 session_system_beep (CamelFilterDriver *driver,
235 gpointer user_data)
236 {
237 g_idle_add ((GSourceFunc) session_play_sound_cb, NULL);
238 }
239
240 static gboolean
session_folder_can_filter_junk(CamelFolder * folder)241 session_folder_can_filter_junk (CamelFolder *folder)
242 {
243 if (!folder)
244 return TRUE;
245
246 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), TRUE);
247
248 return (camel_folder_get_flags (folder) & CAMEL_FOLDER_FILTER_JUNK) != 0;
249 }
250
251 static CamelFilterDriver *
main_get_filter_driver(CamelSession * session,const gchar * type,CamelFolder * for_folder,GError ** error)252 main_get_filter_driver (CamelSession *session,
253 const gchar *type,
254 CamelFolder *for_folder,
255 GError **error)
256 {
257 EMailSession *ms = E_MAIL_SESSION (session);
258 CamelFilterDriver *driver;
259 EFilterRule *rule = NULL;
260 const gchar *config_dir;
261 gchar *user, *system;
262 GSettings *settings;
263 ERuleContext *fc;
264 EMailUISessionPrivate *priv;
265 gboolean add_junk_test;
266
267 priv = E_MAIL_UI_SESSION_GET_PRIVATE (session);
268
269 settings = e_util_ref_settings ("org.gnome.evolution.mail");
270
271 config_dir = mail_session_get_config_dir ();
272 user = g_build_filename (config_dir, "filters.xml", NULL);
273 system = g_build_filename (EVOLUTION_PRIVDATADIR, "filtertypes.xml", NULL);
274 fc = (ERuleContext *) em_filter_context_new (ms);
275 e_rule_context_load (fc, system, user);
276 g_free (system);
277 g_free (user);
278
279 driver = camel_filter_driver_new (session);
280 camel_filter_driver_set_folder_func (driver, get_folder, session);
281
282 if (g_settings_get_boolean (settings, "filters-log-actions") ||
283 camel_debug ("filters")) {
284 if (!priv->filter_logfile &&
285 g_settings_get_boolean (settings, "filters-log-actions")) {
286 gchar *filename;
287
288 filename = g_settings_get_string (settings, "filters-log-file");
289 if (filename) {
290 if (!*filename || g_strcmp0 (filename, "stdout") == 0)
291 priv->filter_logfile = stdout;
292 else
293 priv->filter_logfile = g_fopen (filename, "a+");
294
295 g_free (filename);
296 }
297 } else if (!priv->filter_logfile) {
298 priv->filter_logfile = stdout;
299 }
300
301 if (priv->filter_logfile)
302 camel_filter_driver_set_logfile (driver, priv->filter_logfile);
303 }
304
305 camel_filter_driver_set_shell_func (driver, mail_execute_shell_command, NULL);
306 camel_filter_driver_set_play_sound_func (driver, session_play_sound, NULL);
307 camel_filter_driver_set_system_beep_func (driver, session_system_beep, NULL);
308
309 add_junk_test = g_str_equal (type, E_FILTER_SOURCE_JUNKTEST) || (
310 priv->check_junk &&
311 g_str_equal (type, E_FILTER_SOURCE_INCOMING) &&
312 session_folder_can_filter_junk (for_folder));
313
314 if (add_junk_test) {
315 /* implicit junk check as 1st rule */
316 camel_filter_driver_add_rule (
317 driver, "Junk check", "(junk-test)",
318 "(begin (set-system-flag \"junk\"))");
319 }
320
321 if (strcmp (type, E_FILTER_SOURCE_JUNKTEST) != 0) {
322 GString *fsearch, *faction;
323
324 fsearch = g_string_new ("");
325 faction = g_string_new ("");
326
327 if (!strcmp (type, E_FILTER_SOURCE_DEMAND))
328 type = E_FILTER_SOURCE_INCOMING;
329
330 /* add the user-defined rules next */
331 while ((rule = e_rule_context_next_rule (fc, rule, type))) {
332 g_string_truncate (fsearch, 0);
333 g_string_truncate (faction, 0);
334
335 /* skip disabled rules */
336 if (!rule->enabled)
337 continue;
338
339 e_filter_rule_build_code (rule, fsearch);
340 em_filter_rule_build_action (
341 EM_FILTER_RULE (rule), faction);
342 camel_filter_driver_add_rule (
343 driver, rule->name,
344 fsearch->str, faction->str);
345 }
346
347 g_string_free (fsearch, TRUE);
348 g_string_free (faction, TRUE);
349 }
350
351 g_object_unref (fc);
352
353 g_object_unref (settings);
354
355 return driver;
356 }
357
358 static void
source_context_free(SourceContext * context)359 source_context_free (SourceContext *context)
360 {
361 if (context->session != NULL)
362 g_object_unref (context->session);
363
364 if (context->service != NULL)
365 g_object_unref (context->service);
366
367 g_slice_free (SourceContext, context);
368 }
369
370 static gboolean
mail_ui_session_add_service_cb(SourceContext * context)371 mail_ui_session_add_service_cb (SourceContext *context)
372 {
373 EMailAccountStore *store;
374
375 /* The CamelService should be fully initialized by now. */
376 store = e_mail_ui_session_get_account_store (context->session);
377 e_mail_account_store_add_service (store, context->service);
378
379 return FALSE;
380 }
381
382 static void
mail_ui_session_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)383 mail_ui_session_set_property (GObject *object,
384 guint property_id,
385 const GValue *value,
386 GParamSpec *pspec)
387 {
388 switch (property_id) {
389 case PROP_CHECK_JUNK:
390 e_mail_ui_session_set_check_junk (
391 E_MAIL_UI_SESSION (object),
392 g_value_get_boolean (value));
393 return;
394 }
395
396 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
397 }
398
399 static void
mail_ui_session_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)400 mail_ui_session_get_property (GObject *object,
401 guint property_id,
402 GValue *value,
403 GParamSpec *pspec)
404 {
405 switch (property_id) {
406 case PROP_ACCOUNT_STORE:
407 g_value_set_object (
408 value,
409 e_mail_ui_session_get_account_store (
410 E_MAIL_UI_SESSION (object)));
411 return;
412
413 case PROP_CHECK_JUNK:
414 g_value_set_boolean (
415 value,
416 e_mail_ui_session_get_check_junk (
417 E_MAIL_UI_SESSION (object)));
418 return;
419
420 case PROP_LABEL_STORE:
421 g_value_set_object (
422 value,
423 e_mail_ui_session_get_label_store (
424 E_MAIL_UI_SESSION (object)));
425 return;
426
427 case PROP_PHOTO_CACHE:
428 g_value_set_object (
429 value,
430 e_mail_ui_session_get_photo_cache (
431 E_MAIL_UI_SESSION (object)));
432 return;
433 }
434
435 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
436 }
437
438 static void
mail_ui_session_dispose(GObject * object)439 mail_ui_session_dispose (GObject *object)
440 {
441 EMailUISessionPrivate *priv;
442
443 priv = E_MAIL_UI_SESSION_GET_PRIVATE (object);
444 g_clear_object (&priv->registry);
445
446 if (priv->account_store != NULL) {
447 e_mail_account_store_clear (priv->account_store);
448 g_object_unref (priv->account_store);
449 priv->account_store = NULL;
450 }
451
452 g_clear_object (&priv->label_store);
453 g_clear_object (&priv->photo_cache);
454
455 g_mutex_lock (&priv->address_cache_mutex);
456 g_slist_free_full (priv->address_cache, address_cache_data_free);
457 priv->address_cache = NULL;
458 g_mutex_unlock (&priv->address_cache_mutex);
459
460 /* Chain up to parent's dispose() method. */
461 G_OBJECT_CLASS (e_mail_ui_session_parent_class)->dispose (object);
462 }
463
464 static void
mail_ui_session_finalize(GObject * object)465 mail_ui_session_finalize (GObject *object)
466 {
467 EMailUISessionPrivate *priv;
468
469 priv = E_MAIL_UI_SESSION_GET_PRIVATE (object);
470
471 g_mutex_clear (&priv->address_cache_mutex);
472
473 #ifdef HAVE_CANBERRA
474 g_clear_pointer (&cactx, ca_context_destroy);
475 #endif
476
477 /* Chain up to parent's method. */
478 G_OBJECT_CLASS (e_mail_ui_session_parent_class)->finalize (object);
479 }
480
481 static void
mail_ui_session_constructed(GObject * object)482 mail_ui_session_constructed (GObject *object)
483 {
484 EMailUISessionPrivate *priv;
485 EMFolderTreeModel *folder_tree_model;
486 ESourceRegistry *registry;
487 EClientCache *client_cache;
488 EMailSession *session;
489 EShell *shell;
490
491 session = E_MAIL_SESSION (object);
492 shell = e_shell_get_default ();
493
494 /* synchronize online state first, before any CamelService is created */
495 e_binding_bind_property (
496 shell, "online",
497 session, "online",
498 G_BINDING_SYNC_CREATE);
499
500 priv = E_MAIL_UI_SESSION_GET_PRIVATE (object);
501 priv->account_store = e_mail_account_store_new (session);
502
503 /* Keep our own reference to the ESourceRegistry so we
504 * can easily disconnect signal handlers in dispose(). */
505 registry = e_mail_session_get_registry (session);
506 priv->registry = g_object_ref (registry);
507
508 client_cache = e_shell_get_client_cache (shell);
509 priv->photo_cache = e_photo_cache_new (client_cache);
510
511 /* XXX Make sure the folder tree model is created before we
512 * add built-in CamelStores so it gets signals from the
513 * EMailAccountStore.
514 *
515 * XXX This is creating a circular reference. Perhaps the
516 * model should only hold a weak pointer to EMailSession?
517 *
518 * FIXME EMailSession should just own the default instance.
519 */
520 folder_tree_model = em_folder_tree_model_get_default ();
521 em_folder_tree_model_set_session (folder_tree_model, session);
522
523 /* Chain up to parent's constructed() method. */
524 G_OBJECT_CLASS (e_mail_ui_session_parent_class)->constructed (object);
525 }
526
527 static CamelService *
mail_ui_session_add_service(CamelSession * session,const gchar * uid,const gchar * protocol,CamelProviderType type,GError ** error)528 mail_ui_session_add_service (CamelSession *session,
529 const gchar *uid,
530 const gchar *protocol,
531 CamelProviderType type,
532 GError **error)
533 {
534 CamelService *service;
535
536 /* Chain up to parent's constructed() method. */
537 service = CAMEL_SESSION_CLASS (e_mail_ui_session_parent_class)->
538 add_service (session, uid, protocol, type, error);
539
540 /* Inform the EMailAccountStore of the new CamelService
541 * from an idle callback so the service has a chance to
542 * fully initialize first. */
543 if (CAMEL_IS_STORE (service)) {
544 SourceContext *context;
545
546 context = g_slice_new0 (SourceContext);
547 context->session = E_MAIL_UI_SESSION (g_object_ref (session));
548 context->service = g_object_ref (service);
549
550 /* Prioritize ahead of GTK+ redraws. */
551 g_idle_add_full (
552 G_PRIORITY_HIGH_IDLE,
553 (GSourceFunc) mail_ui_session_add_service_cb,
554 context, (GDestroyNotify) source_context_free);
555 }
556
557 return service;
558 }
559
560 static void
mail_ui_session_remove_service(CamelSession * session,CamelService * service)561 mail_ui_session_remove_service (CamelSession *session,
562 CamelService *service)
563 {
564 EMailAccountStore *store;
565 EMailUISession *ui_session;
566
567 /* Passing a NULL parent window skips confirmation prompts. */
568 ui_session = E_MAIL_UI_SESSION (session);
569 store = e_mail_ui_session_get_account_store (ui_session);
570 e_mail_account_store_remove_service (store, NULL, service);
571 }
572
573 static CamelFilterDriver *
mail_ui_session_get_filter_driver(CamelSession * session,const gchar * type,CamelFolder * for_folder,GError ** error)574 mail_ui_session_get_filter_driver (CamelSession *session,
575 const gchar *type,
576 CamelFolder *for_folder,
577 GError **error)
578 {
579 return (CamelFilterDriver *) mail_call_main (
580 MAIL_CALL_p_pppp, (MailMainFunc) main_get_filter_driver,
581 session, type, for_folder, error);
582 }
583
584 static gboolean
mail_ui_session_lookup_addressbook(CamelSession * session,const gchar * name)585 mail_ui_session_lookup_addressbook (CamelSession *session,
586 const gchar *name)
587 {
588 CamelInternetAddress *cia;
589 gboolean known_address = FALSE;
590
591 /* FIXME CamelSession's lookup_addressbook() method needs redone.
592 * No GCancellable provided, no means of reporting an error. */
593
594 if (!mail_config_get_lookup_book ())
595 return FALSE;
596
597 cia = camel_internet_address_new ();
598
599 if (camel_address_decode (CAMEL_ADDRESS (cia), name) > 0) {
600 GError *error = NULL;
601
602 e_mail_ui_session_check_known_address_sync (
603 E_MAIL_UI_SESSION (session), cia,
604 mail_config_get_lookup_book_local_only (),
605 NULL, &known_address, &error);
606
607 if (error != NULL) {
608 g_warning ("%s: %s", G_STRFUNC, error->message);
609 g_error_free (error);
610 }
611 } else {
612 g_warning (
613 "%s: Failed to decode internet "
614 "address '%s'", G_STRFUNC, name);
615 }
616
617 g_object_unref (cia);
618
619 return known_address;
620 }
621
622 static void
mail_ui_session_user_alert(CamelSession * session,CamelService * service,CamelSessionAlertType type,const gchar * alert_message)623 mail_ui_session_user_alert (CamelSession *session,
624 CamelService *service,
625 CamelSessionAlertType type,
626 const gchar *alert_message)
627 {
628 EAlert *alert;
629 EShell *shell;
630 const gchar *alert_tag;
631 gchar *display_name;
632
633 shell = e_shell_get_default ();
634
635 switch (type) {
636 case CAMEL_SESSION_ALERT_INFO:
637 alert_tag = "mail:user-alert-info";
638 break;
639 case CAMEL_SESSION_ALERT_WARNING:
640 alert_tag = "mail:user-alert-warning";
641 break;
642 case CAMEL_SESSION_ALERT_ERROR:
643 alert_tag = "mail:user-alert-error";
644 break;
645 default:
646 g_return_if_reached ();
647 }
648
649 display_name = camel_service_dup_display_name (service);
650
651 /* Just submit the alert to the EShell rather than hunting for
652 * a suitable window. This will post it to all shell windows in
653 * all views, but if it's coming from the server then it must be
654 * important... right? */
655 alert = e_alert_new (alert_tag, display_name, alert_message, NULL);
656 e_shell_submit_alert (shell, alert);
657 g_object_unref (alert);
658
659 g_free (display_name);
660 }
661
662 static gpointer
mail_ui_session_call_trust_prompt_in_main_thread_cb(const gchar * source_extension,const gchar * source_display_name,const gchar * host,const gchar * certificate_pem,gconstpointer pcertificate_errors)663 mail_ui_session_call_trust_prompt_in_main_thread_cb (const gchar *source_extension,
664 const gchar *source_display_name,
665 const gchar *host,
666 const gchar *certificate_pem,
667 gconstpointer pcertificate_errors)
668 {
669 EShell *shell;
670 ETrustPromptResponse prompt_response;
671
672 shell = e_shell_get_default ();
673
674 prompt_response = e_trust_prompt_run_modal (gtk_application_get_active_window (GTK_APPLICATION (shell)),
675 source_extension, source_display_name, host, certificate_pem, GPOINTER_TO_UINT (pcertificate_errors), NULL);
676
677 return GINT_TO_POINTER (prompt_response);
678 }
679
680 static CamelCertTrust
mail_ui_session_trust_prompt(CamelSession * session,CamelService * service,GTlsCertificate * certificate,GTlsCertificateFlags errors)681 mail_ui_session_trust_prompt (CamelSession *session,
682 CamelService *service,
683 GTlsCertificate *certificate,
684 GTlsCertificateFlags errors)
685 {
686 CamelSettings *settings;
687 CamelCertTrust response;
688 gchar *host, *certificate_pem = NULL;
689 ETrustPromptResponse prompt_response;
690 const gchar *source_extension;
691
692 settings = camel_service_ref_settings (service);
693 g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), 0);
694 host = camel_network_settings_dup_host (
695 CAMEL_NETWORK_SETTINGS (settings));
696 g_object_unref (settings);
697
698 /* XXX No accessor function for this property. */
699 g_object_get (certificate, "certificate-pem", &certificate_pem, NULL);
700 g_return_val_if_fail (certificate_pem != NULL, 0);
701
702 if (CAMEL_IS_TRANSPORT (service))
703 source_extension = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
704 else
705 source_extension = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
706
707 prompt_response = GPOINTER_TO_INT (mail_call_main (MAIL_CALL_p_ppppp,
708 (MailMainFunc) mail_ui_session_call_trust_prompt_in_main_thread_cb,
709 source_extension, camel_service_get_display_name (service), host, certificate_pem, GUINT_TO_POINTER (errors)));
710
711 g_free (certificate_pem);
712 g_free (host);
713
714 switch (prompt_response) {
715 case E_TRUST_PROMPT_RESPONSE_REJECT:
716 response = CAMEL_CERT_TRUST_NEVER;
717 break;
718 case E_TRUST_PROMPT_RESPONSE_ACCEPT:
719 response = CAMEL_CERT_TRUST_FULLY;
720 break;
721 case E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY:
722 response = CAMEL_CERT_TRUST_TEMPORARY;
723 break;
724 default:
725 response = CAMEL_CERT_TRUST_UNKNOWN;
726 break;
727 }
728
729 return response;
730 }
731
732 CamelCertTrust
e_mail_ui_session_trust_prompt(CamelSession * session,CamelService * service,GTlsCertificate * certificate,GTlsCertificateFlags errors)733 e_mail_ui_session_trust_prompt (CamelSession *session,
734 CamelService *service,
735 GTlsCertificate *certificate,
736 GTlsCertificateFlags errors)
737 {
738 return mail_ui_session_trust_prompt (session, service, certificate, errors);
739 }
740
741 typedef struct _TryCredentialsData {
742 CamelService *service;
743 const gchar *mechanism;
744 } TryCredentialsData;
745
746 static gboolean
mail_ui_session_try_credentials_sync(ECredentialsPrompter * prompter,ESource * source,const ENamedParameters * credentials,gboolean * out_authenticated,gpointer user_data,GCancellable * cancellable,GError ** error)747 mail_ui_session_try_credentials_sync (ECredentialsPrompter *prompter,
748 ESource *source,
749 const ENamedParameters *credentials,
750 gboolean *out_authenticated,
751 gpointer user_data,
752 GCancellable *cancellable,
753 GError **error)
754 {
755 TryCredentialsData *data = user_data;
756 gchar *credential_name = NULL;
757 CamelAuthenticationResult result;
758
759 g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
760 g_return_val_if_fail (credentials != NULL, FALSE);
761 g_return_val_if_fail (out_authenticated != NULL, FALSE);
762 g_return_val_if_fail (data != NULL, FALSE);
763 g_return_val_if_fail (CAMEL_IS_SERVICE (data->service), FALSE);
764
765 if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) {
766 ESourceAuthentication *auth_extension;
767
768 auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
769 credential_name = e_source_authentication_dup_credential_name (auth_extension);
770
771 if (!credential_name || !*credential_name) {
772 g_free (credential_name);
773 credential_name = NULL;
774 }
775 }
776
777 camel_service_set_password (data->service, e_named_parameters_get (credentials,
778 credential_name ? credential_name : E_SOURCE_CREDENTIAL_PASSWORD));
779
780 g_free (credential_name);
781
782 result = camel_service_authenticate_sync (data->service, data->mechanism, cancellable, error);
783
784 *out_authenticated = result == CAMEL_AUTHENTICATION_ACCEPTED;
785
786 if (*out_authenticated) {
787 ESourceCredentialsProvider *credentials_provider;
788 ESource *cred_source;
789
790 credentials_provider = e_credentials_prompter_get_provider (prompter);
791 cred_source = e_source_credentials_provider_ref_credentials_source (credentials_provider, source);
792
793 if (cred_source)
794 e_source_invoke_authenticate_sync (cred_source, credentials, cancellable, NULL);
795
796 g_clear_object (&cred_source);
797 }
798
799 return result == CAMEL_AUTHENTICATION_REJECTED;
800 }
801
802 static gboolean
mail_ui_session_authenticate_sync(CamelSession * session,CamelService * service,const gchar * mechanism,GCancellable * cancellable,GError ** error)803 mail_ui_session_authenticate_sync (CamelSession *session,
804 CamelService *service,
805 const gchar *mechanism,
806 GCancellable *cancellable,
807 GError **error)
808 {
809 ESource *source;
810 ESourceRegistry *registry;
811 CamelServiceAuthType *authtype = NULL;
812 CamelAuthenticationResult result;
813 const gchar *uid;
814 gboolean authenticated;
815 gboolean try_empty_password = FALSE;
816 GError *local_error = NULL;
817
818 /* Do not chain up. Camel's default method is only an example for
819 * subclasses to follow. Instead we mimic most of its logic here. */
820
821 registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
822
823 /* Treat a mechanism name of "none" as NULL. */
824 if (g_strcmp0 (mechanism, "none") == 0)
825 mechanism = NULL;
826
827 /* APOP is one case where a non-SASL mechanism name is passed, so
828 * don't bail if the CamelServiceAuthType struct comes back NULL. */
829 if (mechanism != NULL)
830 authtype = camel_sasl_authtype (mechanism);
831
832 /* If the SASL mechanism does not involve a user
833 * password, then it gets one shot to authenticate. */
834 if (authtype != NULL && !authtype->need_password) {
835 result = camel_service_authenticate_sync (service, mechanism, cancellable, &local_error);
836
837 if ((result == CAMEL_AUTHENTICATION_REJECTED ||
838 g_error_matches (local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE)) &&
839 e_oauth2_services_is_oauth2_alias (e_source_registry_get_oauth2_services (registry), mechanism)) {
840 EShell *shell;
841 ECredentialsPrompter *credentials_prompter;
842 TryCredentialsData data;
843
844 g_clear_error (&local_error);
845
846 shell = e_shell_get_default ();
847 credentials_prompter = e_shell_get_credentials_prompter (shell);
848
849 /* Find a matching ESource for this CamelService. */
850 uid = camel_service_get_uid (service);
851 source = e_source_registry_ref_source (registry, uid);
852
853 if (!source) {
854 g_set_error (
855 error, CAMEL_SERVICE_ERROR,
856 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
857 _("No data source found for UID “%s”"), uid);
858 return FALSE;
859 }
860
861 data.service = service;
862 data.mechanism = mechanism;
863
864 if (e_credentials_prompter_loop_prompt_sync (credentials_prompter,
865 source, E_CREDENTIALS_PROMPTER_PROMPT_FLAG_ALLOW_SOURCE_SAVE,
866 mail_ui_session_try_credentials_sync, &data, cancellable, &local_error))
867 result = CAMEL_AUTHENTICATION_ACCEPTED;
868 else
869 result = CAMEL_AUTHENTICATION_ERROR;
870 }
871
872 if (local_error)
873 g_propagate_error (error, local_error);
874
875 if (result == CAMEL_AUTHENTICATION_REJECTED)
876 g_set_error (
877 error, CAMEL_SERVICE_ERROR,
878 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
879 _("%s authentication failed"), mechanism);
880 return (result == CAMEL_AUTHENTICATION_ACCEPTED);
881 }
882
883 /* Some SASL mechanisms can attempt to authenticate without a
884 * user password being provided (e.g. single-sign-on credentials),
885 * but can fall back to a user password. Handle that case next. */
886 if (mechanism != NULL) {
887 CamelProvider *provider;
888 CamelSasl *sasl;
889 const gchar *service_name;
890
891 provider = camel_service_get_provider (service);
892 service_name = provider->protocol;
893
894 /* XXX Would be nice if camel_sasl_try_empty_password_sync()
895 * returned the result in an "out" parameter so it's
896 * easier to distinguish errors from a "no" answer.
897 * YYY There are precisely two states. Either we appear to
898 * have credentials (although we don't yet know if the
899 * server would *accept* them, of course). Or we don't
900 * have any credentials, and we can't even try. There
901 * is no middle ground.
902 * N.B. For 'have credentials', read 'the ntlm_auth
903 * helper exists and at first glance seems to
904 * be responding sanely'. */
905 sasl = camel_sasl_new (service_name, mechanism, service);
906 if (sasl != NULL) {
907 try_empty_password =
908 camel_sasl_try_empty_password_sync (
909 sasl, cancellable, &local_error);
910 g_object_unref (sasl);
911 }
912 }
913
914 /* Abort authentication if we got cancelled.
915 * Otherwise clear any errors and press on. */
916 if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
917 return FALSE;
918
919 g_clear_error (&local_error);
920
921 /* Find a matching ESource for this CamelService. */
922 uid = camel_service_get_uid (service);
923 source = e_source_registry_ref_source (registry, uid);
924
925 if (source == NULL) {
926 g_set_error (
927 error, CAMEL_SERVICE_ERROR,
928 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
929 _("No data source found for UID “%s”"), uid);
930 return FALSE;
931 }
932
933 result = CAMEL_AUTHENTICATION_REJECTED;
934
935 if (try_empty_password) {
936 result = camel_service_authenticate_sync (
937 service, mechanism, cancellable, error);
938 }
939
940 if (result == CAMEL_AUTHENTICATION_REJECTED) {
941 /* We need a password, preferrably one cached in
942 * the keyring or else by interactive user prompt. */
943 EShell *shell;
944 ECredentialsPrompter *credentials_prompter;
945 TryCredentialsData data;
946
947 shell = e_shell_get_default ();
948 credentials_prompter = e_shell_get_credentials_prompter (shell);
949
950 data.service = service;
951 data.mechanism = mechanism;
952
953 authenticated = e_credentials_prompter_loop_prompt_sync (credentials_prompter,
954 source, E_CREDENTIALS_PROMPTER_PROMPT_FLAG_ALLOW_SOURCE_SAVE,
955 mail_ui_session_try_credentials_sync, &data, cancellable, error);
956 } else {
957 authenticated = (result == CAMEL_AUTHENTICATION_ACCEPTED);
958 }
959
960 g_object_unref (source);
961
962 return authenticated;
963 }
964
965 static void
mail_ui_session_refresh_service(EMailSession * session,CamelService * service)966 mail_ui_session_refresh_service (EMailSession *session,
967 CamelService *service)
968 {
969 if (!camel_application_is_exiting &&
970 camel_session_get_online (CAMEL_SESSION (session))) {
971 mail_receive_service (service);
972 }
973 }
974
975 static EMVFolderContext *
mail_ui_session_create_vfolder_context(EMailSession * session)976 mail_ui_session_create_vfolder_context (EMailSession *session)
977 {
978 return (EMVFolderContext *) em_vfolder_editor_context_new (session);
979 }
980
981 static void
e_mail_ui_session_class_init(EMailUISessionClass * class)982 e_mail_ui_session_class_init (EMailUISessionClass *class)
983 {
984 GObjectClass *object_class;
985 CamelSessionClass *session_class;
986 EMailSessionClass *mail_session_class;
987
988 g_type_class_add_private (class, sizeof (EMailUISessionPrivate));
989
990 object_class = G_OBJECT_CLASS (class);
991 object_class->set_property = mail_ui_session_set_property;
992 object_class->get_property = mail_ui_session_get_property;
993 object_class->dispose = mail_ui_session_dispose;
994 object_class->finalize = mail_ui_session_finalize;
995 object_class->constructed = mail_ui_session_constructed;
996
997 session_class = CAMEL_SESSION_CLASS (class);
998 session_class->add_service = mail_ui_session_add_service;
999 session_class->remove_service = mail_ui_session_remove_service;
1000 session_class->get_filter_driver = mail_ui_session_get_filter_driver;
1001 session_class->lookup_addressbook = mail_ui_session_lookup_addressbook;
1002 session_class->user_alert = mail_ui_session_user_alert;
1003 session_class->trust_prompt = mail_ui_session_trust_prompt;
1004 session_class->authenticate_sync = mail_ui_session_authenticate_sync;
1005
1006 mail_session_class = E_MAIL_SESSION_CLASS (class);
1007 mail_session_class->create_vfolder_context = mail_ui_session_create_vfolder_context;
1008 mail_session_class->refresh_service = mail_ui_session_refresh_service;
1009
1010 g_object_class_install_property (
1011 object_class,
1012 PROP_CHECK_JUNK,
1013 g_param_spec_boolean (
1014 "check-junk",
1015 "Check Junk",
1016 "Check if incoming messages are junk",
1017 TRUE,
1018 G_PARAM_READWRITE |
1019 G_PARAM_CONSTRUCT |
1020 G_PARAM_STATIC_STRINGS));
1021
1022 g_object_class_install_property (
1023 object_class,
1024 PROP_LABEL_STORE,
1025 g_param_spec_object (
1026 "label-store",
1027 "Label Store",
1028 "Mail label store",
1029 E_TYPE_MAIL_LABEL_LIST_STORE,
1030 G_PARAM_READABLE |
1031 G_PARAM_STATIC_STRINGS));
1032
1033 g_object_class_install_property (
1034 object_class,
1035 PROP_PHOTO_CACHE,
1036 g_param_spec_object (
1037 "photo-cache",
1038 "Photo Cache",
1039 "Contact photo cache",
1040 E_TYPE_PHOTO_CACHE,
1041 G_PARAM_READABLE |
1042 G_PARAM_STATIC_STRINGS));
1043
1044 signals[ACTIVITY_ADDED] = g_signal_new (
1045 "activity-added",
1046 G_OBJECT_CLASS_TYPE (class),
1047 G_SIGNAL_RUN_LAST,
1048 G_STRUCT_OFFSET (EMailUISessionClass, activity_added),
1049 NULL, NULL,
1050 g_cclosure_marshal_VOID__OBJECT,
1051 G_TYPE_NONE, 1,
1052 E_TYPE_ACTIVITY);
1053 }
1054
1055 static void
e_mail_ui_session_init(EMailUISession * session)1056 e_mail_ui_session_init (EMailUISession *session)
1057 {
1058 session->priv = E_MAIL_UI_SESSION_GET_PRIVATE (session);
1059 g_mutex_init (&session->priv->address_cache_mutex);
1060 session->priv->label_store = e_mail_label_list_store_new ();
1061 }
1062
1063 EMailSession *
e_mail_ui_session_new(ESourceRegistry * registry)1064 e_mail_ui_session_new (ESourceRegistry *registry)
1065 {
1066 const gchar *user_data_dir;
1067 const gchar *user_cache_dir;
1068
1069 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
1070
1071 user_data_dir = mail_session_get_data_dir ();
1072 user_cache_dir = mail_session_get_cache_dir ();
1073
1074 return g_object_new (
1075 E_TYPE_MAIL_UI_SESSION,
1076 "registry", registry,
1077 "user-data-dir", user_data_dir,
1078 "user-cache-dir", user_cache_dir,
1079 NULL);
1080 }
1081
1082 EMailAccountStore *
e_mail_ui_session_get_account_store(EMailUISession * session)1083 e_mail_ui_session_get_account_store (EMailUISession *session)
1084 {
1085 g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL);
1086
1087 return session->priv->account_store;
1088 }
1089
1090 /**
1091 * e_mail_ui_session_get_check_junk:
1092 * @session: an #EMailUISession
1093 *
1094 * Returns whether to automatically check incoming messages for junk content.
1095 *
1096 * Returns: whether to check for junk messages
1097 **/
1098 gboolean
e_mail_ui_session_get_check_junk(EMailUISession * session)1099 e_mail_ui_session_get_check_junk (EMailUISession *session)
1100 {
1101 g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), FALSE);
1102
1103 return session->priv->check_junk;
1104 }
1105
1106 /**
1107 * e_mail_ui_session_set_check_junk:
1108 * @session: an #EMailUISession
1109 * @check_junk: whether to check for junk messages
1110 *
1111 * Sets whether to automatically check incoming messages for junk content.
1112 **/
1113 void
e_mail_ui_session_set_check_junk(EMailUISession * session,gboolean check_junk)1114 e_mail_ui_session_set_check_junk (EMailUISession *session,
1115 gboolean check_junk)
1116 {
1117 g_return_if_fail (E_IS_MAIL_UI_SESSION (session));
1118
1119 if (check_junk == session->priv->check_junk)
1120 return;
1121
1122 session->priv->check_junk = check_junk;
1123
1124 g_object_notify (G_OBJECT (session), "check-junk");
1125 }
1126
1127 EMailLabelListStore *
e_mail_ui_session_get_label_store(EMailUISession * session)1128 e_mail_ui_session_get_label_store (EMailUISession *session)
1129 {
1130 g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL);
1131
1132 return session->priv->label_store;
1133 }
1134
1135 EPhotoCache *
e_mail_ui_session_get_photo_cache(EMailUISession * session)1136 e_mail_ui_session_get_photo_cache (EMailUISession *session)
1137 {
1138 g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), NULL);
1139
1140 return session->priv->photo_cache;
1141 }
1142
1143 void
e_mail_ui_session_add_activity(EMailUISession * session,EActivity * activity)1144 e_mail_ui_session_add_activity (EMailUISession *session,
1145 EActivity *activity)
1146 {
1147 g_return_if_fail (E_IS_MAIL_UI_SESSION (session));
1148 g_return_if_fail (E_IS_ACTIVITY (activity));
1149
1150 g_signal_emit (session, signals[ACTIVITY_ADDED], 0, activity);
1151 }
1152
1153 /* let's test in local books first */
1154 static gint
sort_local_books_first_cb(gconstpointer a,gconstpointer b)1155 sort_local_books_first_cb (gconstpointer a,
1156 gconstpointer b)
1157 {
1158 ESource *asource = (ESource *) a;
1159 ESource *bsource = (ESource *) b;
1160 ESourceBackend *abackend, *bbackend;
1161
1162 abackend = e_source_get_extension (asource, E_SOURCE_EXTENSION_ADDRESS_BOOK);
1163 bbackend = e_source_get_extension (bsource, E_SOURCE_EXTENSION_ADDRESS_BOOK);
1164
1165 if (g_strcmp0 (e_source_backend_get_backend_name (abackend), "local") == 0) {
1166 if (g_strcmp0 (e_source_backend_get_backend_name (bbackend), "local") == 0)
1167 return 0;
1168
1169 return -1;
1170 }
1171
1172 if (g_strcmp0 (e_source_backend_get_backend_name (bbackend), "local") == 0)
1173 return 1;
1174
1175 return g_strcmp0 (e_source_backend_get_backend_name (abackend),
1176 e_source_backend_get_backend_name (bbackend));
1177 }
1178
1179 /**
1180 * e_mail_ui_session_check_known_address_sync:
1181 * @session: an #EMailUISession
1182 * @addr: a #CamelInternetAddress
1183 * @check_local_only: only check the builtin address book
1184 * @cancellable: optional #GCancellable object, or %NULL
1185 * @out_known_address: return location for the determination of
1186 * whether @addr is a known address
1187 * @error: return location for a #GError, or %NULL
1188 *
1189 * Determines whether @addr is a known email address by querying address
1190 * books for contacts with a matching email address. If @check_local_only
1191 * is %TRUE then only the builtin address book is checked, otherwise all
1192 * enabled address books are checked.
1193 *
1194 * The result of the query is returned through the @out_known_address
1195 * boolean pointer, not through the return value. The return value only
1196 * indicates whether the address book queries were completed successfully.
1197 * If an error occurred, the function sets @error and returns %FALSE.
1198 *
1199 * Returns: whether address books were successfully queried
1200 **/
1201 gboolean
e_mail_ui_session_check_known_address_sync(EMailUISession * session,CamelInternetAddress * addr,gboolean check_local_only,GCancellable * cancellable,gboolean * out_known_address,GError ** error)1202 e_mail_ui_session_check_known_address_sync (EMailUISession *session,
1203 CamelInternetAddress *addr,
1204 gboolean check_local_only,
1205 GCancellable *cancellable,
1206 gboolean *out_known_address,
1207 GError **error)
1208 {
1209 EPhotoCache *photo_cache;
1210 EClientCache *client_cache;
1211 ESourceRegistry *registry;
1212 EBookQuery *book_query;
1213 GList *list, *link;
1214 const gchar *email_address = NULL;
1215 gchar *book_query_string;
1216 gboolean known_address = FALSE;
1217 gboolean success = FALSE;
1218
1219 g_return_val_if_fail (E_IS_MAIL_UI_SESSION (session), FALSE);
1220 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), FALSE);
1221 g_return_val_if_fail (camel_internet_address_get (addr, 0, NULL, &email_address), FALSE);
1222 g_return_val_if_fail (email_address != NULL, FALSE);
1223
1224 g_mutex_lock (&session->priv->address_cache_mutex);
1225
1226 session->priv->address_cache = address_cache_data_remove_old_and_test (
1227 session->priv->address_cache,
1228 email_address, &success, &known_address);
1229
1230 if (success) {
1231 g_mutex_unlock (&session->priv->address_cache_mutex);
1232
1233 if (out_known_address)
1234 *out_known_address = known_address;
1235
1236 return success;
1237 }
1238
1239 /* XXX EPhotoCache holds a reference on EClientCache, which
1240 * we need. EMailUISession should probably hold its own
1241 * EClientCache reference, but this will do for now. */
1242 photo_cache = e_mail_ui_session_get_photo_cache (session);
1243 client_cache = e_photo_cache_ref_client_cache (photo_cache);
1244 registry = e_client_cache_ref_registry (client_cache);
1245
1246 book_query = e_book_query_field_test (
1247 E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email_address);
1248 book_query_string = e_book_query_to_string (book_query);
1249 e_book_query_unref (book_query);
1250
1251 if (check_local_only) {
1252 ESource *source;
1253
1254 source = e_source_registry_ref_builtin_address_book (registry);
1255 list = g_list_prepend (NULL, g_object_ref (source));
1256 g_object_unref (source);
1257 } else {
1258 list = e_source_registry_list_enabled (
1259 registry, E_SOURCE_EXTENSION_ADDRESS_BOOK);
1260 list = g_list_sort (list, sort_local_books_first_cb);
1261 }
1262
1263 for (link = list; link != NULL && !g_cancellable_is_cancelled (cancellable); link = g_list_next (link)) {
1264 ESource *source = E_SOURCE (link->data);
1265 EClient *client;
1266 GSList *uids = NULL;
1267 GError *local_error = NULL;
1268
1269 /* Skip disabled sources. */
1270 if (!e_source_get_enabled (source))
1271 continue;
1272
1273 client = e_client_cache_get_client_sync (
1274 client_cache, source,
1275 E_SOURCE_EXTENSION_ADDRESS_BOOK, (guint32) -1,
1276 cancellable, &local_error);
1277
1278 if (client == NULL) {
1279 /* ignore E_CLIENT_ERROR-s, no need to stop searching if one
1280 of the books is temporarily unreachable or any such issue */
1281 if (local_error && local_error->domain == E_CLIENT_ERROR) {
1282 g_clear_error (&local_error);
1283 continue;
1284 }
1285
1286 if (local_error)
1287 g_propagate_error (error, local_error);
1288
1289 success = FALSE;
1290 break;
1291 }
1292
1293 success = e_book_client_get_contacts_uids_sync (
1294 E_BOOK_CLIENT (client), book_query_string,
1295 &uids, cancellable, &local_error);
1296
1297 g_object_unref (client);
1298
1299 if (!success) {
1300 g_warn_if_fail (uids == NULL);
1301
1302 /* ignore book-specific errors here and continue with the next */
1303 g_clear_error (&local_error);
1304 continue;
1305 }
1306
1307 if (uids != NULL) {
1308 g_slist_free_full (uids, (GDestroyNotify) g_free);
1309 known_address = TRUE;
1310 break;
1311 }
1312 }
1313
1314 g_list_free_full (list, (GDestroyNotify) g_object_unref);
1315
1316 g_free (book_query_string);
1317
1318 g_object_unref (registry);
1319 g_object_unref (client_cache);
1320
1321 if (success && out_known_address != NULL)
1322 *out_known_address = known_address;
1323
1324 if (!g_cancellable_is_cancelled (cancellable)) {
1325 AddressCacheData *data = g_new0 (AddressCacheData, 1);
1326
1327 data->email_address = g_strdup (email_address);
1328 data->stamp = g_get_real_time ();
1329 data->is_known = known_address;
1330
1331 /* this makes the list sorted by time, from newest to oldest */
1332 session->priv->address_cache = g_slist_prepend (
1333 session->priv->address_cache, data);
1334 }
1335
1336 g_mutex_unlock (&session->priv->address_cache_mutex);
1337
1338 return success;
1339 }
1340
1341