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