diff -ruN dovecot-0.99.10.6/doc/auth.txt dovecot-0.99.10.6-crammd5/doc/auth.txt
--- dovecot-0.99.10.6/doc/auth.txt	Thu Jun 26 11:02:04 2003
+++ dovecot-0.99.10.6-crammd5/doc/auth.txt	Sun Jun 20 11:03:40 2004
@@ -8,6 +8,8 @@
  - DIGEST-MD5: Should be quite secure by itself. It also supports
    integrity protecting and crypting the rest of the communication, but
    we don't support those yet.
+ - CRAM-MD5: Protects the secret in transit from eavesdroppers.  Doesn't
+   provide any integrity guarantees.
  - ANONYMOUS: No authentication required. User will be logged in as the user
    specified by auth_anonymous_username setting (default "anonymous"). There's
    no special restrictions given for anonymous users so you have to make sure
@@ -46,6 +48,7 @@
 
  - PLAIN: Although not that good idea, it enables support for all current
    and future authentication mechanisms.
+ - HMAC-MD5: HMAC-MD5 context of password, for the CRAM-MD5 mechanism.
  - DIGEST-MD5: MD5 sum of "user:realm:password", as required by DIGEST-MD5
    mechanism.
 
diff -ruN dovecot-0.99.10.6/dovecot-example.conf dovecot-0.99.10.6-crammd5/dovecot-example.conf
--- dovecot-0.99.10.6/dovecot-example.conf	Sat Jun 19 05:25:31 2004
+++ dovecot-0.99.10.6-crammd5/dovecot-example.conf	Sun Jun 20 11:03:40 2004
@@ -373,7 +373,7 @@
 auth = default
 
 # Space separated list of wanted authentication mechanisms:
-#   plain digest-md5 anonymous
+#   plain cram-md5 digest-md5 anonymous
 auth_mechanisms = plain
 
 # Space separated list of realms for SASL authentication mechanisms that need
diff -ruN dovecot-0.99.10.6/src/auth/Makefile.am dovecot-0.99.10.6-crammd5/src/auth/Makefile.am
--- dovecot-0.99.10.6/src/auth/Makefile.am	Wed May 26 04:30:27 2004
+++ dovecot-0.99.10.6-crammd5/src/auth/Makefile.am	Sun Jun 20 11:03:40 2004
@@ -29,6 +29,7 @@
 	mech-anonymous.c \
 	mech-cyrus-sasl2.c \
 	mech-plain.c \
+	mech-cram-md5.c \
 	mech-digest-md5.c \
 	mycrypt.c \
 	passdb.c \
diff -ruN dovecot-0.99.10.6/src/auth/Makefile.in dovecot-0.99.10.6-crammd5/src/auth/Makefile.in
--- dovecot-0.99.10.6/src/auth/Makefile.in	Sun Jun 20 08:57:20 2004
+++ dovecot-0.99.10.6-crammd5/src/auth/Makefile.in	Sun Jun 20 11:03:40 2004
@@ -108,7 +108,7 @@
 dovecot_auth_LDADD =  	../lib-settings/libsettings.a 	../lib/liblib.a 	$(AUTH_LIBS) 	$(RAND_LIBS) 	$(MODULE_LIBS)
 
 
-dovecot_auth_SOURCES =  	auth-module.c 	db-ldap.c 	db-mysql.c 	db-pgsql.c 	db-passwd-file.c 	login-connection.c 	main.c 	master-connection.c 	md5crypt.c 	mech.c 	mech-anonymous.c 	mech-cyrus-sasl2.c 	mech-plain.c 	mech-digest-md5.c 	mycrypt.c 	passdb.c 	passdb-ldap.c 	passdb-passwd.c 	passdb-passwd-file.c 	passdb-pam.c 	passdb-shadow.c 	passdb-vpopmail.c 	passdb-mysql.c 	passdb-pgsql.c 	password-scheme.c 	userdb.c 	userdb-ldap.c 	userdb-passwd.c 	userdb-passwd-file.c 	userdb-static.c 	userdb-vpopmail.c 	userdb-mysql.c 	userdb-pgsql.c
+dovecot_auth_SOURCES =  	auth-module.c 	db-ldap.c 	db-mysql.c 	db-pgsql.c 	db-passwd-file.c 	login-connection.c 	main.c 	master-connection.c 	md5crypt.c 	mech.c 	mech-anonymous.c 	mech-cyrus-sasl2.c 	mech-plain.c 	mech-cram-md5.c 	mech-digest-md5.c 	mycrypt.c 	passdb.c 	passdb-ldap.c 	passdb-passwd.c 	passdb-passwd-file.c 	passdb-pam.c 	passdb-shadow.c 	passdb-vpopmail.c 	passdb-mysql.c 	passdb-pgsql.c 	password-scheme.c 	userdb.c 	userdb-ldap.c 	userdb-passwd.c 	userdb-passwd-file.c 	userdb-static.c 	userdb-vpopmail.c 	userdb-mysql.c 	userdb-pgsql.c
 
 
 noinst_HEADERS =  	auth-login-interface.h 	auth-master-interface.h 	auth-mech-desc.h 	auth-module.h 	db-ldap.h 	db-mysql.h 	db-pgsql.h 	db-passwd-file.h 	common.h 	login-connection.h 	master-connection.h 	md5crypt.h 	mech.h 	mycrypt.h 	passdb.h 	password-scheme.h 	userdb.h 	userdb-vpopmail.h
