1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-pop3-store.c : class for a pop3 store */
6 * Dan Winship <danw@ximian.com>
7 * Michael Zucchi <notzed@ximian.com>
9 * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com)
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of version 2 of the GNU Lesser General Public
13 * License as published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
35 #include <sys/types.h>
38 #include <glib/gi18n-lib.h>
40 #include <libedataserver/md5-utils.h>
42 #include "camel-data-cache.h"
43 #include "camel-exception.h"
44 #include "camel-net-utils.h"
45 #include "camel-operation.h"
46 #include "camel-pop3-engine.h"
47 #include "camel-pop3-folder.h"
48 #include "camel-pop3-store.h"
49 #include "camel-sasl.h"
50 #include "camel-session.h"
51 #include "camel-stream-buffer.h"
52 #include "camel-tcp-stream-raw.h"
53 #include "camel-tcp-stream.h"
54 #include "camel-url.h"
57 #include "camel-tcp-stream-ssl.h"
60 /* Specified in RFC 1939 */
61 #define POP3_PORT "110"
62 #define POP3S_PORT "995"
64 static CamelStoreClass *parent_class = NULL;
66 static void finalize (CamelObject *object);
68 static gboolean pop3_connect (CamelService *service, CamelException *ex);
69 static gboolean pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex);
70 static GList *query_auth_types (CamelService *service, CamelException *ex);
72 static CamelFolder *get_folder (CamelStore *store, const char *folder_name,
73 guint32 flags, CamelException *ex);
75 static CamelFolder *get_trash (CamelStore *store, CamelException *ex);
78 camel_pop3_store_class_init (CamelPOP3StoreClass *camel_pop3_store_class)
80 CamelServiceClass *camel_service_class =
81 CAMEL_SERVICE_CLASS (camel_pop3_store_class);
82 CamelStoreClass *camel_store_class =
83 CAMEL_STORE_CLASS (camel_pop3_store_class);
85 parent_class = CAMEL_STORE_CLASS (camel_type_get_global_classfuncs (camel_store_get_type ()));
87 /* virtual method overload */
88 camel_service_class->query_auth_types = query_auth_types;
89 camel_service_class->connect = pop3_connect;
90 camel_service_class->disconnect = pop3_disconnect;
92 camel_store_class->get_folder = get_folder;
93 camel_store_class->get_trash = get_trash;
99 camel_pop3_store_init (gpointer object, gpointer klass)
105 camel_pop3_store_get_type (void)
107 static CamelType camel_pop3_store_type = CAMEL_INVALID_TYPE;
109 if (!camel_pop3_store_type) {
110 camel_pop3_store_type = camel_type_register (CAMEL_STORE_TYPE,
112 sizeof (CamelPOP3Store),
113 sizeof (CamelPOP3StoreClass),
114 (CamelObjectClassInitFunc) camel_pop3_store_class_init,
116 (CamelObjectInitFunc) camel_pop3_store_init,
120 return camel_pop3_store_type;
124 finalize (CamelObject *object)
126 CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (object);
128 /* force disconnect so we dont have it run later, after we've cleaned up some stuff */
131 camel_service_disconnect((CamelService *)pop3_store, TRUE, NULL);
133 if (pop3_store->engine)
134 camel_object_unref((CamelObject *)pop3_store->engine);
135 if (pop3_store->cache)
136 camel_object_unref((CamelObject *)pop3_store->cache);
146 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
147 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
151 connect_to_server (CamelService *service, struct addrinfo *ai, int ssl_mode, CamelException *ex)
153 CamelPOP3Store *store = CAMEL_POP3_STORE (service);
154 CamelStream *tcp_stream;
155 CamelPOP3Command *pc;
157 int clean_quit = TRUE;
159 const gchar *delete_days;
161 if (ssl_mode != MODE_CLEAR) {
163 if (ssl_mode == MODE_TLS) {
164 tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
166 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
169 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
170 _("Could not connect to %s: %s"),
171 service->url->host, _("SSL unavailable"));
174 #endif /* HAVE_SSL */
176 tcp_stream = camel_tcp_stream_raw_new ();
179 if ((ret = camel_tcp_stream_connect ((CamelTcpStream *) tcp_stream, ai)) == -1) {
181 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
182 _("Connection canceled"));
184 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
185 _("Could not connect to %s: %s"),
189 camel_object_unref (tcp_stream);
194 /* parent class connect initialization */
195 if (CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex) == FALSE) {
196 camel_object_unref (tcp_stream);
200 if (camel_url_get_param (service->url, "disable_extensions"))
201 flags |= CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS;
203 if ((delete_days = (gchar *) camel_url_get_param(service->url,"delete_after")))
204 store->delete_after = atoi(delete_days);
206 if (!(store->engine = camel_pop3_engine_new (tcp_stream, flags))) {
207 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
208 _("Failed to read a valid greeting from POP server %s"),
210 camel_object_unref (tcp_stream);
214 if (ssl_mode != MODE_TLS) {
215 camel_object_unref (tcp_stream);
220 if (!(store->engine->capa & CAMEL_POP3_CAP_STLS)) {
221 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
222 _("Failed to connect to POP server %s in secure mode: %s"),
223 service->url->host, _("STLS not supported by server"));
227 /* as soon as we send a STLS command, all hope is lost of a clean QUIT if problems arise */
230 pc = camel_pop3_engine_command_new (store->engine, 0, NULL, NULL, "STLS\r\n");
231 while (camel_pop3_engine_iterate (store->engine, NULL) > 0)
234 ret = pc->state == CAMEL_POP3_COMMAND_OK;
235 camel_pop3_engine_command_free (store->engine, pc);
238 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
239 _("Failed to connect to POP server %s in secure mode: %s"),
240 service->url->host, store->engine->line);
244 /* Okay, now toggle SSL/TLS mode */
245 ret = camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream));
248 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
249 _("Failed to connect to POP server %s in secure mode: %s"),
250 service->url->host, _("TLS negotiations failed"));
254 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
255 _("Failed to connect to POP server %s in secure mode: %s"),
256 service->url->host, _("TLS is not available in this build"));
258 #endif /* HAVE_SSL */
260 camel_object_unref (tcp_stream);
262 /* rfc2595, section 4 states that after a successful STLS
263 command, the client MUST discard prior CAPA responses */
264 camel_pop3_engine_reget_capabilities (store->engine);
270 /* try to disconnect cleanly */
271 pc = camel_pop3_engine_command_new (store->engine, 0, NULL, NULL, "QUIT\r\n");
272 while (camel_pop3_engine_iterate (store->engine, NULL) > 0)
274 camel_pop3_engine_command_free (store->engine, pc);
277 camel_object_unref (CAMEL_OBJECT (store->engine));
278 camel_object_unref (CAMEL_OBJECT (tcp_stream));
279 store->engine = NULL;
290 { "", "pop3s", POP3S_PORT, MODE_SSL }, /* really old (1.x) */
291 { "always", "pop3s", POP3S_PORT, MODE_SSL },
292 { "when-possible", "pop3", POP3_PORT, MODE_TLS },
293 { "never", "pop3", POP3_PORT, MODE_CLEAR },
294 { NULL, "pop3", POP3_PORT, MODE_CLEAR },
298 connect_to_server_wrapper (CamelService *service, CamelException *ex)
300 struct addrinfo hints, *ai;
301 const char *ssl_mode;
306 if ((ssl_mode = camel_url_get_param (service->url, "use_ssl"))) {
307 for (i = 0; ssl_options[i].value; i++)
308 if (!strcmp (ssl_options[i].value, ssl_mode))
310 mode = ssl_options[i].mode;
311 serv = ssl_options[i].serv;
312 port = ssl_options[i].port;
319 if (service->url->port) {
320 serv = g_alloca (16);
321 sprintf (serv, "%d", service->url->port);
325 memset (&hints, 0, sizeof (hints));
326 hints.ai_socktype = SOCK_STREAM;
327 hints.ai_family = PF_UNSPEC;
328 ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
329 if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
330 camel_exception_clear (ex);
331 ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
337 ret = connect_to_server (service, ai, mode, ex);
339 camel_freeaddrinfo (ai);
344 extern CamelServiceAuthType camel_pop3_password_authtype;
345 extern CamelServiceAuthType camel_pop3_apop_authtype;
348 query_auth_types (CamelService *service, CamelException *ex)
350 CamelPOP3Store *store = CAMEL_POP3_STORE (service);
353 types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex);
354 if (camel_exception_is_set (ex))
357 if (connect_to_server_wrapper (service, NULL)) {
358 types = g_list_concat(types, g_list_copy(store->engine->auth));
359 pop3_disconnect (service, TRUE, NULL);
361 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
362 _("Could not connect to POP server %s"),
370 * camel_pop3_store_expunge:
372 * @ex: a CamelException
374 * Expunge messages from the store. This will result in the connection
375 * being closed, which may cause later commands to fail if they can't
379 camel_pop3_store_expunge (CamelPOP3Store *store, CamelException *ex)
381 CamelPOP3Command *pc;
383 pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n");
384 while (camel_pop3_engine_iterate(store->engine, NULL) > 0)
386 camel_pop3_engine_command_free(store->engine, pc);
388 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, ex);
392 try_sasl(CamelPOP3Store *store, const char *mech, CamelException *ex)
394 CamelPOP3Stream *stream = store->engine->stream;
395 unsigned char *line, *resp;
400 sasl = camel_sasl_new("pop3", mech, (CamelService *)store);
402 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
403 _("Unable to connect to POP server %s: "
404 "No support for requested authentication mechanism."),
405 CAMEL_SERVICE (store)->url->host);
409 if (camel_stream_printf((CamelStream *)stream, "AUTH %s\r\n", mech) == -1)
413 if (camel_pop3_stream_line(stream, &line, &len) == -1)
415 if (strncmp((char *) line, (char *) "+OK", 3) == 0)
417 if (strncmp((char *) line, (char *) "-ERR", 4) == 0) {
418 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
419 _("SASL `%s' Login failed for POP server %s: %s"),
420 mech, CAMEL_SERVICE (store)->url->host, line);
423 /* If we dont get continuation, or the sasl object's run out of work, or we dont get a challenge,
424 its a protocol error, so fail, and try reset the server */
425 if (strncmp((char *) line, (char *) "+ ", 2) != 0
426 || camel_sasl_authenticated(sasl)
427 || (resp = (unsigned char *) camel_sasl_challenge_base64(sasl, (const char *) line+2, ex)) == NULL) {
428 camel_stream_printf((CamelStream *)stream, "*\r\n");
429 camel_pop3_stream_line(stream, &line, &len);
430 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
431 _("Cannot login to POP server %s: SASL Protocol error"),
432 CAMEL_SERVICE (store)->url->host);
436 ret = camel_stream_printf((CamelStream *)stream, "%s\r\n", resp);
442 camel_object_unref((CamelObject *)sasl);
446 if (errno == EINTR) {
447 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
449 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
450 _("Failed to authenticate on POP server %s: %s"),
451 CAMEL_SERVICE (store)->url->host, g_strerror (errno));
454 camel_object_unref((CamelObject *)sasl);
459 pop3_try_authenticate (CamelService *service, gboolean reprompt, const char *errmsg, CamelException *ex)
461 CamelPOP3Store *store = (CamelPOP3Store *)service;
462 CamelPOP3Command *pcu = NULL, *pcp = NULL;
465 /* override, testing only */
466 /*printf("Forcing authmech to 'login'\n");
467 service->url->authmech = g_strdup("LOGIN");*/
469 if (!service->url->passwd) {
471 guint32 flags = CAMEL_SESSION_PASSWORD_SECRET;
474 flags |= CAMEL_SESSION_PASSWORD_REPROMPT;
476 prompt = g_strdup_printf (_("%sPlease enter the POP password for %s on host %s"),
477 errmsg ? errmsg : "",
480 service->url->passwd = camel_session_get_password (camel_service_get_session (service), service, NULL,
481 prompt, "password", flags, ex);
483 if (!service->url->passwd)
487 if (!service->url->authmech) {
488 /* pop engine will take care of pipelining ability */
489 pcu = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "USER %s\r\n", service->url->user);
490 pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "PASS %s\r\n", service->url->passwd);
491 } else if (strcmp(service->url->authmech, "+APOP") == 0 && store->engine->apop) {
492 char *secret, md5asc[33], *d;
493 unsigned char md5sum[16], *s;
495 d = store->engine->apop;
498 if (!isascii((int)*d)) {
500 /* README for Translators: The string APOP should not be translated */
501 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
502 _("Unable to connect to POP server %s: Invalid APOP ID received. Impersonation attack suspected. Please contact your admin."),
503 CAMEL_SERVICE (store)->url->host);
510 secret = g_alloca(strlen(store->engine->apop)+strlen(service->url->passwd)+1);
511 sprintf(secret, "%s%s", store->engine->apop, service->url->passwd);
512 md5_get_digest(secret, strlen (secret), md5sum);
514 for (s = md5sum, d = md5asc; d < md5asc + 32; s++, d += 2)
515 sprintf (d, "%.2x", *s);
517 pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "APOP %s %s\r\n",
518 service->url->user, md5asc);
520 CamelServiceAuthType *auth;
523 l = store->engine->auth;
526 if (strcmp(auth->authproto, service->url->authmech) == 0)
527 return try_sasl(store, service->url->authmech, ex) == -1;
531 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
532 _("Unable to connect to POP server %s: "
533 "No support for requested authentication mechanism."),
534 CAMEL_SERVICE (store)->url->host);
538 while ((status = camel_pop3_engine_iterate(store->engine, pcp)) > 0)
542 if (errno == EINTR) {
543 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled"));
545 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
546 _("Unable to connect to POP server %s.\n"
547 "Error sending password: %s"),
548 CAMEL_SERVICE (store)->url->host,
549 errno ? g_strerror (errno) : _("Unknown error"));
551 } else if (pcu && pcu->state != CAMEL_POP3_COMMAND_OK) {
552 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
553 _("Unable to connect to POP server %s.\n"
554 "Error sending username: %s"),
555 CAMEL_SERVICE (store)->url->host,
556 store->engine->line ? (char *)store->engine->line : _("Unknown error"));
557 } else if (pcp->state != CAMEL_POP3_COMMAND_OK)
558 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
559 _("Unable to connect to POP server %s.\n"
560 "Error sending password: %s"),
561 CAMEL_SERVICE (store)->url->host,
562 store->engine->line ? (char *)store->engine->line : _("Unknown error"));
564 camel_pop3_engine_command_free(store->engine, pcp);
567 camel_pop3_engine_command_free(store->engine, pcu);
573 pop3_connect (CamelService *service, CamelException *ex)
575 CamelPOP3Store *store = (CamelPOP3Store *)service;
576 gboolean reprompt = FALSE;
577 CamelSession *session;
581 session = camel_service_get_session (service);
583 if (store->cache == NULL) {
586 root = camel_session_get_storage_path (session, service, ex);
588 store->cache = camel_data_cache_new(root, 0, ex);
591 /* Default cache expiry - 1 week or not visited in a day */
592 camel_data_cache_set_expire_age(store->cache, 60*60*24*7);
593 camel_data_cache_set_expire_access(store->cache, 60*60*24);
598 if (!connect_to_server_wrapper (service, ex))
602 status = pop3_try_authenticate (service, reprompt, errbuf, ex);
606 /* we only re-prompt if we failed to authenticate, any other error and we just abort */
607 if (status == 0 && camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE) {
608 errbuf = g_strdup_printf ("%s\n\n", camel_exception_get_description (ex));
609 g_free (service->url->passwd);
610 service->url->passwd = NULL;
612 camel_exception_clear (ex);
619 if (status == -1 || camel_exception_is_set(ex)) {
620 camel_service_disconnect(service, TRUE, ex);
624 /* Now that we are in the TRANSACTION state, try regetting the capabilities */
625 store->engine->state = CAMEL_POP3_ENGINE_TRANSACTION;
626 camel_pop3_engine_reget_capabilities (store->engine);
632 pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex)
634 CamelPOP3Store *store = CAMEL_POP3_STORE (service);
637 CamelPOP3Command *pc;
639 pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n");
640 while (camel_pop3_engine_iterate(store->engine, NULL) > 0)
642 camel_pop3_engine_command_free(store->engine, pc);
645 if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
648 camel_object_unref((CamelObject *)store->engine);
649 store->engine = NULL;
655 get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
657 if (g_ascii_strcasecmp (folder_name, "inbox") != 0) {
658 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
659 _("No such folder `%s'."), folder_name);
662 return camel_pop3_folder_new (store, ex);
666 get_trash (CamelStore *store, CamelException *ex)