diff options
Diffstat (limited to 'camel/providers/pop3/camel-pop3-store.c')
-rw-r--r-- | camel/providers/pop3/camel-pop3-store.c | 682 |
1 files changed, 682 insertions, 0 deletions
diff --git a/camel/providers/pop3/camel-pop3-store.c b/camel/providers/pop3/camel-pop3-store.c new file mode 100644 index 0000000000..96db912efc --- /dev/null +++ b/camel/providers/pop3/camel-pop3-store.c @@ -0,0 +1,682 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-pop3-store.c : class for a pop3 store */ + +/* + * Authors: + * Dan Winship <danw@ximian.com> + * Michael Zucchi <notzed@ximian.com> + * + * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "camel-operation.h" + +#include "camel-pop3-store.h" +#include "camel-pop3-folder.h" +#include "camel-stream-buffer.h" +#include "camel-session.h" +#include "camel-exception.h" +#include "camel-url.h" +#include "e-util/md5-utils.h" +#include "camel-pop3-engine.h" +#include "camel-sasl.h" +#include "camel-data-cache.h" +#include "camel-tcp-stream.h" +#include "camel-tcp-stream-raw.h" +#ifdef HAVE_SSL +#include "camel-tcp-stream-ssl.h" +#endif + +/* Specified in RFC 1939 */ +#define POP3_PORT 110 + +static CamelStoreClass *parent_class = NULL; + +static void finalize (CamelObject *object); + +static gboolean pop3_connect (CamelService *service, CamelException *ex); +static gboolean pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex); +static GList *query_auth_types (CamelService *service, CamelException *ex); + +static CamelFolder *get_folder (CamelStore *store, const char *folder_name, + guint32 flags, CamelException *ex); + +static CamelFolder *get_trash (CamelStore *store, CamelException *ex); + +static void +camel_pop3_store_class_init (CamelPOP3StoreClass *camel_pop3_store_class) +{ + CamelServiceClass *camel_service_class = + CAMEL_SERVICE_CLASS (camel_pop3_store_class); + CamelStoreClass *camel_store_class = + CAMEL_STORE_CLASS (camel_pop3_store_class); + + parent_class = CAMEL_STORE_CLASS (camel_type_get_global_classfuncs (camel_store_get_type ())); + + /* virtual method overload */ + camel_service_class->query_auth_types = query_auth_types; + camel_service_class->connect = pop3_connect; + camel_service_class->disconnect = pop3_disconnect; + + camel_store_class->get_folder = get_folder; + camel_store_class->get_trash = get_trash; +} + + + +static void +camel_pop3_store_init (gpointer object, gpointer klass) +{ + ; +} + +CamelType +camel_pop3_store_get_type (void) +{ + static CamelType camel_pop3_store_type = CAMEL_INVALID_TYPE; + + if (!camel_pop3_store_type) { + camel_pop3_store_type = camel_type_register (CAMEL_STORE_TYPE, + "CamelPOP3Store", + sizeof (CamelPOP3Store), + sizeof (CamelPOP3StoreClass), + (CamelObjectClassInitFunc) camel_pop3_store_class_init, + NULL, + (CamelObjectInitFunc) camel_pop3_store_init, + finalize); + } + + return camel_pop3_store_type; +} + +static void +finalize (CamelObject *object) +{ + CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (object); + + /* force disconnect so we dont have it run later, after we've cleaned up some stuff */ + /* SIGH */ + + camel_service_disconnect((CamelService *)pop3_store, TRUE, NULL); + + if (pop3_store->engine) + camel_object_unref((CamelObject *)pop3_store->engine); + if (pop3_store->cache) + camel_object_unref((CamelObject *)pop3_store->cache); +} + +enum { + USE_SSL_NEVER, + USE_SSL_ALWAYS, + USE_SSL_WHEN_POSSIBLE +}; + +#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3) +#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS) + +static gboolean +connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelException *ex) +{ + CamelPOP3Store *store = CAMEL_POP3_STORE (service); + CamelStream *tcp_stream; + CamelPOP3Command *pc; + guint32 flags = 0; + int clean_quit; + int ret; + struct addrinfo *ai, hints = { 0 }; + char *serv; + const char *port = NULL; + + if (service->url->port) { + serv = g_alloca(16); + sprintf(serv, "%d", service->url->port); + } else { + serv = "pop3"; + port = "110"; + } + + if (ssl_mode != USE_SSL_NEVER) { +#ifdef HAVE_SSL + if (try_starttls) { + tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS); + } else { + if (service->url->port == 0) { + serv = "pop3s"; + port = "995"; + } + tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS); + } +#else + if (!try_starttls && service->url->port == 0) { + serv = "pop3s"; + port = "995"; + } + + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Could not connect to %s (port %s): %s"), + service->url->host, serv, + _("SSL unavailable")); + + return FALSE; +#endif /* HAVE_SSL */ + } else { + tcp_stream = camel_tcp_stream_raw_new (); + } + + hints.ai_socktype = SOCK_STREAM; + ai = camel_getaddrinfo(service->url->host, serv, &hints, ex); + if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) { + camel_exception_clear(ex); + ai = camel_getaddrinfo(service->url->host, port, &hints, ex); + } + if (ai == NULL) { + camel_object_unref(tcp_stream); + return FALSE; + } + + ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai); + camel_freeaddrinfo(ai); + if (ret == -1) { + if (errno == EINTR) + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, + _("Connection cancelled")); + else + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Could not connect to POP server %s (port %s): %s"), + service->url->host, serv, g_strerror (errno)); + + camel_object_unref (tcp_stream); + + return FALSE; + } + + /* parent class connect initialization */ + if (CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex) == FALSE) { + camel_object_unref (tcp_stream); + return FALSE; + } + + if (camel_url_get_param (service->url, "disable_extensions")) + flags |= CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS; + + if (!(store->engine = camel_pop3_engine_new (tcp_stream, flags))) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to read a valid greeting from POP server %s (port %s)"), + service->url->host, serv); + return FALSE; + } + +#ifdef HAVE_SSL + if (store->engine) { + if (ssl_mode == USE_SSL_WHEN_POSSIBLE) { + if (store->engine->capa & CAMEL_POP3_CAP_STLS) + goto starttls; + } else if (ssl_mode == USE_SSL_ALWAYS) { + if (try_starttls) { + if (store->engine->capa & CAMEL_POP3_CAP_STLS) { + /* attempt to toggle STARTTLS mode */ + goto starttls; + } else { + /* server doesn't support STARTTLS, abort */ + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to connect to POP server %s in secure mode: %s"), + service->url->host, _("SSL/TLS extension not supported.")); + /* we have the possibility of quitting cleanly here */ + clean_quit = TRUE; + goto stls_exception; + } + } + } + } +#endif /* HAVE_SSL */ + + camel_object_unref (tcp_stream); + + return store->engine != NULL; + +#ifdef HAVE_SSL + starttls: + /* as soon as we send a STLS command, all hope is lost of a clean QUIT if problems arise */ + clean_quit = FALSE; + + pc = camel_pop3_engine_command_new (store->engine, 0, NULL, NULL, "STLS\r\n"); + while (camel_pop3_engine_iterate (store->engine, NULL) > 0) + ; + + ret = pc->state == CAMEL_POP3_COMMAND_OK; + camel_pop3_engine_command_free (store->engine, pc); + + if (ret == FALSE) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to connect to POP server %s in secure mode: %s"), + service->url->host, store->engine->line); + goto stls_exception; + } + + /* Okay, now toggle SSL/TLS mode */ + ret = camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)); + + camel_object_unref (CAMEL_OBJECT (tcp_stream)); + + if (ret == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to connect to POP server %s in secure mode: %s"), + service->url->host, _("SSL negotiations failed")); + goto stls_exception; + } + + /* rfc2595, section 4 states that after a successful STLS + command, the client MUST discard prior CAPA responses */ + camel_pop3_engine_reget_capabilities (store->engine); + + return TRUE; + + stls_exception: + if (clean_quit) { + /* try to disconnect cleanly */ + pc = camel_pop3_engine_command_new (store->engine, 0, NULL, NULL, "QUIT\r\n"); + while (camel_pop3_engine_iterate (store->engine, NULL) > 0) + ; + camel_pop3_engine_command_free (store->engine, pc); + } + + camel_object_unref (CAMEL_OBJECT (store->engine)); + camel_object_unref (CAMEL_OBJECT (tcp_stream)); + store->engine = NULL; + + return FALSE; +#endif /* HAVE_SSL */ +} + +static struct { + char *value; + int mode; +} ssl_options[] = { + { "", USE_SSL_ALWAYS }, + { "always", USE_SSL_ALWAYS }, + { "when-possible", USE_SSL_WHEN_POSSIBLE }, + { "never", USE_SSL_NEVER }, + { NULL, USE_SSL_NEVER }, +}; + +static gboolean +connect_to_server_wrapper (CamelService *service, CamelException *ex) +{ +#ifdef HAVE_SSL + const char *use_ssl; + int i, ssl_mode; + + use_ssl = camel_url_get_param (service->url, "use_ssl"); + if (use_ssl) { + for (i = 0; ssl_options[i].value; i++) + if (!strcmp (ssl_options[i].value, use_ssl)) + break; + ssl_mode = ssl_options[i].mode; + } else + ssl_mode = USE_SSL_NEVER; + + if (ssl_mode == USE_SSL_ALWAYS) { + /* First try the ssl port */ + if (!connect_to_server (service, ssl_mode, FALSE, ex)) { + if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) { + /* The ssl port seems to be unavailable, lets try STARTTLS */ + camel_exception_clear (ex); + return connect_to_server (service, ssl_mode, TRUE, ex); + } else { + return FALSE; + } + } + + return TRUE; + } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) { + /* If the server supports STARTTLS, use it */ + return connect_to_server (service, ssl_mode, TRUE, ex); + } else { + /* User doesn't care about SSL */ + return connect_to_server (service, ssl_mode, FALSE, ex); + } +#else + return connect_to_server (service, USE_SSL_NEVER, FALSE, ex); +#endif +} + +extern CamelServiceAuthType camel_pop3_password_authtype; +extern CamelServiceAuthType camel_pop3_apop_authtype; + +static GList * +query_auth_types (CamelService *service, CamelException *ex) +{ + CamelPOP3Store *store = CAMEL_POP3_STORE (service); + GList *types = NULL; + + types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex); + if (camel_exception_is_set (ex)) + return NULL; + + if (connect_to_server_wrapper (service, NULL)) { + types = g_list_concat(types, g_list_copy(store->engine->auth)); + pop3_disconnect (service, TRUE, NULL); + } else { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Could not connect to POP server %s"), + service->url->host); + } + + return types; +} + +/** + * camel_pop3_store_expunge: + * @store: the store + * @ex: a CamelException + * + * Expunge messages from the store. This will result in the connection + * being closed, which may cause later commands to fail if they can't + * reconnect. + **/ +void +camel_pop3_store_expunge (CamelPOP3Store *store, CamelException *ex) +{ + CamelPOP3Command *pc; + + pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n"); + while (camel_pop3_engine_iterate(store->engine, NULL) > 0) + ; + camel_pop3_engine_command_free(store->engine, pc); + + camel_service_disconnect (CAMEL_SERVICE (store), FALSE, ex); +} + +static int +try_sasl(CamelPOP3Store *store, const char *mech, CamelException *ex) +{ + CamelPOP3Stream *stream = store->engine->stream; + unsigned char *line, *resp; + CamelSasl *sasl; + unsigned int len; + int ret; + + sasl = camel_sasl_new("pop3", mech, (CamelService *)store); + if (sasl == NULL) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, + _("Unable to connect to POP server %s: " + "No support for requested authentication mechanism."), + CAMEL_SERVICE (store)->url->host); + return -1; + } + + if (camel_stream_printf((CamelStream *)stream, "AUTH %s\r\n", mech) == -1) + goto ioerror; + + while (1) { + if (camel_pop3_stream_line(stream, &line, &len) == -1) + goto ioerror; + if (strncmp(line, "+OK", 3) == 0) + break; + if (strncmp(line, "-ERR", 4) == 0) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("SASL `%s' Login failed for POP server %s: %s"), + mech, CAMEL_SERVICE (store)->url->host, line); + goto done; + } + /* If we dont get continuation, or the sasl object's run out of work, or we dont get a challenge, + its a protocol error, so fail, and try reset the server */ + if (strncmp(line, "+ ", 2) != 0 + || camel_sasl_authenticated(sasl) + || (resp = camel_sasl_challenge_base64(sasl, line+2, ex)) == NULL) { + camel_stream_printf((CamelStream *)stream, "*\r\n"); + camel_pop3_stream_line(stream, &line, &len); + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Cannot login to POP server %s: SASL Protocol error"), + CAMEL_SERVICE (store)->url->host); + goto done; + } + + ret = camel_stream_printf((CamelStream *)stream, "%s\r\n", resp); + g_free(resp); + if (ret == -1) + goto ioerror; + + } + camel_object_unref((CamelObject *)sasl); + return 0; + + ioerror: + if (errno == EINTR) { + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); + } else { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Failed to authenticate on POP server %s: %s"), + CAMEL_SERVICE (store)->url->host, g_strerror (errno)); + } + done: + camel_object_unref((CamelObject *)sasl); + return -1; +} + +static int +pop3_try_authenticate (CamelService *service, gboolean reprompt, const char *errmsg, CamelException *ex) +{ + CamelPOP3Store *store = (CamelPOP3Store *)service; + CamelPOP3Command *pcu = NULL, *pcp = NULL; + int status; + + /* override, testing only */ + /*printf("Forcing authmech to 'login'\n"); + service->url->authmech = g_strdup("LOGIN");*/ + + if (!service->url->passwd) { + char *prompt; + guint32 flags = CAMEL_SESSION_PASSWORD_SECRET; + + if (reprompt) + flags |= CAMEL_SESSION_PASSWORD_REPROMPT; + + prompt = g_strdup_printf (_("%sPlease enter the POP password for %s on host %s"), + errmsg ? errmsg : "", + service->url->user, + service->url->host); + service->url->passwd = camel_session_get_password (camel_service_get_session (service), service, NULL, + prompt, "password", flags, ex); + g_free (prompt); + if (!service->url->passwd) + return FALSE; + } + + if (!service->url->authmech) { + /* pop engine will take care of pipelining ability */ + pcu = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "USER %s\r\n", service->url->user); + pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "PASS %s\r\n", service->url->passwd); + } else if (strcmp(service->url->authmech, "+APOP") == 0 && store->engine->apop) { + char *secret, md5asc[33], *d; + unsigned char md5sum[16], *s; + + secret = g_alloca(strlen(store->engine->apop)+strlen(service->url->passwd)+1); + sprintf(secret, "%s%s", store->engine->apop, service->url->passwd); + md5_get_digest(secret, strlen (secret), md5sum); + + for (s = md5sum, d = md5asc; d < md5asc + 32; s++, d += 2) + sprintf (d, "%.2x", *s); + + pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "APOP %s %s\r\n", + service->url->user, md5asc); + } else { + CamelServiceAuthType *auth; + GList *l; + + l = store->engine->auth; + while (l) { + auth = l->data; + if (strcmp(auth->authproto, service->url->authmech) == 0) + return try_sasl(store, service->url->authmech, ex) == -1; + l = l->next; + } + + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, + _("Unable to connect to POP server %s: " + "No support for requested authentication mechanism."), + CAMEL_SERVICE (store)->url->host); + return FALSE; + } + + while ((status = camel_pop3_engine_iterate(store->engine, pcp)) > 0) + ; + + if (status == -1) { + if (errno == EINTR) { + camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); + } else { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Unable to connect to POP server %s.\n" + "Error sending password: %s"), + CAMEL_SERVICE (store)->url->host, + errno ? g_strerror (errno) : _("Unknown error")); + } + } else if (pcu && pcu->state != CAMEL_POP3_COMMAND_OK) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Unable to connect to POP server %s.\n" + "Error sending username: %s"), + CAMEL_SERVICE (store)->url->host, + store->engine->line ? (char *)store->engine->line : _("Unknown error")); + } else if (pcp->state != CAMEL_POP3_COMMAND_OK) + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Unable to connect to POP server %s.\n" + "Error sending password: %s"), + CAMEL_SERVICE (store)->url->host, + store->engine->line ? (char *)store->engine->line : _("Unknown error")); + + camel_pop3_engine_command_free(store->engine, pcp); + + if (pcu) + camel_pop3_engine_command_free(store->engine, pcu); + + return status; +} + +static gboolean +pop3_connect (CamelService *service, CamelException *ex) +{ + CamelPOP3Store *store = (CamelPOP3Store *)service; + gboolean reprompt = FALSE; + CamelSession *session; + char *errbuf = NULL; + int status; + + session = camel_service_get_session (service); + + if (store->cache == NULL) { + char *root; + + root = camel_session_get_storage_path (session, service, ex); + if (root) { + store->cache = camel_data_cache_new(root, 0, ex); + g_free(root); + if (store->cache) { + /* Default cache expiry - 1 week or not visited in a day */ + camel_data_cache_set_expire_age(store->cache, 60*60*24*7); + camel_data_cache_set_expire_access(store->cache, 60*60*24); + } + } + } + + if (!connect_to_server_wrapper (service, ex)) + return FALSE; + + while (1) { + status = pop3_try_authenticate (service, reprompt, errbuf, ex); + g_free (errbuf); + errbuf = NULL; + + /* we only re-prompt if we failed to authenticate, any other error and we just abort */ + if (status == 0 && camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE) { + errbuf = g_strdup_printf ("%s\n\n", camel_exception_get_description (ex)); + g_free (service->url->passwd); + service->url->passwd = NULL; + reprompt = TRUE; + camel_exception_clear (ex); + } else + break; + } + + g_free (errbuf); + + if (status == -1 || camel_exception_is_set(ex)) { + camel_service_disconnect(service, TRUE, ex); + return FALSE; + } + + /* Now that we are in the TRANSACTION state, try regetting the capabilities */ + store->engine->state = CAMEL_POP3_ENGINE_TRANSACTION; + camel_pop3_engine_reget_capabilities (store->engine); + + return TRUE; +} + +static gboolean +pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex) +{ + CamelPOP3Store *store = CAMEL_POP3_STORE (service); + + if (clean) { + CamelPOP3Command *pc; + + pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n"); + while (camel_pop3_engine_iterate(store->engine, NULL) > 0) + ; + camel_pop3_engine_command_free(store->engine, pc); + } + + if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex)) + return FALSE; + + camel_object_unref((CamelObject *)store->engine); + store->engine = NULL; + + return TRUE; +} + +static CamelFolder * +get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex) +{ + if (g_ascii_strcasecmp (folder_name, "inbox") != 0) { + camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID, + _("No such folder `%s'."), folder_name); + return NULL; + } + return camel_pop3_folder_new (store, ex); +} + +static CamelFolder * +get_trash (CamelStore *store, CamelException *ex) +{ + /* no-op */ + return NULL; +} |