@@ -128,7 +128,7 @@
 db-mysql.$(OBJEXT) db-pgsql.$(OBJEXT) db-passwd-file.$(OBJEXT) \
 login-connection.$(OBJEXT) main.$(OBJEXT) master-connection.$(OBJEXT) \
 md5crypt.$(OBJEXT) mech.$(OBJEXT) mech-anonymous.$(OBJEXT) \
-mech-cyrus-sasl2.$(OBJEXT) mech-plain.$(OBJEXT) \
+mech-cyrus-sasl2.$(OBJEXT) mech-plain.$(OBJEXT) mech-cram-md5.$(OBJEXT) \
 mech-digest-md5.$(OBJEXT) mycrypt.$(OBJEXT) passdb.$(OBJEXT) \
 passdb-ldap.$(OBJEXT) passdb-passwd.$(OBJEXT) \
 passdb-passwd-file.$(OBJEXT) passdb-pam.$(OBJEXT) \
diff -ruN dovecot-0.99.10.6/src/auth/auth-login-interface.h dovecot-0.99.10.6-crammd5/src/auth/auth-login-interface.h
--- dovecot-0.99.10.6/src/auth/auth-login-interface.h	Sun May 18 22:26:28 2003
+++ dovecot-0.99.10.6-crammd5/src/auth/auth-login-interface.h	Sun Jun 20 11:03:41 2004
@@ -12,6 +12,7 @@
 	AUTH_MECH_PLAIN		= 0x01,
 	AUTH_MECH_DIGEST_MD5	= 0x02,
 	AUTH_MECH_ANONYMOUS	= 0x04,
+	AUTH_MECH_CRAM_MD5	= 0x08,
 
 	AUTH_MECH_COUNT
 };
diff -ruN dovecot-0.99.10.6/src/auth/auth-mech-desc.h dovecot-0.99.10.6-crammd5/src/auth/auth-mech-desc.h
--- dovecot-0.99.10.6/src/auth/auth-mech-desc.h	Sun May 18 22:26:28 2003
+++ dovecot-0.99.10.6-crammd5/src/auth/auth-mech-desc.h	Sun Jun 20 11:03:41 2004
@@ -10,6 +10,7 @@
 
 static struct auth_mech_desc auth_mech_desc[AUTH_MECH_COUNT] = {
 	{ AUTH_MECH_PLAIN,		"PLAIN",	TRUE, FALSE },
+	{ AUTH_MECH_CRAM_MD5,		"CRAM-MD5",	FALSE, TRUE },
 	{ AUTH_MECH_DIGEST_MD5,		"DIGEST-MD5",	FALSE, TRUE },
 	{ AUTH_MECH_ANONYMOUS,		"ANONYMOUS",	FALSE, TRUE }
 };
