1 /*
2  * e-mail-config-auth-check.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 
18 #include "evolution-config.h"
19 
20 #include <glib/gi18n-lib.h>
21 
22 #include "e-util/e-util.h"
23 #include "mail/e-mail-config-service-page.h"
24 
25 #include "e-mail-ui-session.h"
26 
27 #include "e-mail-config-auth-check.h"
28 
29 #define E_MAIL_CONFIG_AUTH_CHECK_GET_PRIVATE(obj) \
30 	(G_TYPE_INSTANCE_GET_PRIVATE \
31 	((obj), E_TYPE_MAIL_CONFIG_AUTH_CHECK, EMailConfigAuthCheckPrivate))
32 
33 typedef struct _AsyncContext AsyncContext;
34 
35 struct _EMailConfigAuthCheckPrivate {
36 	EMailConfigServiceBackend *backend;
37 	gchar *active_mechanism;
38 
39 	GtkWidget *combo_box;  /* not referenced */
40 	gulong host_changed_id;
41 	CamelServiceAuthType *used_xoauth2;
42 };
43 
44 struct _AsyncContext {
45 	EMailConfigAuthCheck *auth_check;
46 	CamelSession *temporary_session;
47 	EActivity *activity;
48 };
49 
50 enum {
51 	PROP_0,
52 	PROP_ACTIVE_MECHANISM,
53 	PROP_BACKEND
54 };
55 
G_DEFINE_TYPE(EMailConfigAuthCheck,e_mail_config_auth_check,GTK_TYPE_BOX)56 G_DEFINE_TYPE (
57 	EMailConfigAuthCheck,
58 	e_mail_config_auth_check,
59 	GTK_TYPE_BOX)
60 
61 static void
62 async_context_free (AsyncContext *async_context)
63 {
64 	if (async_context->auth_check != NULL)
65 		g_object_unref (async_context->auth_check);
66 
67 	if (async_context->temporary_session != NULL)
68 		g_object_unref (async_context->temporary_session);
69 
70 	if (async_context->activity != NULL)
71 		g_object_unref (async_context->activity);
72 
73 	g_slice_free (AsyncContext, async_context);
74 }
75 
76 static void
mail_config_auth_check_update_done_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)77 mail_config_auth_check_update_done_cb (GObject *source_object,
78                                        GAsyncResult *result,
79                                        gpointer user_data)
80 {
81 	AsyncContext *async_context = user_data;
82 	EMailConfigAuthCheck *auth_check;
83 	EAlertSink *alert_sink;
84 	GList *available_authtypes;
85 	GError *error = NULL;
86 
87 	auth_check = async_context->auth_check;
88 	alert_sink = e_activity_get_alert_sink (async_context->activity);
89 
90 	available_authtypes = camel_service_query_auth_types_finish (
91 		CAMEL_SERVICE (source_object), result, &error);
92 
93 	if (e_activity_handle_cancellation (async_context->activity, error)) {
94 		g_warn_if_fail (available_authtypes == NULL);
95 		g_error_free (error);
96 
97 	} else if (error != NULL) {
98 		g_warn_if_fail (available_authtypes == NULL);
99 		e_alert_submit (
100 			alert_sink,
101 			"mail:checking-service-error",
102 			error->message, NULL);
103 		g_error_free (error);
104 
105 	} else {
106 		e_auth_combo_box_update_available (
107 			E_AUTH_COMBO_BOX (auth_check->priv->combo_box),
108 			available_authtypes);
109 		e_auth_combo_box_pick_highest_available (E_AUTH_COMBO_BOX (auth_check->priv->combo_box));
110 
111 		g_list_free (available_authtypes);
112 	}
113 
114 	gtk_widget_set_sensitive (GTK_WIDGET (auth_check), TRUE);
115 
116 	async_context_free (async_context);
117 }
118 
119 static void
mail_config_auth_check_update(EMailConfigAuthCheck * auth_check)120 mail_config_auth_check_update (EMailConfigAuthCheck *auth_check)
121 {
122 	EActivity *activity;
123 	ESource *source;
124 	EMailConfigServicePage *page;
125 	EMailConfigServiceBackend *backend;
126 	EMailConfigServicePageClass *page_class;
127 	EMailConfigServiceBackendClass *backend_class;
128 	CamelService *service;
129 	CamelSession *session;
130 	CamelSettings *settings;
131 	GCancellable *cancellable;
132 	AsyncContext *async_context;
133 	gchar *temp_dir;
134 	GError *error = NULL;
135 
136 	backend = e_mail_config_auth_check_get_backend (auth_check);
137 	page = e_mail_config_service_backend_get_page (backend);
138 	settings = e_mail_config_service_backend_get_settings (backend);
139 	source = e_mail_config_service_backend_get_source (backend);
140 
141 	page_class = E_MAIL_CONFIG_SERVICE_PAGE_GET_CLASS (page);
142 	backend_class = E_MAIL_CONFIG_SERVICE_BACKEND_GET_CLASS (backend);
143 
144 	temp_dir = e_mkdtemp ("evolution-auth-check-XXXXXX");
145 
146 	/* Create a temporary session for our temporary service.
147 	 * Use the same temporary directory for "user-data-dir" and
148 	 * "user-cache-dir".  For our purposes it shouldn't matter. */
149 	session = g_object_new (
150 		CAMEL_TYPE_SESSION,
151 		"user-data-dir", temp_dir,
152 		"user-cache-dir", temp_dir,
153 		NULL);
154 
155 	/* to be able to answer for invalid/self-signed server certificates */
156 	CAMEL_SESSION_GET_CLASS (session)->trust_prompt = e_mail_ui_session_trust_prompt;
157 
158 	service = camel_session_add_service (
159 		session, "fake-uid",
160 		backend_class->backend_name,
161 		page_class->provider_type, &error);
162 
163 	g_free (temp_dir);
164 
165 	if (error != NULL) {
166 		g_warn_if_fail (service == NULL);
167 		e_alert_submit (
168 			E_ALERT_SINK (page),
169 			"mail:checking-service-error",
170 			error->message, NULL);
171 		g_error_free (error);
172 		return;
173 	}
174 
175 	g_return_if_fail (CAMEL_IS_SERVICE (service));
176 
177 	camel_service_set_settings (service, settings);
178 
179 	/* Setup Proxy */
180 	if (source) {
181 		ESourceRegistry *registry;
182 		ESource *authentication_source;
183 
184 		registry = e_mail_config_service_page_get_registry (e_mail_config_service_backend_get_page (backend));
185 		authentication_source = e_source_registry_find_extension (registry, source, E_SOURCE_EXTENSION_AUTHENTICATION);
186 
187 		if (authentication_source) {
188 			GProxyResolver *proxy_resolver = NULL;
189 			ESourceAuthentication *extension;
190 			ESource *proxy_source = NULL;
191 			gchar *uid;
192 
193 			extension = e_source_get_extension (authentication_source, E_SOURCE_EXTENSION_AUTHENTICATION);
194 
195 			uid = e_source_authentication_dup_proxy_uid (extension);
196 			if (uid) {
197 				proxy_source = e_source_registry_ref_source (registry, uid);
198 				g_free (uid);
199 			}
200 
201 			if (proxy_source) {
202 				proxy_resolver = G_PROXY_RESOLVER (proxy_source);
203 				if (!g_proxy_resolver_is_supported (proxy_resolver))
204 					proxy_resolver = NULL;
205 			}
206 
207 			camel_service_set_proxy_resolver (service, proxy_resolver);
208 
209 			g_clear_object (&authentication_source);
210 			g_clear_object (&proxy_source);
211 		}
212 	}
213 
214 	activity = e_mail_config_activity_page_new_activity (
215 		E_MAIL_CONFIG_ACTIVITY_PAGE (page));
216 	cancellable = e_activity_get_cancellable (activity);
217 	e_activity_set_text (activity, _("Querying authentication types…"));
218 
219 	gtk_widget_set_sensitive (GTK_WIDGET (auth_check), FALSE);
220 
221 	async_context = g_slice_new (AsyncContext);
222 	async_context->auth_check = g_object_ref (auth_check);
223 	async_context->temporary_session = session;  /* takes ownership */
224 	async_context->activity = activity;          /* takes ownership */
225 
226 	camel_service_query_auth_types (
227 		service, G_PRIORITY_DEFAULT, cancellable,
228 		mail_config_auth_check_update_done_cb, async_context);
229 
230 	g_object_unref (service);
231 }
232 
233 static void
mail_config_auth_check_clicked_cb(GtkButton * button,EMailConfigAuthCheck * auth_check)234 mail_config_auth_check_clicked_cb (GtkButton *button,
235                                    EMailConfigAuthCheck *auth_check)
236 {
237 	mail_config_auth_check_update (auth_check);
238 }
239 
240 static void
mail_config_auth_check_init_mechanism(EMailConfigAuthCheck * auth_check)241 mail_config_auth_check_init_mechanism (EMailConfigAuthCheck *auth_check)
242 {
243 	EMailConfigServiceBackend *backend;
244 	CamelProvider *provider;
245 	CamelSettings *settings;
246 	const gchar *auth_mechanism = NULL;
247 
248 	/* Pick an initial active mechanism name by examining both
249 	 * the corresponding CamelNetworkSettings and CamelProvider. */
250 
251 	backend = e_mail_config_auth_check_get_backend (auth_check);
252 	provider = e_mail_config_service_backend_get_provider (backend);
253 	settings = e_mail_config_service_backend_get_settings (backend);
254 	g_return_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings));
255 
256 	auth_mechanism =
257 		camel_network_settings_get_auth_mechanism (
258 		CAMEL_NETWORK_SETTINGS (settings));
259 
260 	/* If CamelNetworkSettings does not have a mechanism name set,
261 	 * choose from the CamelProvider's list of supported mechanisms. */
262 	if (auth_mechanism == NULL && provider != NULL) {
263 		if (provider->authtypes != NULL) {
264 			CamelServiceAuthType *auth_type;
265 			auth_type = provider->authtypes->data;
266 			auth_mechanism = auth_type->authproto;
267 		}
268 	}
269 
270 	if (auth_mechanism != NULL)
271 		e_mail_config_auth_check_set_active_mechanism (
272 			auth_check, auth_mechanism);
273 }
274 
275 static void
mail_config_auth_check_host_changed_cb(CamelNetworkSettings * network_settings,GParamSpec * param,EMailConfigAuthCheck * auth_check)276 mail_config_auth_check_host_changed_cb (CamelNetworkSettings *network_settings,
277 					GParamSpec *param,
278 					EMailConfigAuthCheck *auth_check)
279 {
280 	EMailConfigServiceBackend *backend;
281 	ESourceRegistry *registry;
282 	EOAuth2Service *oauth2_service;
283 	CamelProvider *provider;
284 	CamelServiceAuthType *change_authtype = NULL;
285 
286 	g_return_if_fail (CAMEL_IS_NETWORK_SETTINGS (network_settings));
287 	g_return_if_fail (E_IS_MAIL_CONFIG_AUTH_CHECK (auth_check));
288 
289 	backend = e_mail_config_auth_check_get_backend (auth_check);
290 	provider = e_mail_config_service_backend_get_provider (backend);
291 
292 	registry = e_mail_config_service_page_get_registry (e_mail_config_service_backend_get_page (backend));
293 	oauth2_service = e_oauth2_services_find (e_source_registry_get_oauth2_services (registry),
294 		e_mail_config_service_backend_get_source (backend));
295 	if (!oauth2_service) {
296 		oauth2_service = e_oauth2_services_guess (e_source_registry_get_oauth2_services (registry),
297 			provider ? provider->protocol : NULL, camel_network_settings_get_host (network_settings));
298 	}
299 
300 	if (oauth2_service)
301 		change_authtype = camel_sasl_authtype (e_oauth2_service_get_name (oauth2_service));
302 
303 	g_clear_object (&oauth2_service);
304 
305 	if (change_authtype != auth_check->priv->used_xoauth2) {
306 		if (auth_check->priv->used_xoauth2)
307 			e_auth_combo_box_remove_auth_type (E_AUTH_COMBO_BOX (auth_check->priv->combo_box), auth_check->priv->used_xoauth2);
308 
309 		auth_check->priv->used_xoauth2 = change_authtype;
310 
311 		if (auth_check->priv->used_xoauth2)
312 			e_auth_combo_box_add_auth_type (E_AUTH_COMBO_BOX (auth_check->priv->combo_box), auth_check->priv->used_xoauth2);
313 	}
314 }
315 
316 static void
mail_config_auth_check_set_backend(EMailConfigAuthCheck * auth_check,EMailConfigServiceBackend * backend)317 mail_config_auth_check_set_backend (EMailConfigAuthCheck *auth_check,
318                                     EMailConfigServiceBackend *backend)
319 {
320 	g_return_if_fail (E_IS_MAIL_CONFIG_SERVICE_BACKEND (backend));
321 	g_return_if_fail (auth_check->priv->backend == NULL);
322 
323 	auth_check->priv->backend = g_object_ref (backend);
324 }
325 
326 static void
mail_config_auth_check_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)327 mail_config_auth_check_set_property (GObject *object,
328                                      guint property_id,
329                                      const GValue *value,
330                                      GParamSpec *pspec)
331 {
332 	switch (property_id) {
333 		case PROP_ACTIVE_MECHANISM:
334 			e_mail_config_auth_check_set_active_mechanism (
335 				E_MAIL_CONFIG_AUTH_CHECK (object),
336 				g_value_get_string (value));
337 			return;
338 
339 		case PROP_BACKEND:
340 			mail_config_auth_check_set_backend (
341 				E_MAIL_CONFIG_AUTH_CHECK (object),
342 				g_value_get_object (value));
343 			return;
344 	}
345 
346 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
347 }
348 
349 static void
mail_config_auth_check_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)350 mail_config_auth_check_get_property (GObject *object,
351                                      guint property_id,
352                                      GValue *value,
353                                      GParamSpec *pspec)
354 {
355 	switch (property_id) {
356 		case PROP_ACTIVE_MECHANISM:
357 			g_value_set_string (
358 				value,
359 				e_mail_config_auth_check_get_active_mechanism (
360 				E_MAIL_CONFIG_AUTH_CHECK (object)));
361 			return;
362 
363 		case PROP_BACKEND:
364 			g_value_set_object (
365 				value,
366 				e_mail_config_auth_check_get_backend (
367 				E_MAIL_CONFIG_AUTH_CHECK (object)));
368 			return;
369 	}
370 
371 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
372 }
373 
374 static void
mail_config_auth_check_dispose(GObject * object)375 mail_config_auth_check_dispose (GObject *object)
376 {
377 	EMailConfigAuthCheckPrivate *priv;
378 
379 	priv = E_MAIL_CONFIG_AUTH_CHECK_GET_PRIVATE (object);
380 
381 	if (priv->backend != NULL) {
382 		if (priv->host_changed_id) {
383 			CamelSettings *settings;
384 
385 			settings = e_mail_config_service_backend_get_settings (priv->backend);
386 			if (settings)
387 				e_signal_disconnect_notify_handler (settings, &priv->host_changed_id);
388 		}
389 
390 		g_object_unref (priv->backend);
391 		priv->backend = NULL;
392 	}
393 
394 	/* Chain up to parent's dispose() method. */
395 	G_OBJECT_CLASS (e_mail_config_auth_check_parent_class)->
396 		dispose (object);
397 }
398 
399 static void
mail_config_auth_check_finalize(GObject * object)400 mail_config_auth_check_finalize (GObject *object)
401 {
402 	EMailConfigAuthCheckPrivate *priv;
403 
404 	priv = E_MAIL_CONFIG_AUTH_CHECK_GET_PRIVATE (object);
405 
406 	g_free (priv->active_mechanism);
407 
408 	/* Chain up to parent's finalize() method. */
409 	G_OBJECT_CLASS (e_mail_config_auth_check_parent_class)->
410 		finalize (object);
411 }
412 
413 static void
mail_config_auth_check_constructed(GObject * object)414 mail_config_auth_check_constructed (GObject *object)
415 {
416 	EMailConfigAuthCheck *auth_check;
417 	EMailConfigServiceBackend *backend;
418 	CamelProvider *provider;
419 	CamelSettings *settings;
420 	GtkWidget *widget;
421 	const gchar *text;
422 
423 	/* Chain up to parent's constructed() method. */
424 	G_OBJECT_CLASS (e_mail_config_auth_check_parent_class)->constructed (object);
425 
426 	auth_check = E_MAIL_CONFIG_AUTH_CHECK (object);
427 	backend = e_mail_config_auth_check_get_backend (auth_check);
428 	provider = e_mail_config_service_backend_get_provider (backend);
429 
430 	text = _("Check for Supported Types");
431 	widget = gtk_button_new_with_label (text);
432 	gtk_box_pack_start (GTK_BOX (object), widget, FALSE, FALSE, 0);
433 	gtk_widget_show (widget);
434 
435 	g_signal_connect (
436 		widget, "clicked",
437 		G_CALLBACK (mail_config_auth_check_clicked_cb),
438 		auth_check);
439 
440 	widget = e_auth_combo_box_new ();
441 	e_auth_combo_box_set_provider (E_AUTH_COMBO_BOX (widget), provider);
442 	gtk_box_pack_start (GTK_BOX (object), widget, FALSE, FALSE, 0);
443 	auth_check->priv->combo_box = widget;  /* do not reference */
444 	gtk_widget_show (widget);
445 
446 	settings = e_mail_config_service_backend_get_settings (backend);
447 	if (CAMEL_IS_NETWORK_SETTINGS (settings)) {
448 		ESourceRegistry *registry;
449 		EOAuth2Service *oauth2_service;
450 
451 		auth_check->priv->host_changed_id = e_signal_connect_notify (
452 			settings, "notify::host", G_CALLBACK (mail_config_auth_check_host_changed_cb), auth_check);
453 
454 		registry = e_mail_config_service_page_get_registry (e_mail_config_service_backend_get_page (backend));
455 		oauth2_service = e_oauth2_services_find (e_source_registry_get_oauth2_services (registry),
456 			e_mail_config_service_backend_get_source (backend));
457 		if (!oauth2_service) {
458 			oauth2_service = e_oauth2_services_guess (e_source_registry_get_oauth2_services (registry),
459 				provider ? provider->protocol : NULL, camel_network_settings_get_host (CAMEL_NETWORK_SETTINGS (settings)));
460 		}
461 
462 		if (oauth2_service)
463 			auth_check->priv->used_xoauth2 = camel_sasl_authtype (e_oauth2_service_get_name (oauth2_service));
464 
465 		g_clear_object (&oauth2_service);
466 
467 		if (auth_check->priv->used_xoauth2)
468 			e_auth_combo_box_add_auth_type (E_AUTH_COMBO_BOX (auth_check->priv->combo_box), auth_check->priv->used_xoauth2);
469 	}
470 
471 	e_binding_bind_property (
472 		widget, "active-id",
473 		auth_check, "active-mechanism",
474 		G_BINDING_BIDIRECTIONAL |
475 		G_BINDING_SYNC_CREATE);
476 
477 	mail_config_auth_check_init_mechanism (auth_check);
478 }
479 
480 static void
e_mail_config_auth_check_class_init(EMailConfigAuthCheckClass * class)481 e_mail_config_auth_check_class_init (EMailConfigAuthCheckClass *class)
482 {
483 	GObjectClass *object_class;
484 
485 	g_type_class_add_private (class, sizeof (EMailConfigAuthCheckPrivate));
486 
487 	object_class = G_OBJECT_CLASS (class);
488 	object_class->set_property = mail_config_auth_check_set_property;
489 	object_class->get_property = mail_config_auth_check_get_property;
490 	object_class->dispose = mail_config_auth_check_dispose;
491 	object_class->finalize = mail_config_auth_check_finalize;
492 	object_class->constructed = mail_config_auth_check_constructed;
493 
494 	g_object_class_install_property (
495 		object_class,
496 		PROP_ACTIVE_MECHANISM,
497 		g_param_spec_string (
498 			"active-mechanism",
499 			"Active Mechanism",
500 			"Active authentication mechanism",
501 			NULL,
502 			G_PARAM_READWRITE |
503 			G_PARAM_STATIC_STRINGS));
504 
505 	g_object_class_install_property (
506 		object_class,
507 		PROP_BACKEND,
508 		g_param_spec_object (
509 			"backend",
510 			"Backend",
511 			"Mail configuration backend",
512 			E_TYPE_MAIL_CONFIG_SERVICE_BACKEND,
513 			G_PARAM_READWRITE |
514 			G_PARAM_CONSTRUCT_ONLY |
515 			G_PARAM_STATIC_STRINGS));
516 }
517 
518 static void
e_mail_config_auth_check_init(EMailConfigAuthCheck * auth_check)519 e_mail_config_auth_check_init (EMailConfigAuthCheck *auth_check)
520 {
521 	auth_check->priv = E_MAIL_CONFIG_AUTH_CHECK_GET_PRIVATE (auth_check);
522 	auth_check->priv->host_changed_id = 0;
523 	auth_check->priv->used_xoauth2 = NULL;
524 
525 	gtk_orientable_set_orientation (
526 		GTK_ORIENTABLE (auth_check),
527 		GTK_ORIENTATION_HORIZONTAL);
528 
529 	gtk_box_set_spacing (GTK_BOX (auth_check), 6);
530 }
531 
532 GtkWidget *
e_mail_config_auth_check_new(EMailConfigServiceBackend * backend)533 e_mail_config_auth_check_new (EMailConfigServiceBackend *backend)
534 {
535 	g_return_val_if_fail (E_IS_MAIL_CONFIG_SERVICE_BACKEND (backend), NULL);
536 
537 	return g_object_new (
538 		E_TYPE_MAIL_CONFIG_AUTH_CHECK,
539 		"backend", backend, NULL);
540 }
541 
542 EMailConfigServiceBackend *
e_mail_config_auth_check_get_backend(EMailConfigAuthCheck * auth_check)543 e_mail_config_auth_check_get_backend (EMailConfigAuthCheck *auth_check)
544 {
545 	g_return_val_if_fail (E_IS_MAIL_CONFIG_AUTH_CHECK (auth_check), NULL);
546 
547 	return auth_check->priv->backend;
548 }
549 
550 const gchar *
e_mail_config_auth_check_get_active_mechanism(EMailConfigAuthCheck * auth_check)551 e_mail_config_auth_check_get_active_mechanism (EMailConfigAuthCheck *auth_check)
552 {
553 	g_return_val_if_fail (E_IS_MAIL_CONFIG_AUTH_CHECK (auth_check), NULL);
554 
555 	return auth_check->priv->active_mechanism;
556 }
557 
558 void
e_mail_config_auth_check_set_active_mechanism(EMailConfigAuthCheck * auth_check,const gchar * active_mechanism)559 e_mail_config_auth_check_set_active_mechanism (EMailConfigAuthCheck *auth_check,
560                                                const gchar *active_mechanism)
561 {
562 	g_return_if_fail (E_IS_MAIL_CONFIG_AUTH_CHECK (auth_check));
563 
564 	if (g_strcmp0 (auth_check->priv->active_mechanism, active_mechanism) == 0)
565 		return;
566 
567 	g_free (auth_check->priv->active_mechanism);
568 	/* Do not allow NULL here, thus Password auth, which uses
569 	   empty string as ID, will match in the combo */
570 	auth_check->priv->active_mechanism = g_strdup (active_mechanism ? active_mechanism : "");
571 
572 	g_object_notify (G_OBJECT (auth_check), "active-mechanism");
573 }
574