1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-pop3-store.c : class for a pop3 store
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This library is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors: Dan Winship <danw@ximian.com>
19  *          Michael Zucchi <notzed@ximian.com>
20  */
21 
22 #include "evolution-data-server-config.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <ctype.h>
30 
31 #include <glib/gi18n-lib.h>
32 
33 #include "camel-pop3-folder.h"
34 #include "camel-pop3-settings.h"
35 #include "camel-pop3-store.h"
36 
37 #ifdef G_OS_WIN32
38 #include <winsock2.h>
39 #include <ws2tcpip.h>
40 #endif
41 
42 /* Specified in RFC 1939 */
43 #define POP3_PORT  110
44 #define POP3S_PORT 995
45 
46 /* defines the length of the server error message we can display in the error dialog */
47 #define POP3_ERROR_SIZE_LIMIT 60
48 
49 struct _CamelPOP3StorePrivate {
50 	GMutex property_lock;
51 	CamelDataCache *cache;
52 	CamelPOP3Engine *engine;
53 };
54 
55 enum {
56 	PROP_0,
57 	PROP_CONNECTABLE,
58 	PROP_HOST_REACHABLE
59 };
60 
61 extern CamelServiceAuthType camel_pop3_password_authtype;
62 extern CamelServiceAuthType camel_pop3_apop_authtype;
63 
64 /* Forward Declarations */
65 static void camel_network_service_init (CamelNetworkServiceInterface *iface);
66 
G_DEFINE_TYPE_WITH_CODE(CamelPOP3Store,camel_pop3_store,CAMEL_TYPE_STORE,G_ADD_PRIVATE (CamelPOP3Store)G_IMPLEMENT_INTERFACE (CAMEL_TYPE_NETWORK_SERVICE,camel_network_service_init))67 G_DEFINE_TYPE_WITH_CODE (
68 	CamelPOP3Store,
69 	camel_pop3_store,
70 	CAMEL_TYPE_STORE,
71 	G_ADD_PRIVATE (CamelPOP3Store)
72 	G_IMPLEMENT_INTERFACE (
73 		CAMEL_TYPE_NETWORK_SERVICE,
74 		camel_network_service_init))
75 
76 /* returns error message with ': ' as prefix */
77 static gchar *
78 get_valid_utf8_error (const gchar *text)
79 {
80 	gchar *tmp = camel_utf8_make_valid (text);
81 	gchar *ret = NULL;
82 
83 	/*TODO If the error message > size limit log it somewhere */
84 	if (!tmp || g_utf8_strlen (tmp, -1) > POP3_ERROR_SIZE_LIMIT) {
85 		g_free (tmp);
86 		return NULL;
87 	}
88 
89 	/* Translators: This is the separator between an error and an explanation */
90 	ret = g_strconcat (_(": "), tmp, NULL);
91 
92 	g_free (tmp);
93 	return ret;
94 }
95 
96 static gboolean
connect_to_server(CamelService * service,GCancellable * cancellable,GError ** error)97 connect_to_server (CamelService *service,
98                    GCancellable *cancellable,
99                    GError **error)
100 {
101 	CamelPOP3Store *store = CAMEL_POP3_STORE (service);
102 	CamelNetworkSettings *network_settings;
103 	CamelNetworkSecurityMethod method;
104 	CamelSettings *settings;
105 	CamelStream *stream = NULL;
106 	CamelPOP3Engine *pop3_engine = NULL;
107 	CamelPOP3Command *pc;
108 	GIOStream *base_stream;
109 	GIOStream *tls_stream;
110 	gboolean disable_extensions;
111 	gboolean success = TRUE;
112 	gchar *host;
113 	guint32 flags = 0;
114 	gint ret;
115 	GError *local_error = NULL;
116 
117 	settings = camel_service_ref_settings (service);
118 
119 	network_settings = CAMEL_NETWORK_SETTINGS (settings);
120 	host = camel_network_settings_dup_host (network_settings);
121 	method = camel_network_settings_get_security_method (network_settings);
122 
123 	disable_extensions = camel_pop3_settings_get_disable_extensions (
124 		CAMEL_POP3_SETTINGS (settings));
125 
126 	g_object_unref (settings);
127 
128 	base_stream = camel_network_service_connect_sync (
129 		CAMEL_NETWORK_SERVICE (service), cancellable, error);
130 
131 	if (base_stream != NULL) {
132 		stream = camel_stream_new (base_stream);
133 		g_object_unref (base_stream);
134 	} else {
135 		success = FALSE;
136 		goto exit;
137 	}
138 
139 	/* parent class connect initialization */
140 	if (CAMEL_SERVICE_CLASS (camel_pop3_store_parent_class)->
141 		connect_sync (service, cancellable, error) == FALSE) {
142 		g_object_unref (stream);
143 		success = FALSE;
144 		goto exit;
145 	}
146 
147 	if (disable_extensions)
148 		flags |= CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS;
149 
150 	if (!(pop3_engine = camel_pop3_engine_new (stream, flags, cancellable, &local_error)) ||
151 	    local_error != NULL) {
152 		if (local_error)
153 			g_propagate_error (error, local_error);
154 		else
155 			g_set_error (
156 				error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
157 				_("Failed to read a valid greeting from POP server %s"),
158 				host);
159 		g_object_unref (stream);
160 		success = FALSE;
161 		goto exit;
162 	}
163 
164 	if (method != CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
165 		g_object_unref (stream);
166 		goto exit;
167 	}
168 
169 	if (!(pop3_engine->capa & CAMEL_POP3_CAP_STLS)) {
170 		g_set_error (
171 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
172 			_("Failed to connect to POP server %s in secure mode: %s"),
173 			host, _("STLS not supported by server"));
174 		goto stls_exception;
175 	}
176 
177 	pc = camel_pop3_engine_command_new (
178 		pop3_engine, 0, NULL, NULL,
179 		cancellable, error, "STLS\r\n");
180 	while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
181 		;
182 
183 	ret = pc->state == CAMEL_POP3_COMMAND_OK;
184 	camel_pop3_engine_command_free (pop3_engine, pc);
185 
186 	if (ret == FALSE) {
187 		gchar *tmp;
188 
189 		tmp = get_valid_utf8_error ((gchar *) pop3_engine->line);
190 		g_set_error (
191 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
192 			/* Translators: Last %s is an optional
193 			 * explanation beginning with ": " separator. */
194 			_("Failed to connect to POP server %s in secure mode%s"),
195 			host, (tmp != NULL) ? tmp : "");
196 		g_free (tmp);
197 		goto stls_exception;
198 	}
199 
200 	/* Okay, now toggle SSL/TLS mode */
201 	base_stream = camel_stream_ref_base_stream (stream);
202 	tls_stream = camel_network_service_starttls (
203 		CAMEL_NETWORK_SERVICE (service), base_stream, error);
204 	g_object_unref (base_stream);
205 
206 	if (tls_stream != NULL) {
207 		camel_stream_set_base_stream (stream, tls_stream);
208 		/* Truncate any left cached input from the insecure part of the session */
209 		camel_pop3_stream_discard_cache (pop3_engine->stream);
210 		g_object_unref (tls_stream);
211 	} else {
212 		g_prefix_error (
213 			error,
214 			_("Failed to connect to POP server %s in secure mode: "),
215 			host);
216 		goto stls_exception;
217 	}
218 
219 	g_clear_object (&stream);
220 
221 	/* rfc2595, section 4 states that after a successful STLS
222 	 * command, the client MUST discard prior CAPA responses */
223 	if (!camel_pop3_engine_reget_capabilities (pop3_engine, cancellable, error))
224 		goto exception;
225 
226 	goto exit;
227 
228 stls_exception:
229 	/* As soon as we send a STLS command, all hope
230 	 * is lost of a clean QUIT if problems arise. */
231 	/* if (clean_quit) {
232 		/ * try to disconnect cleanly * /
233 		pc = camel_pop3_engine_command_new (
234 			pop3_engine, 0, NULL, NULL,
235 			cancellable, NULL, "QUIT\r\n");
236 		while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
237 			;
238 		camel_pop3_engine_command_free (pop3_engine, pc);
239 	}*/
240 
241 exception:
242 	g_clear_object (&stream);
243 	g_clear_object (&pop3_engine);
244 
245 	success = FALSE;
246 
247 exit:
248 	g_free (host);
249 
250 	g_mutex_lock (&store->priv->property_lock);
251 	if (pop3_engine != NULL)
252 		store->priv->engine = g_object_ref (pop3_engine);
253 	g_mutex_unlock (&store->priv->property_lock);
254 
255 	g_clear_object (&pop3_engine);
256 
257 	return success;
258 }
259 
260 static CamelAuthenticationResult
try_sasl(CamelPOP3Store * store,const gchar * mechanism,GCancellable * cancellable,GError ** error)261 try_sasl (CamelPOP3Store *store,
262           const gchar *mechanism,
263           GCancellable *cancellable,
264           GError **error)
265 {
266 	CamelPOP3Engine *pop3_engine;
267 	CamelPOP3Stream *pop3_stream;
268 	CamelNetworkSettings *network_settings;
269 	CamelAuthenticationResult result;
270 	CamelSettings *settings;
271 	CamelService *service;
272 	guchar *line, *resp;
273 	CamelSasl *sasl = NULL;
274 	gchar *string;
275 	gchar *host;
276 	guint len;
277 	gint ret;
278 
279 	service = CAMEL_SERVICE (store);
280 
281 	settings = camel_service_ref_settings (service);
282 
283 	network_settings = CAMEL_NETWORK_SETTINGS (settings);
284 	host = camel_network_settings_dup_host (network_settings);
285 
286 	g_object_unref (settings);
287 
288 	pop3_engine = camel_pop3_store_ref_engine (store);
289 	if (!pop3_engine) {
290 		g_set_error_literal (
291 			error, CAMEL_SERVICE_ERROR,
292 			CAMEL_SERVICE_ERROR_UNAVAILABLE,
293 			_("You must be working online to complete this operation"));
294 		result = CAMEL_AUTHENTICATION_ERROR;
295 		goto exit;
296 	}
297 
298 	pop3_stream = pop3_engine->stream;
299 
300 	sasl = camel_sasl_new ("pop", mechanism, service);
301 	if (sasl == NULL) {
302 		g_set_error (
303 			error, CAMEL_SERVICE_ERROR,
304 			CAMEL_SERVICE_ERROR_URL_INVALID,
305 			_("No support for %s authentication"), mechanism);
306 		result = CAMEL_AUTHENTICATION_ERROR;
307 		goto exit;
308 	}
309 
310 	string = g_strdup_printf ("AUTH %s\r\n", camel_sasl_is_xoauth2_alias (mechanism) ? "XOAUTH2" : mechanism);
311 	ret = camel_stream_write_string (
312 		CAMEL_STREAM (pop3_stream), string, cancellable, error);
313 	g_free (string);
314 
315 	if (ret == -1)
316 		goto ioerror;
317 
318 	while (1) {
319 		GError *local_error = NULL;
320 
321 		if (camel_pop3_stream_line (pop3_stream, &line, &len, cancellable, error) == -1)
322 			goto ioerror;
323 
324 		if (strncmp ((gchar *) line, "+OK", 3) == 0) {
325 			result = CAMEL_AUTHENTICATION_ACCEPTED;
326 			break;
327 		}
328 
329 		if (strncmp ((gchar *) line, "-ERR", 4) == 0) {
330 			result = CAMEL_AUTHENTICATION_REJECTED;
331 			break;
332 		}
333 
334 		/* If we dont get continuation, or the sasl object's run out
335 		 * of work, or we dont get a challenge, its a protocol error,
336 		 * so fail, and try reset the server. */
337 		if (strncmp ((gchar *) line, "+ ", 2) != 0
338 		    || camel_sasl_get_authenticated (sasl)
339 		    || (resp = (guchar *) camel_sasl_challenge_base64_sync (sasl, (const gchar *) line + 2, cancellable, &local_error)) == NULL) {
340 			if (camel_stream_write_string (CAMEL_STREAM (pop3_stream), "*\r\n", cancellable, NULL)) {
341 				/* coverity[unchecked_value] */
342 				if (!camel_pop3_stream_line (pop3_stream, &line, &len, cancellable, NULL)) {
343 					;
344 				}
345 			}
346 
347 			if (local_error) {
348 				g_propagate_error (error, local_error);
349 				local_error = NULL;
350 				goto ioerror;
351 			}
352 
353 			g_set_error (
354 				error, CAMEL_SERVICE_ERROR,
355 				CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
356 				_("Cannot login to POP server %s: "
357 				"SASL Protocol error"), host);
358 			result = CAMEL_AUTHENTICATION_ERROR;
359 			goto exit;
360 		}
361 
362 		string = g_strdup_printf ("%s\r\n", resp);
363 		ret = camel_stream_write_string (
364 			CAMEL_STREAM (pop3_stream), string, cancellable, error);
365 		g_free (string);
366 
367 		g_free (resp);
368 
369 		if (ret == -1)
370 			goto ioerror;
371 
372 	}
373 
374 	goto exit;
375 
376 ioerror:
377 	g_prefix_error (
378 		error, _("Failed to authenticate on POP server %s: "), host);
379 	result = CAMEL_AUTHENTICATION_ERROR;
380 
381 exit:
382 	if (sasl != NULL)
383 		g_object_unref (sasl);
384 
385 	g_free (host);
386 
387 	g_clear_object (&pop3_engine);
388 
389 	return result;
390 }
391 
392 static void
pop3_store_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)393 pop3_store_set_property (GObject *object,
394                          guint property_id,
395                          const GValue *value,
396                          GParamSpec *pspec)
397 {
398 	switch (property_id) {
399 		case PROP_CONNECTABLE:
400 			camel_network_service_set_connectable (
401 				CAMEL_NETWORK_SERVICE (object),
402 				g_value_get_object (value));
403 			return;
404 	}
405 
406 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
407 }
408 
409 static void
pop3_store_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)410 pop3_store_get_property (GObject *object,
411                          guint property_id,
412                          GValue *value,
413                          GParamSpec *pspec)
414 {
415 	switch (property_id) {
416 		case PROP_CONNECTABLE:
417 			g_value_take_object (
418 				value,
419 				camel_network_service_ref_connectable (
420 				CAMEL_NETWORK_SERVICE (object)));
421 			return;
422 
423 		case PROP_HOST_REACHABLE:
424 			g_value_set_boolean (
425 				value,
426 				camel_network_service_get_host_reachable (
427 				CAMEL_NETWORK_SERVICE (object)));
428 			return;
429 	}
430 
431 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
432 }
433 
434 static void
pop3_store_dispose(GObject * object)435 pop3_store_dispose (GObject *object)
436 {
437 	CamelPOP3StorePrivate *priv;
438 
439 	priv = CAMEL_POP3_STORE (object)->priv;
440 
441 	/* Force disconnect so we dont have it run
442 	 * later, after we've cleaned up some stuff. */
443 	camel_service_disconnect_sync (
444 		CAMEL_SERVICE (object), TRUE, NULL, NULL);
445 
446 	g_clear_object (&priv->cache);
447 	g_clear_object (&priv->engine);
448 
449 	/* Chain up to parent's dispose() method. */
450 	G_OBJECT_CLASS (camel_pop3_store_parent_class)->dispose (object);
451 }
452 
453 static void
pop3_store_finalize(GObject * object)454 pop3_store_finalize (GObject *object)
455 {
456 	CamelPOP3StorePrivate *priv;
457 
458 	priv = CAMEL_POP3_STORE (object)->priv;
459 
460 	g_mutex_clear (&priv->property_lock);
461 
462 	/* Chain up to parent's finalize() method. */
463 	G_OBJECT_CLASS (camel_pop3_store_parent_class)->finalize (object);
464 }
465 
466 static gchar *
pop3_store_get_name(CamelService * service,gboolean brief)467 pop3_store_get_name (CamelService *service,
468                      gboolean brief)
469 {
470 	CamelNetworkSettings *network_settings;
471 	CamelSettings *settings;
472 	gchar *host;
473 	gchar *user;
474 	gchar *name;
475 
476 	settings = camel_service_ref_settings (service);
477 
478 	network_settings = CAMEL_NETWORK_SETTINGS (settings);
479 	host = camel_network_settings_dup_host (network_settings);
480 	user = camel_network_settings_dup_user (network_settings);
481 
482 	g_object_unref (settings);
483 
484 	if (brief)
485 		name = g_strdup_printf (
486 			_("POP3 server %s"), host);
487 	else
488 		name = g_strdup_printf (
489 			_("POP3 server for %s on %s"), user, host);
490 
491 	g_free (host);
492 	g_free (user);
493 
494 	return name;
495 }
496 
497 static gboolean
pop3_store_connect_sync(CamelService * service,GCancellable * cancellable,GError ** error)498 pop3_store_connect_sync (CamelService *service,
499                          GCancellable *cancellable,
500                          GError **error)
501 {
502 	CamelPOP3Store *store = (CamelPOP3Store *) service;
503 	CamelPOP3Engine *pop3_engine;
504 	CamelSettings *settings;
505 	CamelSession *session;
506 	const gchar *user_data_dir;
507 	gboolean success = TRUE;
508 	gchar *mechanism;
509 
510 	/* Chain up to parent's method. */
511 	if (!CAMEL_SERVICE_CLASS (camel_pop3_store_parent_class)->connect_sync (service, cancellable, error))
512 		return FALSE;
513 
514 	session = camel_service_ref_session (service);
515 	user_data_dir = camel_service_get_user_data_dir (service);
516 
517 	settings = camel_service_ref_settings (service);
518 
519 	mechanism = camel_network_settings_dup_auth_mechanism (
520 		CAMEL_NETWORK_SETTINGS (settings));
521 
522 	g_object_unref (settings);
523 
524 	if (!session || !camel_session_get_online (session)) {
525 		g_set_error (
526 			error, CAMEL_SERVICE_ERROR,
527 			CAMEL_SERVICE_ERROR_UNAVAILABLE,
528 			_("You must be working online to complete this operation"));
529 		success = FALSE;
530 		goto exit;
531 	}
532 
533 	g_mutex_lock (&store->priv->property_lock);
534 
535 	if (store->priv->cache == NULL) {
536 		CamelDataCache *cache;
537 
538 		cache = camel_data_cache_new (user_data_dir, error);
539 		if (cache != NULL) {
540 			/* Ensure cache will never expire, otherwise
541 			 * it causes redownload of messages. */
542 			camel_data_cache_set_expire_age (cache, -1);
543 			camel_data_cache_set_expire_access (cache, -1);
544 
545 			store->priv->cache = g_object_ref (cache);
546 
547 			g_object_unref (cache);
548 		}
549 	}
550 
551 	g_mutex_unlock (&store->priv->property_lock);
552 
553 	success = connect_to_server (service, cancellable, error);
554 
555 	if (!success)
556 		goto exit;
557 
558 	success = camel_session_authenticate_sync (
559 		session, service, mechanism, cancellable, error);
560 
561 	if (!success)
562 		goto exit;
563 
564 	/* Now that we are in the TRANSACTION state,
565 	 * try regetting the capabilities */
566 	pop3_engine = camel_pop3_store_ref_engine (store);
567 	if (pop3_engine) {
568 		pop3_engine->state = CAMEL_POP3_ENGINE_TRANSACTION;
569 		if (!camel_pop3_engine_reget_capabilities (pop3_engine, cancellable, error))
570 			success = FALSE;
571 		g_clear_object (&pop3_engine);
572 	} else {
573 		g_set_error_literal (
574 			error, CAMEL_SERVICE_ERROR,
575 			CAMEL_SERVICE_ERROR_UNAVAILABLE,
576 			_("You must be working online to complete this operation"));
577 		success = FALSE;
578 	}
579 
580 exit:
581 	g_free (mechanism);
582 
583 	g_object_unref (session);
584 
585 	if (!success) {
586 		/* to not leak possible connection to the server */
587 		g_mutex_lock (&store->priv->property_lock);
588 		g_clear_object (&store->priv->engine);
589 		g_mutex_unlock (&store->priv->property_lock);
590 	}
591 
592 	return success;
593 }
594 
595 static gboolean
pop3_store_disconnect_sync(CamelService * service,gboolean clean,GCancellable * cancellable,GError ** error)596 pop3_store_disconnect_sync (CamelService *service,
597                             gboolean clean,
598                             GCancellable *cancellable,
599                             GError **error)
600 {
601 	CamelServiceClass *service_class;
602 	CamelPOP3Store *store = CAMEL_POP3_STORE (service);
603 	gboolean success;
604 
605 	if (clean) {
606 		CamelPOP3Engine *pop3_engine;
607 		CamelPOP3Command *pc;
608 
609 		pop3_engine = camel_pop3_store_ref_engine (store);
610 
611 		if (pop3_engine) {
612 			if (camel_pop3_engine_busy_lock (pop3_engine, cancellable, NULL)) {
613 				pc = camel_pop3_engine_command_new (
614 					pop3_engine, 0, NULL, NULL,
615 					cancellable, error, "QUIT\r\n");
616 				while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
617 					;
618 				camel_pop3_engine_command_free (pop3_engine, pc);
619 				camel_pop3_engine_busy_unlock (pop3_engine);
620 			}
621 
622 			g_clear_object (&pop3_engine);
623 		}
624 	}
625 
626 	/* Chain up to parent's disconnect() method. */
627 	service_class = CAMEL_SERVICE_CLASS (camel_pop3_store_parent_class);
628 
629 	success = service_class->disconnect_sync (service, clean, cancellable, error);
630 
631 	g_mutex_lock (&store->priv->property_lock);
632 	g_clear_object (&store->priv->engine);
633 	g_mutex_unlock (&store->priv->property_lock);
634 
635 	return success;
636 }
637 
638 static CamelAuthenticationResult
pop3_store_authenticate_sync(CamelService * service,const gchar * mechanism,GCancellable * cancellable,GError ** error)639 pop3_store_authenticate_sync (CamelService *service,
640                               const gchar *mechanism,
641                               GCancellable *cancellable,
642                               GError **error)
643 {
644 	CamelPOP3Store *store = CAMEL_POP3_STORE (service);
645 	CamelNetworkSettings *network_settings;
646 	CamelAuthenticationResult result;
647 	CamelSettings *settings;
648 	CamelPOP3Command *pcu = NULL;
649 	CamelPOP3Command *pcp = NULL;
650 	CamelPOP3Engine *pop3_engine;
651 	const gchar *password;
652 	gboolean enable_utf8;
653 	gchar *host;
654 	gchar *user;
655 	gint status;
656 
657 	password = camel_service_get_password (service);
658 
659 	settings = camel_service_ref_settings (service);
660 	enable_utf8 = camel_pop3_settings_get_enable_utf8 (CAMEL_POP3_SETTINGS (settings));
661 
662 	network_settings = CAMEL_NETWORK_SETTINGS (settings);
663 	host = camel_network_settings_dup_host (network_settings);
664 	user = camel_network_settings_dup_user (network_settings);
665 
666 	g_object_unref (settings);
667 
668 	pop3_engine = camel_pop3_store_ref_engine (store);
669 	if (!pop3_engine) {
670 		g_set_error_literal (
671 			error, CAMEL_SERVICE_ERROR,
672 			CAMEL_SERVICE_ERROR_UNAVAILABLE,
673 			_("You must be working online to complete this operation"));
674 		result = CAMEL_AUTHENTICATION_ERROR;
675 		goto exit;
676 	}
677 
678 	if (!camel_pop3_engine_busy_lock (pop3_engine, cancellable, error)) {
679 		g_free (host);
680 		g_free (user);
681 		g_clear_object (&pop3_engine);
682 
683 		return CAMEL_AUTHENTICATION_ERROR;
684 	}
685 
686 	if ((pop3_engine->capa & CAMEL_POP3_CAP_UTF8) != 0 && enable_utf8) {
687 		pcu = camel_pop3_engine_command_new (
688 			pop3_engine, 0, NULL, NULL, cancellable, error,
689 			"UTF8");
690 		if (error && *error) {
691 			g_prefix_error (
692 				error,
693 				_("Unable to connect to POP server %s.\n"
694 				"Error enabling UTF-8 mode: "), host);
695 			result = CAMEL_AUTHENTICATION_ERROR;
696 			goto exit;
697 		}
698 		while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
699 			;
700 		camel_pop3_engine_command_free (pop3_engine, pcu);
701 		pcu = NULL;
702 	}
703 
704 	if (mechanism == NULL) {
705 		if (password == NULL) {
706 			g_set_error_literal (
707 				error, CAMEL_SERVICE_ERROR,
708 				CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
709 				_("Authentication password not available"));
710 			result = CAMEL_AUTHENTICATION_ERROR;
711 			goto exit;
712 		}
713 
714 		/* pop engine will take care of pipelining ability */
715 		pcu = camel_pop3_engine_command_new (
716 			pop3_engine, 0, NULL, NULL, cancellable, error,
717 			"USER %s\r\n", user);
718 		if (error && *error) {
719 			g_prefix_error (
720 				error,
721 				_("Unable to connect to POP server %s.\n"
722 				"Error sending password: "), host);
723 			result = CAMEL_AUTHENTICATION_ERROR;
724 			goto exit;
725 		}
726 
727 		pcp = camel_pop3_engine_command_new (
728 			pop3_engine, 0, NULL, NULL, cancellable, error,
729 			"PASS %s\r\n", password);
730 
731 		if (error && *error) {
732 			g_prefix_error (
733 				error,
734 				_("Unable to connect to POP server %s.\n"
735 				"Error sending password: "), host);
736 			result = CAMEL_AUTHENTICATION_ERROR;
737 			goto exit;
738 		}
739 	} else if (strcmp (mechanism, "+APOP") == 0 && pop3_engine->apop) {
740 		gchar *secret, *md5asc, *d;
741 		gsize secret_len;
742 
743 		if (password == NULL) {
744 			g_set_error_literal (
745 				error, CAMEL_SERVICE_ERROR,
746 				CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
747 				_("Authentication password not available"));
748 			result = CAMEL_AUTHENTICATION_ERROR;
749 			goto exit;
750 		}
751 
752 		d = pop3_engine->apop;
753 
754 		while (*d != '\0') {
755 			if (!isascii ((gint) * d)) {
756 
757 				g_set_error (
758 					error, CAMEL_SERVICE_ERROR,
759 					CAMEL_SERVICE_ERROR_URL_INVALID,
760 					/* Translators: Do not translate APOP. */
761 					_("Unable to connect to POP server %s:	"
762 					"Invalid APOP ID received. Impersonation "
763 					"attack suspected. Please contact your admin."),
764 					host);
765 
766 				result = CAMEL_AUTHENTICATION_ERROR;
767 				goto exit;
768 			}
769 			d++;
770 		}
771 
772 		secret_len =
773 			strlen (pop3_engine->apop) +
774 			strlen (password) + 1;
775 		secret = g_alloca (secret_len);
776 		g_snprintf (
777 			secret, secret_len, "%s%s",
778 			pop3_engine->apop, password);
779 		md5asc = g_compute_checksum_for_string (
780 			G_CHECKSUM_MD5, secret, -1);
781 		pcp = camel_pop3_engine_command_new (
782 			pop3_engine, 0, NULL, NULL, cancellable, error,
783 			"APOP %s %s\r\n", user, md5asc);
784 		g_free (md5asc);
785 
786 	} else {
787 		GList *link;
788 		const gchar *test_mechanism = mechanism;
789 
790 		if (camel_sasl_is_xoauth2_alias (test_mechanism))
791 			test_mechanism = "XOAUTH2";
792 
793 		link = pop3_engine->auth;
794 		while (link != NULL) {
795 			CamelServiceAuthType *auth = link->data;
796 
797 			if (g_strcmp0 (auth->authproto, test_mechanism) == 0) {
798 				result = try_sasl (
799 					store, mechanism,
800 					cancellable, error);
801 				goto exit;
802 			}
803 			link = g_list_next (link);
804 		}
805 
806 		g_set_error (
807 			error, CAMEL_SERVICE_ERROR,
808 			CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
809 			_("No support for %s authentication"), mechanism);
810 		result = CAMEL_AUTHENTICATION_ERROR;
811 		goto exit;
812 	}
813 
814 	while ((status = camel_pop3_engine_iterate (pop3_engine, pcp, cancellable, error)) > 0)
815 		;
816 
817 	if (status == -1) {
818 		g_prefix_error (
819 			error,
820 			_("Unable to connect to POP server %s.\n"
821 			"Error sending password: "), host);
822 		result = CAMEL_AUTHENTICATION_ERROR;
823 
824 	} else if (pcu && pcu->state != CAMEL_POP3_COMMAND_OK) {
825 		gchar *tmp;
826 
827 		/* Abort authentication if the server rejects the user
828 		 * name.  Reprompting for a password won't do any good. */
829 		tmp = get_valid_utf8_error ((gchar *) pop3_engine->line);
830 		g_set_error (
831 			error, CAMEL_SERVICE_ERROR,
832 			CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
833 			/* Translators: Last %s is an optional explanation
834 			 * beginning with ": " separator. */
835 			_("Unable to connect to POP server %s.\n"
836 			"Error sending username%s"),
837 			host, (tmp != NULL) ? tmp : "");
838 		g_free (tmp);
839 		result = CAMEL_AUTHENTICATION_ERROR;
840 
841 	} else if (pcp->state != CAMEL_POP3_COMMAND_OK) {
842 		result = CAMEL_AUTHENTICATION_REJECTED;
843 	} else {
844 		result = CAMEL_AUTHENTICATION_ACCEPTED;
845 	}
846 
847 exit:
848 	if (pcp != NULL)
849 		camel_pop3_engine_command_free (pop3_engine, pcp);
850 
851 	if (pcu != NULL)
852 		camel_pop3_engine_command_free (pop3_engine, pcu);
853 
854 	g_free (host);
855 	g_free (user);
856 
857 	camel_pop3_engine_busy_unlock (pop3_engine);
858 	g_clear_object (&pop3_engine);
859 
860 	return result;
861 }
862 
863 static GList *
pop3_store_query_auth_types_sync(CamelService * service,GCancellable * cancellable,GError ** error)864 pop3_store_query_auth_types_sync (CamelService *service,
865                                   GCancellable *cancellable,
866                                   GError **error)
867 {
868 	CamelServiceClass *service_class;
869 	CamelPOP3Store *store = CAMEL_POP3_STORE (service);
870 	GList *types = NULL;
871 	GError *local_error = NULL;
872 
873 	/* Chain up to parent's query_auth_types() method. */
874 	service_class = CAMEL_SERVICE_CLASS (camel_pop3_store_parent_class);
875 	types = service_class->query_auth_types_sync (
876 		service, cancellable, &local_error);
877 
878 	if (local_error != NULL) {
879 		g_propagate_error (error, local_error);
880 		return NULL;
881 	}
882 
883 	if (connect_to_server (service, cancellable, error)) {
884 		CamelPOP3Engine *pop3_engine;
885 
886 		pop3_engine = camel_pop3_store_ref_engine (store);
887 
888 		if (pop3_engine) {
889 			types = g_list_concat (types, g_list_copy (pop3_engine->auth));
890 			pop3_store_disconnect_sync (service, TRUE, cancellable, NULL);
891 
892 			g_clear_object (&pop3_engine);
893 		}
894 	}
895 
896 	return types;
897 }
898 
899 static gboolean
pop3_store_can_refresh_folder(CamelStore * store,CamelFolderInfo * info,GError ** error)900 pop3_store_can_refresh_folder (CamelStore *store,
901                                CamelFolderInfo *info,
902                                GError **error)
903 {
904 	/* any pop3 folder can be refreshed */
905 	return TRUE;
906 }
907 
908 static CamelFolder *
pop3_store_get_folder_sync(CamelStore * store,const gchar * folder_name,CamelStoreGetFolderFlags flags,GCancellable * cancellable,GError ** error)909 pop3_store_get_folder_sync (CamelStore *store,
910                             const gchar *folder_name,
911                             CamelStoreGetFolderFlags flags,
912                             GCancellable *cancellable,
913                             GError **error)
914 {
915 	if (g_ascii_strcasecmp (folder_name, "inbox") != 0) {
916 		g_set_error (
917 			error, CAMEL_FOLDER_ERROR,
918 			CAMEL_FOLDER_ERROR_INVALID,
919 			_("No such folder “%s”."), folder_name);
920 		return NULL;
921 	}
922 
923 	return camel_pop3_folder_new (store, cancellable, error);
924 }
925 
926 static CamelFolderInfo *
pop3_store_get_folder_info_sync(CamelStore * store,const gchar * top,CamelStoreGetFolderInfoFlags flags,GCancellable * cancellable,GError ** error)927 pop3_store_get_folder_info_sync (CamelStore *store,
928                                  const gchar *top,
929                                  CamelStoreGetFolderInfoFlags flags,
930                                  GCancellable *cancellable,
931                                  GError **error)
932 {
933 	g_set_error (
934 		error, CAMEL_STORE_ERROR,
935 		CAMEL_STORE_ERROR_NO_FOLDER,
936 		_("POP3 stores have no folder hierarchy"));
937 
938 	return NULL;
939 }
940 
941 static CamelFolder *
pop3_store_get_trash_folder_sync(CamelStore * store,GCancellable * cancellable,GError ** error)942 pop3_store_get_trash_folder_sync (CamelStore *store,
943                                   GCancellable *cancellable,
944                                   GError **error)
945 {
946 	/* no-op */
947 	return NULL;
948 }
949 
950 static const gchar *
pop3_store_get_service_name(CamelNetworkService * service,CamelNetworkSecurityMethod method)951 pop3_store_get_service_name (CamelNetworkService *service,
952                              CamelNetworkSecurityMethod method)
953 {
954 	const gchar *service_name;
955 
956 	switch (method) {
957 		case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
958 			service_name = "pop3s";
959 			break;
960 
961 		default:
962 			service_name = "pop3";
963 			break;
964 	}
965 
966 	return service_name;
967 }
968 
969 static guint16
pop3_store_get_default_port(CamelNetworkService * service,CamelNetworkSecurityMethod method)970 pop3_store_get_default_port (CamelNetworkService *service,
971                              CamelNetworkSecurityMethod method)
972 {
973 	guint16 default_port;
974 
975 	switch (method) {
976 		case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
977 			default_port = POP3S_PORT;
978 			break;
979 
980 		default:
981 			default_port = POP3_PORT;
982 			break;
983 	}
984 
985 	return default_port;
986 }
987 
988 static void
camel_pop3_store_class_init(CamelPOP3StoreClass * class)989 camel_pop3_store_class_init (CamelPOP3StoreClass *class)
990 {
991 	GObjectClass *object_class;
992 	CamelServiceClass *service_class;
993 	CamelStoreClass *store_class;
994 
995 	object_class = G_OBJECT_CLASS (class);
996 	object_class->set_property = pop3_store_set_property;
997 	object_class->get_property = pop3_store_get_property;
998 	object_class->dispose = pop3_store_dispose;
999 	object_class->finalize = pop3_store_finalize;
1000 
1001 	service_class = CAMEL_SERVICE_CLASS (class);
1002 	service_class->settings_type = CAMEL_TYPE_POP3_SETTINGS;
1003 	service_class->get_name = pop3_store_get_name;
1004 	service_class->connect_sync = pop3_store_connect_sync;
1005 	service_class->disconnect_sync = pop3_store_disconnect_sync;
1006 	service_class->authenticate_sync = pop3_store_authenticate_sync;
1007 	service_class->query_auth_types_sync = pop3_store_query_auth_types_sync;
1008 
1009 	store_class = CAMEL_STORE_CLASS (class);
1010 	store_class->can_refresh_folder = pop3_store_can_refresh_folder;
1011 	store_class->get_folder_sync = pop3_store_get_folder_sync;
1012 	store_class->get_folder_info_sync = pop3_store_get_folder_info_sync;
1013 	store_class->get_trash_folder_sync = pop3_store_get_trash_folder_sync;
1014 
1015 	/* Inherited from CamelNetworkService. */
1016 	g_object_class_override_property (
1017 		object_class,
1018 		PROP_CONNECTABLE,
1019 		"connectable");
1020 
1021 	/* Inherited from CamelNetworkService. */
1022 	g_object_class_override_property (
1023 		object_class,
1024 		PROP_HOST_REACHABLE,
1025 		"host-reachable");
1026 }
1027 
1028 static void
camel_network_service_init(CamelNetworkServiceInterface * iface)1029 camel_network_service_init (CamelNetworkServiceInterface *iface)
1030 {
1031 	iface->get_service_name = pop3_store_get_service_name;
1032 	iface->get_default_port = pop3_store_get_default_port;
1033 }
1034 
1035 static void
camel_pop3_store_init(CamelPOP3Store * pop3_store)1036 camel_pop3_store_init (CamelPOP3Store *pop3_store)
1037 {
1038 	pop3_store->priv = camel_pop3_store_get_instance_private (pop3_store);
1039 
1040 	g_mutex_init (&pop3_store->priv->property_lock);
1041 }
1042 
1043 /**
1044  * camel_pop3_store_ref_cache:
1045  * @store: a #CamelPOP3Store
1046  *
1047  * Returns the #CamelDataCache for @store.
1048  *
1049  * The returned #CamelDataCache is referenced for thread-safety and must be
1050  * unreferenced with g_object_unref() when finished with it.
1051  *
1052  * Returns: a #CamelDataCache
1053  **/
1054 CamelDataCache *
camel_pop3_store_ref_cache(CamelPOP3Store * store)1055 camel_pop3_store_ref_cache (CamelPOP3Store *store)
1056 {
1057 	CamelDataCache *cache = NULL;
1058 
1059 	g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), NULL);
1060 
1061 	g_mutex_lock (&store->priv->property_lock);
1062 
1063 	if (store->priv->cache != NULL)
1064 		cache = g_object_ref (store->priv->cache);
1065 
1066 	g_mutex_unlock (&store->priv->property_lock);
1067 
1068 	return cache;
1069 }
1070 
1071 /**
1072  * camel_pop3_store_ref_engine:
1073  * @store: a #CamelPOP3Store
1074  *
1075  * Returns the #CamelPOP3Engine for @store.
1076  *
1077  * The returned #CamelPOP3Engine is referenced for thread-safety and must be
1078  * unreferenced with g_object_unref() when finished with it.
1079  *
1080  * Returns: a #CamelPOP3Store
1081  **/
1082 CamelPOP3Engine *
camel_pop3_store_ref_engine(CamelPOP3Store * store)1083 camel_pop3_store_ref_engine (CamelPOP3Store *store)
1084 {
1085 	CamelPOP3Engine *engine = NULL;
1086 
1087 	g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), NULL);
1088 
1089 	g_mutex_lock (&store->priv->property_lock);
1090 
1091 	if (store->priv->engine != NULL)
1092 		engine = g_object_ref (store->priv->engine);
1093 
1094 	g_mutex_unlock (&store->priv->property_lock);
1095 
1096 	return engine;
1097 }
1098 
1099 /**
1100  * camel_pop3_store_expunge:
1101  * @store: a #CamelPOP3Store
1102  * @error: return location for a #GError, or %NULL
1103  * @cancellable: optional #GCancellable object, or %NULL
1104  *
1105  * Expunge messages from the store. This will result in the connection
1106  * being closed, which may cause later commands to fail if they can't
1107  * reconnect.
1108  **/
1109 gboolean
camel_pop3_store_expunge(CamelPOP3Store * store,GCancellable * cancellable,GError ** error)1110 camel_pop3_store_expunge (CamelPOP3Store *store,
1111                           GCancellable *cancellable,
1112                           GError **error)
1113 {
1114 	CamelPOP3Command *pc;
1115 	CamelPOP3Engine *pop3_engine;
1116 	CamelServiceConnectionStatus status;
1117 
1118 	status = camel_service_get_connection_status (CAMEL_SERVICE (store));
1119 
1120 	if (status != CAMEL_SERVICE_CONNECTED) {
1121 		g_set_error (
1122 			error, CAMEL_SERVICE_ERROR,
1123 			CAMEL_SERVICE_ERROR_UNAVAILABLE,
1124 			_("You must be working online to complete this operation"));
1125 		return FALSE;
1126 	}
1127 
1128 	pop3_engine = camel_pop3_store_ref_engine (store);
1129 
1130 	if (!camel_pop3_engine_busy_lock (pop3_engine, cancellable, error)) {
1131 		g_clear_object (&pop3_engine);
1132 		return FALSE;
1133 	}
1134 
1135 	pc = camel_pop3_engine_command_new (
1136 		pop3_engine, 0, NULL, NULL, cancellable, error, "QUIT\r\n");
1137 
1138 	while (camel_pop3_engine_iterate (pop3_engine, NULL, cancellable, NULL) > 0)
1139 		;
1140 
1141 	camel_pop3_engine_command_free (pop3_engine, pc);
1142 
1143 	camel_pop3_engine_busy_unlock (pop3_engine);
1144 	g_clear_object (&pop3_engine);
1145 
1146 	return TRUE;
1147 }
1148 
1149 /**
1150  * camel_pop3_store_cache_add:
1151  * @store: a #CamelPOP3Store
1152  * @uid: a message UID
1153  * @error: return location for a #GError, or %NULL
1154  *
1155  * Creates a cache file for @uid in @store and returns a #CamelStream for it.
1156  * If an error occurs in opening the cache file, the function sets @error and
1157  * returns %NULL.
1158  *
1159  * The returned #CamelStream is referenced for thread-safety and must be
1160  * unreferenced when finished with it.
1161  *
1162  * Returns: a #CamelStream, or %NULL
1163  **/
1164 CamelStream *
camel_pop3_store_cache_add(CamelPOP3Store * store,const gchar * uid,GError ** error)1165 camel_pop3_store_cache_add (CamelPOP3Store *store,
1166                             const gchar *uid,
1167                             GError **error)
1168 {
1169 	CamelDataCache *cache;
1170 	GIOStream *base_stream;
1171 	CamelStream *stream = NULL;
1172 
1173 	g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), NULL);
1174 	g_return_val_if_fail (uid != NULL, NULL);
1175 
1176 	cache = camel_pop3_store_ref_cache (store);
1177 	g_return_val_if_fail (cache != NULL, NULL);
1178 
1179 	base_stream = camel_data_cache_add (cache, "cache", uid, error);
1180 	if (base_stream != NULL) {
1181 		stream = camel_stream_new (base_stream);
1182 		g_object_unref (base_stream);
1183 	}
1184 
1185 	g_object_unref (cache);
1186 
1187 	return stream;
1188 }
1189 
1190 /**
1191  * camel_pop3_store_cache_get:
1192  * @store: a #CamelPOP3Store
1193  * @uid: a message UID
1194  * @error: return location for a #GError, or %NULL
1195  *
1196  * Opens the cache file for @uid in @store and returns a #CamelStream for it.
1197  * If no matching cache file exists, the function returns %NULL.  If an error
1198  * occurs in opening the cache file, the function sets @error and returns
1199  * %NULL.
1200  *
1201  * The returned #CamelStream is referenced for thread-safety and must be
1202  * unreferenced when finished with it.
1203  *
1204  * Returns: a #CamelStream, or %NULL
1205  **/
1206 CamelStream *
camel_pop3_store_cache_get(CamelPOP3Store * store,const gchar * uid,GError ** error)1207 camel_pop3_store_cache_get (CamelPOP3Store *store,
1208                             const gchar *uid,
1209                             GError **error)
1210 {
1211 	CamelDataCache *cache;
1212 	GIOStream *base_stream;
1213 	CamelStream *stream = NULL;
1214 
1215 	g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), NULL);
1216 	g_return_val_if_fail (uid != NULL, NULL);
1217 
1218 	cache = camel_pop3_store_ref_cache (store);
1219 	g_return_val_if_fail (cache != NULL, NULL);
1220 
1221 	base_stream = camel_data_cache_get (cache, "cache", uid, error);
1222 	if (base_stream != NULL) {
1223 		GInputStream *input_stream;
1224 		gchar buffer[1];
1225 		gssize n_bytes;
1226 
1227 		input_stream = g_io_stream_get_input_stream (base_stream);
1228 
1229 		n_bytes = g_input_stream_read (
1230 			input_stream, buffer, 1, NULL, error);
1231 
1232 		if (n_bytes == 1 && buffer[0] == '#')
1233 			stream = camel_stream_new (base_stream);
1234 
1235 		g_object_unref (base_stream);
1236 	}
1237 
1238 	g_object_unref (cache);
1239 
1240 	return stream;
1241 }
1242 
1243 /**
1244  * camel_pop3_store_cache_has:
1245  * @store: a #CamelPOP3Store
1246  * @uid: a message UID
1247  *
1248  * Returns whether @store has a cached message for @uid.
1249  *
1250  * Returns: whether @uid is cached
1251  **/
1252 gboolean
camel_pop3_store_cache_has(CamelPOP3Store * store,const gchar * uid)1253 camel_pop3_store_cache_has (CamelPOP3Store *store,
1254                             const gchar *uid)
1255 {
1256 	CamelStream *stream;
1257 	gboolean uid_is_cached;
1258 
1259 	g_return_val_if_fail (CAMEL_IS_POP3_STORE (store), FALSE);
1260 	g_return_val_if_fail (uid != NULL, FALSE);
1261 
1262 	stream = camel_pop3_store_cache_get (store, uid, NULL);
1263 	uid_is_cached = (stream != NULL);
1264 	g_clear_object (&stream);
1265 
1266 	return uid_is_cached;
1267 }
1268 
1269