aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--camel/ChangeLog27
-rw-r--r--camel/providers/imap/Makefile.am5
-rw-r--r--camel/providers/imap/camel-imap-auth.c195
-rw-r--r--camel/providers/imap/camel-imap-store.c231
-rw-r--r--camel/providers/imap/camel-imap-store.h10
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;