diff options
-rw-r--r-- | camel/ChangeLog | 27 | ||||
-rw-r--r-- | camel/providers/imap/Makefile.am | 5 | ||||
-rw-r--r-- | camel/providers/imap/camel-imap-auth.c | 195 | ||||
-rw-r--r-- | camel/providers/imap/camel-imap-store.c | 231 | ||||
-rw-r--r-- | camel/providers/imap/camel-imap-store.h | 10 |
5 files changed, 384 insertions, 84 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog index e6ab9c5f0e..f0b0752b9f 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -1,3 +1,30 @@ +2000-10-30 Dan Winship <danw@helixcode.com> + + * providers/imap/camel-imap-auth.c: New file with code for IMAP + authentication mechanisms. (Currently just krb4, and without + integrity/privacy protection). + + * providers/imap/Makefile.am: Add camel-imap-auth.[ch] and krb4 + CFLAGS/LDFLAGS + + * providers/imap/camel-imap-store.c (connect_to_server): Split out + from imap_connect. Just does the basic connect and CAPABILITY + check. Redo the CAPABILITY code more robustly. + (query_auth_types_connected): Do this right rather than punting to + query_auth_types_generic. Check for KERBEROS_V4 if compiled with + krb4 support. + (query_auth_types_generic): Mention KERBEROS_V4 if compiled with + krb4 support. + (imap_connect): Use connect_to_server(). + + * camel-mime-utils.c (base64_encode_step, base64_encode_close): + Take an additional argument, "break_lines", saying whether or not + to add '\n's to the output. + + * camel-multipart.c (set_boundary): + * camel-mime-filter-basic.c (filter, complete): Update for base64 + api change. + 2000-10-29 Dan Winship <danw@helixcode.com> Improved IMAP namespace handling: leave the namespace in the diff --git a/camel/providers/imap/Makefile.am b/camel/providers/imap/Makefile.am index a6f5cbf3b1..19646f1960 100644 --- a/camel/providers/imap/Makefile.am +++ b/camel/providers/imap/Makefile.am @@ -17,9 +17,11 @@ INCLUDES = -I.. \ -I$(top_srcdir) \ -I$(includedir) \ $(GTK_INCLUDEDIR) \ + $(KRB4_CFLAGS) \ -DG_LOG_DOMAIN=\"camel-imap-provider\" libcamelimap_la_SOURCES = \ + camel-imap-auth.c \ camel-imap-command.c \ camel-imap-folder.c \ camel-imap-provider.c \ @@ -28,6 +30,7 @@ libcamelimap_la_SOURCES = \ camel-imap-utils.c libcamelimapinclude_HEADERS = \ + camel-imap-auth.h \ camel-imap-command.h \ camel-imap-folder.h \ camel-imap-store.h \ @@ -35,7 +38,7 @@ libcamelimapinclude_HEADERS = \ camel-imap-summary.h \ camel-imap-utils.h -libcamelimap_la_LDFLAGS = -version-info 0:0:0 +libcamelimap_la_LDFLAGS = $(KRB4_LDFLAGS) -version-info 0:0:0 EXTRA_DIST = libcamelimap.urls diff --git a/camel/providers/imap/camel-imap-auth.c b/camel/providers/imap/camel-imap-auth.c new file mode 100644 index 0000000000..3bf1af6535 --- /dev/null +++ b/camel/providers/imap/camel-imap-auth.c @@ -0,0 +1,195 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-imap-auth.c: IMAP AUTHENTICATE implementations */ + +/* + * Authors: Dan Winship <danw@helixcode.com> + * + * Copyright 2000 Helix Code, Inc. (www.helixcode.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +#include <config.h> + +#include <string.h> + +#ifdef HAVE_KRB4 +#include <krb.h> +/* MIT krb4 des.h #defines _. Sigh. We don't need it. */ +#undef _ +#endif + +#include "camel-exception.h" +#include "camel-mime-utils.h" + +#include "camel-imap-auth.h" +#include "camel-imap-command.h" +#include "camel-imap-utils.h" + +static char * +base64_encode_simple (const char *data, int len) +{ + unsigned char *out; + int state = 0, outlen; + unsigned int save = 0; + + out = g_malloc (len * 4 / 3 + 5); + outlen = base64_encode_close ((unsigned char *)data, len, FALSE, + out, &state, &save); + out[outlen] = '\0'; + return (char *)out; +} + +static int +base64_decode_simple (char *data, int len) +{ + int state = 0; + unsigned int save = 0; + + return base64_decode_step ((unsigned char *)data, len, + (unsigned char *)data, &state, &save); +} + +#ifdef HAVE_KRB4 +#define IMAP_KERBEROS_V4_PROTECTION_NONE 1 +#define IMAP_KERBEROS_V4_PROTECTION_INTEGRITY 2 +#define IMAP_KERBEROS_V4_PROTECTION_PRIVACY 4 + +gboolean +imap_try_kerberos_v4_auth (CamelImapStore *store, CamelException *ex) +{ + CamelImapResponse *response; + char *resp, *data; + int status, len; + char *inst, *realm, *buf, *username; + guint32 nonce_n, nonce_h, plus1; + struct hostent *h; + KTEXT_ST authenticator; + CREDENTIALS credentials; + des_cblock session; + des_key_schedule schedule; + + /* The kickoff. */ + response = camel_imap_command (store, NULL, ex, + "AUTHENTICATE KERBEROS_V4"); + if (!response) + return FALSE; + resp = camel_imap_response_extract_continuation (response, ex); + if (!resp) + return FALSE; + data = imap_next_word (resp); + + /* First server response is a base64-encoded 32-bit random number + * ("nonce") in network byte order. + */ + if (strlen (data) != 8 || base64_decode_simple (data, 8) != 4) { + g_free (resp); + goto lose; + } + memcpy (&nonce_n, data, 4); + g_free (resp); + nonce_h = ntohl (nonce_n); + + /* Our response is an authenticator including that number. */ + h = camel_service_gethost (CAMEL_SERVICE (store), ex); + if (!h) + goto lose; + inst = g_strndup (h->h_name, strcspn (h->h_name, ".")); + g_strdown (inst); + realm = g_strdup (krb_realmofhost (h->h_name)); + status = krb_mk_req (&authenticator, "imap", inst, realm, nonce_h); + if (status == KSUCCESS) { + status = krb_get_cred ("imap", inst, realm, &credentials); + memcpy (session, credentials.session, sizeof (session)); + memset (&credentials, 0, sizeof (credentials)); + } + g_free (inst); + g_free (realm); + + if (status != KSUCCESS) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Could not get Kerberos ticket:\n%s"), + krb_err_txt[status]); + goto lose; + } + des_key_sched (&session, schedule); + + buf = base64_encode_simple (authenticator.dat, authenticator.length); + response = camel_imap_command_continuation (store, ex, buf); + g_free (buf); + if (!response) + goto lose; + resp = camel_imap_response_extract_continuation (response, ex); + if (!resp) + goto lose; + data = imap_next_word (resp); + + len = strlen (data); + base64_decode_simple (data, strlen (data)); + + /* This one is encrypted. */ + des_ecb_encrypt ((des_cblock *)data, (des_cblock *)data, schedule, 0); + + /* Check that the returned value is the original nonce plus one. */ + memcpy (&plus1, data, 4); + if (ntohl (plus1) != nonce_h + 1) { + g_free (resp); + goto lose; + } + + /* "the fifth octet contain[s] a bit-mask specifying the + * protection mechanisms supported by the server" + */ + if (!(data[4] & IMAP_KERBEROS_V4_PROTECTION_NONE)) { + g_warning ("Server does not support `no protection' :-("); + g_free (resp); + goto lose; + } + g_free (resp); + + username = CAMEL_SERVICE (store)->url->user; + len = strlen (username) + 9; + len += 8 - len % 8; + data = g_malloc0 (len); + memcpy (data, &nonce_n, 4); + data[4] = IMAP_KERBEROS_V4_PROTECTION_NONE; + data[5] = data[6] = data[7] = 0; + strcpy (data + 8, username); + + des_pcbc_encrypt ((des_cblock *)data, (des_cblock *)data, len, + schedule, &session, 1); + memset (&session, 0, sizeof (session)); + buf = base64_encode_simple (data, len); + g_free (data); + + response = camel_imap_command_continuation (store, ex, buf); + if (!response) + goto lose; + camel_imap_response_free (response); + return TRUE; + + lose: + memset (&session, 0, sizeof (session)); + + /* Get the server out of "waiting for continuation data" mode. + */ + response = camel_imap_command_continuation (store, NULL, "*"); + if (response) + camel_imap_response_free (response); + + return FALSE; +} +#endif /* HAVE_KRB4 */ diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c index bea7a03b18..749785b724 100644 --- a/camel/providers/imap/camel-imap-store.c +++ b/camel/providers/imap/camel-imap-store.c @@ -34,6 +34,7 @@ #include <gal/util/e-util.h> #include "camel-imap-store.h" +#include "camel-imap-auth.h" #include "camel-imap-folder.h" #include "camel-imap-utils.h" #include "camel-imap-command.h" @@ -157,6 +158,76 @@ camel_imap_store_get_type (void) return camel_imap_store_type; } +static struct { + const char *name; + guint32 flag; +} capabilities[] = { + { "IMAP4", IMAP_CAPABILITY_IMAP4 }, + { "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 }, + { "STATUS", IMAP_CAPABILITY_STATUS }, + { "NAMESPACE", IMAP_CAPABILITY_NAMESPACE }, + { "AUTH=KERBEROS_V4", IMAP_CAPABILITY_AUTH_KERBEROS_V4 }, + { "AUTH=GSSAPI", IMAP_CAPABILITY_AUTH_GSSAPI }, + { "UIDPLUS", IMAP_CAPABILITY_UIDPLUS }, + { "LITERAL+", IMAP_CAPABILITY_LITERALPLUS }, + { NULL, 0 } +}; + +static gboolean +connect_to_server (CamelService *service, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (service); + CamelImapResponse *response; + char *result, *buf, *capa, *lasts; + int i; + + if (!CAMEL_SERVICE_CLASS (remote_store_class)->connect (service, ex)) + return FALSE; + + store->command = 0; + + /* Read the greeting, if any. FIXME: deal with PREAUTH */ + if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (service), + &buf, ex) < 0) { + return FALSE; + } + g_free (buf); + store->connected = TRUE; + + /* Find out the IMAP capabilities */ + store->capabilities = 0; + response = camel_imap_command (store, NULL, ex, "CAPABILITY"); + if (!response) + return FALSE; + result = camel_imap_response_extract (response, "CAPABILITY", ex); + if (!result) + return FALSE; + + /* Skip over "* CAPABILITY". */ + capa = imap_next_word (result + 2); + + for (capa = strtok_r (capa, " ", &lasts); capa; + capa = strtok_r (NULL, " ", &lasts)) { + for (i = 0; capabilities[i].name; i++) { + if (g_strcasecmp (capa, capabilities[i].name) == 0) { + store->capabilities |= capabilities[i].flag; + break; + } + } + } + g_free (result); + + if (store->capabilities & IMAP_CAPABILITY_IMAP4REV1) { + store->server_level = IMAP_LEVEL_IMAP4REV1; + store->capabilities |= IMAP_CAPABILITY_STATUS; + } else if (store->capabilities & IMAP_CAPABILITY_IMAP4) + store->server_level = IMAP_LEVEL_IMAP4; + else + store->server_level = IMAP_LEVEL_UNKNOWN; + + return TRUE; +} + static CamelServiceAuthType password_authtype = { N_("Password"), @@ -167,44 +238,45 @@ static CamelServiceAuthType password_authtype = { TRUE }; +#ifdef HAVE_KRB4 +static CamelServiceAuthType kerberos_v4_authtype = { + N_("Kerberos 4"), + + N_("This option will connect to the IMAP server using " + "Kerberos 4 authentication."), + + "KERBEROS_V4", + FALSE +}; +#endif + static GList * query_auth_types_connected (CamelService *service, CamelException *ex) { -#if 0 - GList *ret = NULL; - gboolean passwd = TRUE; - - if (service->url) { - passwd = try_connect (service, ex); - if (camel_exception_get_id (ex) != CAMEL_EXCEPTION_NONE) - return NULL; - } - - if (passwd) - ret = g_list_append (ret, &password_authtype); - - if (!ret) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not connect to IMAP server on %s."), - service->url->host ? service->url->host : - _("(unknown host)")); - } - - return ret; -#else - g_warning ("imap::query_auth_types_connected: not implemented. Defaulting."); - /* FIXME: use the classfunc instead of the local? */ - return query_auth_types_generic (service, ex); + GList *types; + + if (!connect_to_server (service, ex)) + return NULL; + + types = CAMEL_SERVICE_CLASS (remote_store_class)->query_auth_types_connected (service, ex); +#ifdef HAVE_KRB4 + if (CAMEL_IMAP_STORE (service)->capabilities & + IMAP_CAPABILITY_AUTH_KERBEROS_V4) + types = g_list_prepend (types, &kerberos_v4_authtype); #endif + return g_list_prepend (types, &password_authtype); } static GList * query_auth_types_generic (CamelService *service, CamelException *ex) { - GList *prev; + GList *types; - prev = CAMEL_SERVICE_CLASS (remote_store_class)->query_auth_types_generic (service, ex); - return g_list_prepend (prev, &password_authtype); + types = CAMEL_SERVICE_CLASS (remote_store_class)->query_auth_types_generic (service, ex); +#ifdef HAVE_KRB4 + types = g_list_prepend (types, &kerberos_v4_authtype); +#endif + return g_list_prepend (types, &password_authtype); } static gboolean @@ -212,65 +284,78 @@ imap_connect (CamelService *service, CamelException *ex) { CamelImapStore *store = CAMEL_IMAP_STORE (service); CamelSession *session = camel_service_get_session (CAMEL_SERVICE (store)); - gchar *result, *buf, *errbuf = NULL, *namespace; + gchar *result, *errbuf = NULL, *namespace; CamelImapResponse *response; gboolean authenticated = FALSE; int len; - if (CAMEL_SERVICE_CLASS (remote_store_class)->connect (service, ex) == FALSE) + if (connect_to_server (service, ex) == 0) return FALSE; - store->command = 0; - if (!store->storage_path) { - store->storage_path = - camel_session_get_storage_path (session, service, ex); - if (camel_exception_is_set (ex)) + /* authenticate the user */ +#ifdef HAVE_KRB4 + if (service->url->authmech && + !g_strcasecmp (service->url->authmech, "KERBEROS_V4")) { + if (!(store->capabilities & IMAP_CAPABILITY_AUTH_KERBEROS_V4)) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + "IMAP server %s does not " + "support requested " + "authentication type %s", + service->url->host, + service->url->authmech); + camel_service_disconnect (service, NULL); return FALSE; + } + + authenticated = imap_try_kerberos_v4_auth (store, ex); + if (camel_exception_is_set (ex)) { + camel_service_disconnect (service, NULL); + return FALSE; + } } - - /* Read the greeting, if any. */ - if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (service), &buf, ex) < 0) { - return FALSE; - } - g_free (buf); - - /* authenticate the user */ +#endif + while (!authenticated) { if (errbuf) { /* We need to un-cache the password before prompting again */ - camel_session_query_authenticator (session, - CAMEL_AUTHENTICATOR_TELL, NULL, - TRUE, service, "password", ex); + camel_session_query_authenticator ( + session, CAMEL_AUTHENTICATOR_TELL, NULL, + TRUE, service, "password", ex); g_free (service->url->passwd); service->url->passwd = NULL; } - + if (!service->url->authmech && !service->url->passwd) { - gchar *prompt; - - prompt = g_strdup_printf (_("%sPlease enter the IMAP password for %s@%s"), - errbuf ? errbuf : "", service->url->user, service->url->host); + char *prompt; + + prompt = g_strdup_printf (_("%sPlease enter the IMAP " + "password for %s@%s"), + errbuf ? errbuf : "", + service->url->user, + service->url->host); service->url->passwd = - camel_session_query_authenticator (session, - CAMEL_AUTHENTICATOR_ASK, prompt, - TRUE, service, "password", ex); + camel_session_query_authenticator ( + session, CAMEL_AUTHENTICATOR_ASK, + prompt, TRUE, service, "password", ex); g_free (prompt); g_free (errbuf); errbuf = NULL; - + if (!service->url->passwd) { camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, "You didn\'t enter a password."); + camel_service_disconnect (service, NULL); return FALSE; } } - + response = camel_imap_command (store, NULL, ex, "LOGIN \"%s\" \"%s\"", service->url->user, service->url->passwd); if (!response) { - errbuf = g_strdup_printf (_("Unable to authenticate to IMAP server.\n%s\n\n"), + errbuf = g_strdup_printf (_("Unable to authenticate " + "to IMAP server.\n%s\n\n"), camel_exception_get_description (ex)); camel_exception_clear (ex); } else { @@ -278,32 +363,14 @@ imap_connect (CamelService *service, CamelException *ex) camel_imap_response_free (response); } } - - /* At this point we know we're connected... */ - store->connected = TRUE; - - /* Now lets find out the IMAP capabilities */ - response = camel_imap_command (store, NULL, ex, "CAPABILITY"); - if (!response) - return FALSE; - result = camel_imap_response_extract (response, "CAPABILITY", ex); - if (!result) - return FALSE; - /* parse for capabilities here. */ - if (e_strstrcase (result, "IMAP4REV1")) - store->server_level = IMAP_LEVEL_IMAP4REV1; - else if (e_strstrcase (result, "IMAP4")) - store->server_level = IMAP_LEVEL_IMAP4; - else - store->server_level = IMAP_LEVEL_UNKNOWN; - - if ((store->server_level >= IMAP_LEVEL_IMAP4REV1) || - (e_strstrcase (result, "STATUS"))) - store->has_status_capability = TRUE; - else - store->has_status_capability = FALSE; - g_free (result); + /* Find our storage path. */ + if (!store->storage_path) { + store->storage_path = + camel_session_get_storage_path (session, service, ex); + if (camel_exception_is_set (ex)) + return FALSE; + } /* Find the hierarchy separator for our namespace. */ namespace = service->url->path; diff --git a/camel/providers/imap/camel-imap-store.h b/camel/providers/imap/camel-imap-store.h index b5038ea7de..8fd996c420 100644 --- a/camel/providers/imap/camel-imap-store.h +++ b/camel/providers/imap/camel-imap-store.h @@ -45,6 +45,14 @@ typedef enum { IMAP_LEVEL_IMAP4REV1 } CamelImapServerLevel; +#define IMAP_CAPABILITY_IMAP4 (1 << 0) +#define IMAP_CAPABILITY_IMAP4REV1 (1 << 1) +#define IMAP_CAPABILITY_STATUS (1 << 2) +#define IMAP_CAPABILITY_NAMESPACE (1 << 3) +#define IMAP_CAPABILITY_AUTH_KERBEROS_V4 (1 << 4) +#define IMAP_CAPABILITY_AUTH_GSSAPI (1 << 5) +#define IMAP_CAPABILITY_UIDPLUS (1 << 6) +#define IMAP_CAPABILITY_LITERALPLUS (1 << 7) typedef struct { CamelRemoteStore parent_object; @@ -54,7 +62,7 @@ typedef struct { guint32 command; CamelImapServerLevel server_level; - gboolean has_status_capability; + guint32 capabilities; gchar dir_sep, *storage_path, *base_url; |