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 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 General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <netinet/in.h>
40 #include "camel-operation.h"
42 #include "camel-pop3-store.h"
43 #include "camel-pop3-folder.h"
44 #include "camel-stream-buffer.h"
45 #include "camel-session.h"
46 #include "camel-exception.h"
47 #include "camel-url.h"
48 #include "e-util/md5-utils.h"
49 #include "camel-pop3-engine.h"
50 #include "camel-sasl.h"
51 #include "camel-data-cache.h"
52 #include "camel-tcp-stream.h"
53 #include "camel-tcp-stream-raw.h"
55 #include "camel-tcp-stream-ssl.h"
58 /* Specified in RFC 1939 */
61 static CamelStoreClass *parent_class = NULL;
63 static void finalize (CamelObject *object);
65 static gboolean pop3_connect (CamelService *service, CamelException *ex);
66 static gboolean pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex);
67 static GList *query_auth_types (CamelService *service, CamelException *ex);
69 static CamelFolder *get_folder (CamelStore *store, const char *folder_name,
70 guint32 flags, CamelException *ex);
72 static void init_trash (CamelStore *store);
73 static CamelFolder *get_trash (CamelStore *store, CamelException *ex);
76 camel_pop3_store_class_init (CamelPOP3StoreClass *camel_pop3_store_class)
78 CamelServiceClass *camel_service_class =
79 CAMEL_SERVICE_CLASS (camel_pop3_store_class);
80 CamelStoreClass *camel_store_class =
81 CAMEL_STORE_CLASS (camel_pop3_store_class);
83 parent_class = CAMEL_STORE_CLASS (camel_type_get_global_classfuncs (camel_store_get_type ()));
85 /* virtual method overload */
86 camel_service_class->query_auth_types = query_auth_types;
87 camel_service_class->connect = pop3_connect;
88 camel_service_class->disconnect = pop3_disconnect;
90 camel_store_class->get_folder = get_folder;
91 camel_store_class->init_trash = init_trash;
92 camel_store_class->get_trash = get_trash;
98 camel_pop3_store_init (gpointer object, gpointer klass)
104 camel_pop3_store_get_type (void)
106 static CamelType camel_pop3_store_type = CAMEL_INVALID_TYPE;
108 if (!camel_pop3_store_type) {
109 camel_pop3_store_type = camel_type_register (CAMEL_STORE_TYPE,
111 sizeof (CamelPOP3Store),
112 sizeof (CamelPOP3StoreClass),
113 (CamelObjectClassInitFunc) camel_pop3_store_class_init,
115 (CamelObjectInitFunc) camel_pop3_store_init,
119 return camel_pop3_store_type;
123 finalize (CamelObject *object)
125 CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (object);
127 /* force disconnect so we dont have it run later, after we've cleaned up some stuff */
130 camel_service_disconnect((CamelService *)pop3_store, TRUE, NULL);
132 if (pop3_store->engine)
133 camel_object_unref((CamelObject *)pop3_store->engine);
134 if (pop3_store->cache)
135 camel_object_unref((CamelObject *)pop3_store->cache);
141 USE_SSL_WHEN_POSSIBLE
144 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
145 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
148 connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelException *ex)
150 CamelPOP3Store *store = CAMEL_POP3_STORE (service);
151 CamelStream *tcp_stream;
152 CamelPOP3Command *pc;
158 h = camel_service_gethost (service, ex);
162 port = service->url->port ? service->url->port : 110;
164 if (ssl_mode != USE_SSL_NEVER) {
167 tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
169 port = service->url->port ? service->url->port : 995;
170 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
174 port = service->url->port ? service->url->port : 995;
176 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
177 _("Could not connect to %s (port %d): %s"),
178 service->url->host, port,
179 _("SSL unavailable"));
184 #endif /* HAVE_SSL */
186 tcp_stream = camel_tcp_stream_raw_new ();
189 ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
193 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
194 _("Connection cancelled"));
196 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
197 _("Could not connect to POP server %s (port %d): %s"),
198 service->url->host, port, g_strerror (errno));
200 camel_object_unref (CAMEL_OBJECT (tcp_stream));
205 /* parent class connect initialization */
206 if (CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex) == FALSE) {
207 camel_object_unref (CAMEL_OBJECT (tcp_stream));
211 if (camel_url_get_param (service->url, "disable_extensions"))
212 flags |= CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS;
214 store->engine = camel_pop3_engine_new (tcp_stream, flags);
218 if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
219 if (store->engine->capa & CAMEL_POP3_CAP_STLS)
221 } else if (ssl_mode == USE_SSL_ALWAYS) {
223 if (store->engine->capa & CAMEL_POP3_CAP_STLS) {
224 /* attempt to toggle STARTTLS mode */
227 /* server doesn't support STARTTLS, abort */
228 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
229 _("Failed to connect to POP server %s in secure mode: %s"),
230 service->url->host, _("SSL/TLS extension not supported."));
231 /* we have the possibility of quitting cleanly here */
238 #endif /* HAVE_SSL */
240 camel_object_unref (CAMEL_OBJECT (tcp_stream));
242 return store->engine != NULL;
246 /* as soon as we send a STLS command, all hope is lost of a clean QUIT if problems arise */
249 pc = camel_pop3_engine_command_new (store->engine, 0, NULL, NULL, "STLS\r\n");
250 while (camel_pop3_engine_iterate (store->engine, NULL) > 0)
253 ret = pc->state == CAMEL_POP3_COMMAND_OK;
254 camel_pop3_engine_command_free (store->engine, pc);
257 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
258 _("Failed to connect to POP server %s in secure mode: %s"),
259 service->url->host, store->engine->line);
263 /* Okay, now toggle SSL/TLS mode */
264 ret = camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream));
266 camel_object_unref (CAMEL_OBJECT (tcp_stream));
269 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
270 _("Failed to connect to POP server %s in secure mode: %s"),
271 service->url->host, _("SSL negotiations failed"));
275 /* rfc2595, section 4 states that after a successful STLS
276 command, the client MUST discard prior CAPA responses */
277 camel_pop3_engine_reget_capabilities (store->engine);
283 /* try to disconnect cleanly */
284 pc = camel_pop3_engine_command_new (store->engine, 0, NULL, NULL, "QUIT\r\n");
285 while (camel_pop3_engine_iterate (store->engine, NULL) > 0)
287 camel_pop3_engine_command_free (store->engine, pc);
290 camel_object_unref (CAMEL_OBJECT (store->engine));
291 camel_object_unref (CAMEL_OBJECT (tcp_stream));
292 store->engine = NULL;
295 #endif /* HAVE_SSL */
302 { "", USE_SSL_ALWAYS },
303 { "always", USE_SSL_ALWAYS },
304 { "when-possible", USE_SSL_WHEN_POSSIBLE },
305 { "never", USE_SSL_NEVER },
306 { NULL, USE_SSL_NEVER },
310 connect_to_server_wrapper (CamelService *service, CamelException *ex)
316 use_ssl = camel_url_get_param (service->url, "use_ssl");
318 for (i = 0; ssl_options[i].value; i++)
319 if (!strcmp (ssl_options[i].value, use_ssl))
321 ssl_mode = ssl_options[i].mode;
323 ssl_mode = USE_SSL_NEVER;
325 if (ssl_mode == USE_SSL_ALWAYS) {
326 /* First try the ssl port */
327 if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
328 if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
329 /* The ssl port seems to be unavailable, lets try STARTTLS */
330 camel_exception_clear (ex);
331 return connect_to_server (service, ssl_mode, TRUE, ex);
338 } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
339 /* If the server supports STARTTLS, use it */
340 return connect_to_server (service, ssl_mode, TRUE, ex);
342 /* User doesn't care about SSL */
343 return connect_to_server (service, ssl_mode, FALSE, ex);
346 return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
350 extern CamelServiceAuthType camel_pop3_password_authtype;
351 extern CamelServiceAuthType camel_pop3_apop_authtype;
354 query_auth_types (CamelService *service, CamelException *ex)
356 CamelPOP3Store *store = CAMEL_POP3_STORE (service);
359 types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex);
360 if (camel_exception_is_set (ex))
363 if (connect_to_server_wrapper (service, NULL)) {
364 types = g_list_concat(types, g_list_copy(store->engine->auth));
365 pop3_disconnect (service, TRUE, NULL);
367 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
368 _("Could not connect to POP server %s"),
376 * camel_pop3_store_expunge:
378 * @ex: a CamelException
380 * Expunge messages from the store. This will result in the connection
381 * being closed, which may cause later commands to fail if they can't
385 camel_pop3_store_expunge (CamelPOP3Store *store, CamelException *ex)
387 CamelPOP3Command *pc;
389 pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n");
390 while (camel_pop3_engine_iterate(store->engine, NULL) > 0)
392 camel_pop3_engine_command_free(store->engine, pc);
394 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, ex);
398 try_sasl(CamelPOP3Store *store, const char *mech, CamelException *ex)
400 CamelPOP3Stream *stream = store->engine->stream;
401 unsigned char *line, *resp;
406 sasl = camel_sasl_new("pop3", mech, (CamelService *)store);
408 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
409 _("Unable to connect to POP server %s: "
410 "No support for requested authentication mechanism."),
411 CAMEL_SERVICE (store)->url->host);
415 if (camel_stream_printf((CamelStream *)stream, "AUTH %s\r\n", mech) == -1)
419 if (camel_pop3_stream_line(stream, &line, &len) == -1)
421 if (strncmp(line, "+OK", 3) == 0)
423 if (strncmp(line, "-ERR", 4) == 0) {
424 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
425 _("SASL `%s' Login failed for POP server %s: %s"),
426 mech, CAMEL_SERVICE (store)->url->host, line);
429 /* If we dont get continuation, or the sasl object's run out of work, or we dont get a challenge,
430 its a protocol error, so fail, and try reset the server */
431 if (strncmp(line, "+ ", 2) != 0
432 || camel_sasl_authenticated(sasl)
433 || (resp = camel_sasl_challenge_base64(sasl, line+2, ex)) == NULL) {
434 camel_stream_printf((CamelStream *)stream, "*\r\n");
435 camel_pop3_stream_line(stream, &line, &len);
436 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
437 _("Cannot login to POP server %s: SASL Protocol error"),
438 CAMEL_SERVICE (store)->url->host);
442 ret = camel_stream_printf((CamelStream *)stream, "%s\r\n", resp);
448 camel_object_unref((CamelObject *)sasl);
452 if (errno == EINTR) {
453 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
455 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
456 _("Failed to authenticate on POP server %s: %s"),
457 CAMEL_SERVICE (store)->url->host, g_strerror (errno));
460 camel_object_unref((CamelObject *)sasl);
465 pop3_try_authenticate (CamelService *service, gboolean reprompt, const char *errmsg, CamelException *ex)
467 CamelPOP3Store *store = (CamelPOP3Store *)service;
468 CamelPOP3Command *pcu = NULL, *pcp = NULL;
471 /* override, testing only */
472 /*printf("Forcing authmech to 'login'\n");
473 service->url->authmech = g_strdup("LOGIN");*/
475 if (!service->url->passwd) {
478 prompt = g_strdup_printf (_("%sPlease enter the POP password for %s@%s"),
479 errmsg ? errmsg : "",
482 service->url->passwd = camel_session_get_password (camel_service_get_session (service),
483 prompt, reprompt, TRUE, service, "password", ex);
485 if (!service->url->passwd)
489 if (!service->url->authmech) {
490 /* pop engine will take care of pipelining ability */
491 pcu = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "USER %s\r\n", service->url->user);
492 pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "PASS %s\r\n", service->url->passwd);
493 } else if (strcmp(service->url->authmech, "+APOP") == 0 && store->engine->apop) {
494 char *secret, md5asc[33], *d;
495 unsigned char md5sum[16], *s;
497 secret = g_alloca(strlen(store->engine->apop)+strlen(service->url->passwd)+1);
498 sprintf(secret, "%s%s", store->engine->apop, service->url->passwd);
499 md5_get_digest(secret, strlen (secret), md5sum);
501 for (s = md5sum, d = md5asc; d < md5asc + 32; s++, d += 2)
502 sprintf (d, "%.2x", *s);
504 pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "APOP %s %s\r\n",
505 service->url->user, md5asc);
507 CamelServiceAuthType *auth;
510 l = store->engine->auth;
513 if (strcmp(auth->authproto, service->url->authmech) == 0)
514 return try_sasl(store, service->url->authmech, ex) == -1;
518 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
519 _("Unable to connect to POP server %s: "
520 "No support for requested authentication mechanism."),
521 CAMEL_SERVICE (store)->url->host);
525 while ((status = camel_pop3_engine_iterate(store->engine, pcp)) > 0)
529 if (errno == EINTR) {
530 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
532 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
533 _("Unable to connect to POP server %s.\n"
534 "Error sending password: %s"),
535 CAMEL_SERVICE (store)->url->host,
536 errno ? g_strerror (errno) : _("Unknown error"));
538 } else if (pcp->state != CAMEL_POP3_COMMAND_OK)
539 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
540 _("Unable to connect to POP server %s.\n"
541 "Error sending password: %s"),
542 CAMEL_SERVICE (store)->url->host,
543 store->engine->line ? (char *)store->engine->line : _("Unknown error"));
545 camel_pop3_engine_command_free(store->engine, pcp);
548 camel_pop3_engine_command_free(store->engine, pcu);
554 pop3_connect (CamelService *service, CamelException *ex)
556 CamelPOP3Store *store = (CamelPOP3Store *)service;
557 gboolean reprompt = FALSE;
558 CamelSession *session;
562 session = camel_service_get_session (service);
564 if (store->cache == NULL) {
567 root = camel_session_get_storage_path (session, service, ex);
569 store->cache = camel_data_cache_new(root, 0, ex);
572 /* Default cache expiry - 1 week or not visited in a day */
573 camel_data_cache_set_expire_age(store->cache, 60*60*24*7);
574 camel_data_cache_set_expire_access(store->cache, 60*60*24);
579 if (!connect_to_server_wrapper (service, ex))
583 camel_exception_clear (ex);
584 status = pop3_try_authenticate (service, reprompt, errbuf, ex);
588 /* we only re-prompt if we failed to authenticate, any other error and we just abort */
589 if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE) {
590 errbuf = g_strdup_printf ("%s\n\n", camel_exception_get_description (ex));
591 g_free (service->url->passwd);
592 service->url->passwd = NULL;
595 } while (status != -1 && ex->id == CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE);
599 if (status == -1 || camel_exception_is_set(ex)) {
600 camel_service_disconnect(service, TRUE, ex);
604 /* Now that we are in the TRANSACTION state, try regetting the capabilities */
605 store->engine->state = CAMEL_POP3_ENGINE_TRANSACTION;
606 camel_pop3_engine_reget_capabilities (store->engine);
612 pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex)
614 CamelPOP3Store *store = CAMEL_POP3_STORE (service);
617 CamelPOP3Command *pc;
619 pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n");
620 while (camel_pop3_engine_iterate(store->engine, NULL) > 0)
622 camel_pop3_engine_command_free(store->engine, pc);
625 if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
628 camel_object_unref((CamelObject *)store->engine);
629 store->engine = NULL;
635 get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
637 if (strcasecmp (folder_name, "inbox") != 0) {
638 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
639 _("No such folder `%s'."), folder_name);
642 return camel_pop3_folder_new (store, ex);
646 init_trash (CamelStore *store)
653 get_trash (CamelStore *store, CamelException *ex)