diff -ruN dovecot-0.99.10.6/src/auth/mech-cram-md5.c dovecot-0.99.10.6-crammd5/src/auth/mech-cram-md5.c
--- dovecot-0.99.10.6/src/auth/mech-cram-md5.c	Thu Jan  1 10:00:00 1970
+++ dovecot-0.99.10.6-crammd5/src/auth/mech-cram-md5.c	Sun Jun 20 11:03:41 2004
@@ -0,0 +1,228 @@
+/* Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall */
+
+/* CRAM-MD5 SASL authentication, see RFC-2195
+   Joshua Goodall <joshua@roughtrade.net> */
+
+#include "common.h"
+#include "ioloop.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "md5.h"
+#include "randgen.h"
+#include "mech.h"
+#include "passdb.h"
+#include "hostpid.h"
+
+#include <stdlib.h>
+#include <time.h>
+
+struct cram_auth_request {
+	struct auth_request auth_request;
+
+	pool_t pool;
+
+	/* requested: */
+	char *challenge;
+
+	/* received: */
+	char *username;
+	char *response;
+	unsigned long maxbuf;
+};
+
+static const char *get_cram_challenge(void)
+{
+	unsigned char buf[17];
+	size_t i;
+
+	hostpid_init();
+	random_fill(buf, sizeof(buf)-1);
+
+	for (i = 0; i < sizeof(buf)-1; i++)
+		buf[i] = (buf[i] % 10) + '0';
+	buf[sizeof(buf)-1] = '\0';
+
+	return t_strdup_printf("<%s.%s@%s>", buf, dec2str(ioloop_time),
+			       my_hostname);
+}
+
+static int verify_credentials(struct cram_auth_request *auth,
+			      const char *credentials)
+{
+	
+	unsigned char digest[16], context_digest[32], *cdp;
+	struct md5_context ctxo, ctxi;
+	buffer_t *context_digest_buf;
+	const char *response_hex;
+
+	if (credentials == NULL)
+		return FALSE;
+
+	context_digest_buf =
+		buffer_create_data(data_stack_pool,
+				   context_digest, sizeof(context_digest));
+
+	if (hex_to_binary(credentials, context_digest_buf) <= 0)
+		return FALSE;
+
+#define CDGET(p, c) STMT_START { \
+	(c)  = (*p++);           \
+	(c) += (*p++ << 8);      \
+	(c) += (*p++ << 16);     \
+	(c) += (*p++ << 24);     \
+} STMT_END
+
+	cdp = context_digest;
+	CDGET(cdp, ctxo.a);
+	CDGET(cdp, ctxo.b);
+	CDGET(cdp, ctxo.c);
+	CDGET(cdp, ctxo.d);
+	CDGET(cdp, ctxi.a);
+	CDGET(cdp, ctxi.b);
+	CDGET(cdp, ctxi.c);
+	CDGET(cdp, ctxi.d);
+
+	ctxo.lo = ctxi.lo = 64;
+	ctxo.hi = ctxi.hi = 0;
+
+	md5_update(&ctxi, auth->challenge, strlen(auth->challenge));
+	md5_final(&ctxi, digest);
+	md5_update(&ctxo, digest, 16);
+	md5_final(&ctxo, digest);
+	response_hex = binary_to_hex(digest, 16);
+
+	if (memcmp(response_hex, auth->response, 32) != 0) {
+		if (verbose) {
+			i_info("cram-md5(%s): password mismatch",
+			       auth->username);
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int parse_cram_response(struct cram_auth_request *auth,
+			       const unsigned char *data, size_t size,
+			       const char **error_r)
+{
+	size_t i;
+
+	*error_r = NULL;
+
+	for (i = 0; i < size; i++) {
+		if (data[i] == ' ')
+			break;
+	}
+
+	if (i == size) {
+		*error_r = "missing digest";
+		return FALSE;
+	}
+
+	auth->username = p_strndup(auth->pool, data, i);
+	i++;
+	auth->response = p_strndup(auth->pool, data + i, size - i);
+	return TRUE;
+}
+
+static void credentials_callback(const char *result,
+				 struct auth_request *request)
+{
+	struct cram_auth_request *auth =
+		(struct cram_auth_request *) request;
+
+	if (verify_credentials(auth, result)) {
+		if (verbose) {
+			i_info("cram-md5(%s): authenticated",
+			       auth->username == NULL ? "" : auth->username);
+		}
+		mech_auth_finish(request, NULL, 0, TRUE);
+	} else {
+		if (verbose) {
+			i_info("cram-md5(%s): authentication failed",
+			       auth->username == NULL ? "" : auth->username);
+		}
+		mech_auth_finish(request, NULL, 0, FALSE);
+	}
+}
+
+static int
+mech_cram_md5_auth_continue(struct auth_request *auth_request,
+	struct auth_login_request_continue *request __attr_unused__,
+	const unsigned char *data,
+	mech_callback_t *callback)
+{
+	struct cram_auth_request *auth =
+		(struct cram_auth_request *)auth_request;
+	const char *error;
+
+	if (parse_cram_response(auth, data, request->data_size, &error)) {
+		auth_request->callback = callback;
+
+		auth_request->user =
+			p_strdup(auth_request->pool, auth->username);
+
+		if (mech_is_valid_username(auth_request->user)) {
+			passdb->lookup_credentials(&auth->auth_request,
+						   PASSDB_CREDENTIALS_CRAM_MD5,
+						   credentials_callback);
+			return TRUE;
+		}
+
+		error = "invalid username";
+	}
+
+	if (error == NULL)
+		error = "authentication failed";
+
+	if (verbose) {
+		i_info("cram-md5(%s): %s",
+		       auth->username == NULL ? "" : auth->username, error);
+	}
+
+	/* failed */
+	mech_auth_finish(auth_request, NULL, 0, FALSE);
+	return FALSE;
+}
+
+static void mech_cram_md5_auth_free(struct auth_request *auth_request)
+{
+	pool_unref(auth_request->pool);
+}
+
+static struct auth_request *
+mech_cram_md5_auth_new(struct login_connection *conn,
+		       unsigned int id, mech_callback_t *callback)
+{
+	struct auth_login_reply reply;
+	struct cram_auth_request *auth;
+	pool_t pool;
+
+	pool = pool_alloconly_create("cram_md5_auth_request", 2048);
+	auth = p_new(pool, struct cram_auth_request, 1);
+	auth->pool = pool;
+
+	auth->auth_request.pool = pool;
+	auth->auth_request.auth_continue = mech_cram_md5_auth_continue;
+	auth->auth_request.auth_free = mech_cram_md5_auth_free;
+
+	auth->challenge = p_strdup(auth->pool, get_cram_challenge());
+
+	/* initialize reply */
+	mech_init_login_reply(&reply);
+	reply.id = id;
+	reply.result = AUTH_LOGIN_RESULT_CONTINUE;
+
+	/* send the initial challenge */
+	reply.reply_idx = 0;
+	reply.data_size = strlen(auth->challenge);
+	callback(&reply, auth->challenge, conn);
+
+	return &auth->auth_request;
+}
+
+struct mech_module mech_cram_md5 = {
+	AUTH_MECH_CRAM_MD5,
+	mech_cram_md5_auth_new
+};
diff -ruN dovecot-0.99.10.6/src/auth/mech.c dovecot-0.99.10.6-crammd5/src/auth/mech.c
--- dovecot-0.99.10.6/src/auth/mech.c	Sun May 18 22:26:28 2003
+++ dovecot-0.99.10.6-crammd5/src/auth/mech.c	Sun Jun 20 11:03:41 2004
@@ -201,6 +201,7 @@
 }
 
 extern struct mech_module mech_plain;
+extern struct mech_module mech_cram_md5;
 extern struct mech_module mech_digest_md5;
 extern struct mech_module mech_anonymous;
 
@@ -228,6 +229,8 @@
 	while (*mechanisms != NULL) {
 		if (strcasecmp(*mechanisms, "PLAIN") == 0)
 			mech_register_module(&mech_plain);
+		else if (strcasecmp(*mechanisms, "CRAM-MD5") == 0)
+			mech_register_module(&mech_cram_md5);
 		else if (strcasecmp(*mechanisms, "DIGEST-MD5") == 0)
 			mech_register_module(&mech_digest_md5);
 		else if (strcasecmp(*mechanisms, "ANONYMOUS") == 0) {
@@ -279,6 +282,7 @@
 void mech_deinit(void)
 {
 	mech_unregister_module(&mech_plain);
+	mech_unregister_module(&mech_cram_md5);
 	mech_unregister_module(&mech_digest_md5);
 	mech_unregister_module(&mech_anonymous);
 }
diff -ruN dovecot-0.99.10.6/src/auth/passdb.c dovecot-0.99.10.6-crammd5/src/auth/passdb.c
--- dovecot-0.99.10.6/src/auth/passdb.c	Wed May 26 04:20:21 2004
+++ dovecot-0.99.10.6-crammd5/src/auth/passdb.c	Sun Jun 20 11:03:41 2004
@@ -24,6 +24,8 @@
 		return "PLAIN";
 	case PASSDB_CREDENTIALS_CRYPT:
 		return "CRYPT";
+	case PASSDB_CREDENTIALS_CRAM_MD5:
+		return "HMAC-MD5";
 	case PASSDB_CREDENTIALS_DIGEST_MD5:
 		return "DIGEST-MD5";
 	}
@@ -132,6 +134,10 @@
 	if ((auth_mechanisms & AUTH_MECH_PLAIN) &&
 	    passdb->verify_plain == NULL)
 		i_fatal("Passdb %s doesn't support PLAIN method", name);
+
+	if ((auth_mechanisms & AUTH_MECH_CRAM_MD5) &&
+	    passdb->lookup_credentials == NULL)
+		i_fatal("Passdb %s doesn't support CRAM-MD5 method", name);
 
 	if ((auth_mechanisms & AUTH_MECH_DIGEST_MD5) &&
 	    passdb->lookup_credentials == NULL)
diff -ruN dovecot-0.99.10.6/src/auth/passdb.h dovecot-0.99.10.6-crammd5/src/auth/passdb.h
--- dovecot-0.99.10.6/src/auth/passdb.h	Wed May 26 04:20:21 2004
+++ dovecot-0.99.10.6-crammd5/src/auth/passdb.h	Sun Jun 20 11:03:41 2004
@@ -11,6 +11,7 @@
 
 	PASSDB_CREDENTIALS_PLAINTEXT,
 	PASSDB_CREDENTIALS_CRYPT,
+	PASSDB_CREDENTIALS_CRAM_MD5,
 	PASSDB_CREDENTIALS_DIGEST_MD5
 };
 
diff -ruN dovecot-0.99.10.6/src/auth/password-scheme.c dovecot-0.99.10.6-crammd5/src/auth/password-scheme.c
--- dovecot-0.99.10.6/src/auth/password-scheme.c	Sat Jun 19 05:17:11 2004
+++ dovecot-0.99.10.6-crammd5/src/auth/password-scheme.c	Sun Jun 20 11:07:32 2004
@@ -1,4 +1,4 @@
-/* Copyright (C) 2003 Timo Sirainen */
+/* Copyright (C) 2003 Timo Sirainen / Joshua Goodall */
 
 #include "lib.h"
 #include "base64.h"
@@ -9,6 +9,7 @@
 #include "randgen.h"
 #include "str.h"
 #include "password-scheme.h"
+#include "safe-memset.h"
 
 #ifdef HAVE_OPENSSL_SHA1
 #  include <openssl/sha.h>
@@ -17,6 +18,57 @@
 static const char *salt_chars =
 	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
+static const char *password_generate_cram_md5(const char *plaintext)
+{
+	unsigned char digest[16], ipad[64], opad[64], context_digest[32], *cdp;
+	struct md5_context ctxo, ctxi;
+	size_t len;
+	int i;
+
+	memset(ipad, 0, sizeof(ipad));
+	memset(opad, 0, sizeof(opad));
+
+	/* Hash excessively long passwords */
+	len = strlen(plaintext);
+	if (len > 64) {
+		md5_get_digest(plaintext, len, digest);
+		memcpy(ipad, digest, 16);
+		memcpy(opad, digest, 16);
+	} else {
+		memcpy(ipad, plaintext, len);
+		memcpy(opad, plaintext, len);
+	}
+
+	/* ipad/opad operation */
+	for (i = 0; i < 64; i++) {
+		ipad[i] ^= 0x36;
+		opad[i] ^= 0x5c;
+	}
+	md5_init(&ctxi);
+	md5_init(&ctxo);
+	md5_update(&ctxi, ipad, 64);
+	md5_update(&ctxo, opad, 64);
+
+	/* Make HMAC-MD5 hex digest */
+#define CDPUT(p,c) STMT_START {	\
+	*p++ = (c) & 0xff;	\
+	*p++ = (c)>>8 & 0xff;	\
+	*p++ = (c)>>16 & 0xff;	\
+	*p++ = (c)>>24 & 0xff;	\
+} STMT_END
+	cdp = context_digest;
+	CDPUT(cdp, ctxo.a);
+	CDPUT(cdp, ctxo.b);
+	CDPUT(cdp, ctxo.c);
+	CDPUT(cdp, ctxo.d);
+	CDPUT(cdp, ctxi.a);
+	CDPUT(cdp, ctxi.b);
+	CDPUT(cdp, ctxi.c);
+	CDPUT(cdp, ctxi.d);
+
+	return binary_to_hex(context_digest, sizeof(context_digest));
+}
+
 int password_verify(const char *plaintext, const char *password,
 		    const char *scheme, const char *user)
 {
@@ -35,6 +87,11 @@
 	if (strcasecmp(scheme, "PLAIN") == 0)
 		return strcmp(password, plaintext) == 0;
 
+	if (strcasecmp(scheme, "HMAC-MD5") == 0) {
+		str = password_generate_cram_md5(plaintext);
+		return strcmp(str, password) == 0;
+	}
+
 	if (strcasecmp(scheme, "DIGEST-MD5") == 0) {
 		/* user:realm:passwd */
 		realm = strchr(user, '@');
@@ -126,6 +183,9 @@
 
 	if (strcasecmp(scheme, "PLAIN") == 0)
 		return plaintext;
+
+	if (strcasecmp(scheme, "HMAC-MD5") == 0)
+		return password_generate_cram_md5(plaintext);
 
 	if (strcasecmp(scheme, "DIGEST-MD5") == 0) {
 		/* user:realm:passwd */
