[Dovecot] PATCH: CRAM-MD5 for Dovecot

Joshua Goodall joshua at roughtrade.net
Mon Nov 10 17:29:16 EET 2003


Hi Timo,

Mozilla 1.5 doesn't understand DIGEST-MD5; it only speaks CRAM-MD5 or
PLAIN.  Maybe other clients have the same problem.  My users like
Mozilla, so I wrote new code for Dovecot to speak CRAM-MD5, using
your mech-digest-md5.c as a reference.

Attached diff in two formats
i) for current CVS
ii) against last release

both tested and working with Mozilla and KMail's CRAM-MD5.

I don't know if I've caught precisely the coding style you use for
Dovecot.   I hope so, but I'm unsure if I've used your string and
buffer libraries properly.  Please enlighten me if you have time.

Would you consider including this in the next release?

Regards,
Joshua

-- 
Joshua Goodall
joshua at roughtrade.net             "Your object hit ratio is weak, old man"
"If you cache me now, I will dump more core than you can possibly imagine"
-------------- next part --------------
diff -ruN dovecot/src/auth/Makefile.am dovecot-crammd5/src/auth/Makefile.am
--- dovecot/src/auth/Makefile.am	Thu Oct 30 01:10:20 2003
+++ dovecot-crammd5/src/auth/Makefile.am	Mon Nov 10 18:35:23 2003
@@ -28,6 +28,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/src/auth/auth-client-interface.h dovecot-crammd5/src/auth/auth-client-interface.h
--- dovecot/src/auth/auth-client-interface.h	Fri Aug 22 12:42:13 2003
+++ dovecot-crammd5/src/auth/auth-client-interface.h	Mon Nov 10 18:35:34 2003
@@ -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/src/auth/auth-mech-desc.h dovecot-crammd5/src/auth/auth-mech-desc.h
--- dovecot/src/auth/auth-mech-desc.h	Thu May  8 13:24:57 2003
+++ dovecot-crammd5/src/auth/auth-mech-desc.h	Mon Nov 10 18:35:34 2003
@@ -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/src/auth/mech-cram-md5.c dovecot-crammd5/src/auth/mech-cram-md5.c
--- dovecot/src/auth/mech-cram-md5.c	Thu Jan  1 10:00:00 1970
+++ dovecot-crammd5/src/auth/mech-cram-md5.c	Tue Nov 11 01:43:53 2003
@@ -0,0 +1,225 @@
+/* CRAM-MD5 SASL authentication, see RFC-2195
+   Joshua Goodall <joshua at roughtrade.net>
+
+   Derived from mech-digest-md5.c by Timo Sirainen.
+
+   Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall
+ */
+
+#include "common.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "md5.h"
+#include "randgen.h"
+#include "str.h"
+#include "mech.h"
+#include "passdb.h"
+#include "hostpid.h"
+#include "safe-memset.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 string_t *get_cram_challenge(struct cram_auth_request *auth)
+{
+	string_t *str;
+	struct {
+		uint64_t v1,v2;
+	} challenge;
+
+	random_fill(&challenge, sizeof(challenge));
+	hostpid_init();
+	str = t_str_new(256);
+	str_printfa(str, "<%llu%llu.%u@%s>", challenge.v1, challenge.v2, time(NULL), my_hostname);
+	auth->challenge = p_strdup(auth->pool, str_data(str));
+
+	return str;
+}
+
+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(pool_datastack_create(),
+		context_digest, sizeof(context_digest));
+
+	if (hex_to_binary(credentials, context_digest_buf) <= 0)
+		return FALSE;
+
+#define CDGET(p,c) do {		\
+	(c)  = (*p++);		\
+	(c) += (*p++ << 8);	\
+	(c) += (*p++ << 16);	\
+	(c) += (*p++ << 24);	\
+} while (0)
+
+	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 char *data, const char **error)
+{
+	char *digest;
+	int failed;
+
+	*error = NULL;
+	failed = FALSE;
+
+	digest = strchr(data, ' ');
+	if (digest != NULL) {
+		auth->username = p_strdup_until(auth->pool, data, digest);
+		digest++;
+		auth->response = p_strdup(auth->pool, digest);
+	} else {
+		*error = "missing digest";
+		failed = TRUE;
+	}
+
+	return !failed;
+}
+
+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_client_request_continue *request,
+				const unsigned char *data,
+				mech_callback_t *callback)
+{
+	struct cram_auth_request *auth =
+		(struct cram_auth_request *)auth_request;
+	const char *error;
+
+	/* unused */
+	(void)request;
+
+	if (parse_cram_response(auth, (const char *) data, &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 auth_client_connection *conn,
+			 unsigned int id, mech_callback_t *callback)
+{
+	struct auth_client_request_reply reply;
+	struct cram_auth_request *auth;
+	pool_t pool;
+	string_t *challenge;
+
+	pool = pool_alloconly_create("cram_md5_auth_request", 2048);
+	auth = p_new(pool, struct cram_auth_request, 1);
+	auth->pool = pool;
+
+	auth->auth_request.refcount = 1;
+	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;
+
+	/* initialize reply */
+	mech_init_auth_client_reply(&reply);
+	reply.id = id;
+	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
+
+	/* send the initial challenge */
+	reply.reply_idx = 0;
+	challenge = get_cram_challenge(auth);
+	reply.data_size = str_len(challenge);
+	callback(&reply, str_data(challenge), conn);
+
+	return &auth->auth_request;
+}
+
+struct mech_module mech_cram_md5 = {
+	AUTH_MECH_CRAM_MD5,
+	mech_cram_md5_auth_new
+};
diff -ruN dovecot/src/auth/mech.c dovecot-crammd5/src/auth/mech.c
--- dovecot/src/auth/mech.c	Mon Oct 20 14:15:16 2003
+++ dovecot-crammd5/src/auth/mech.c	Mon Nov 10 18:35:34 2003
@@ -215,6 +215,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;
 
@@ -242,6 +243,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) {
@@ -293,6 +296,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/src/auth/passdb.c dovecot-crammd5/src/auth/passdb.c
--- dovecot/src/auth/passdb.c	Thu Oct 30 01:10:20 2003
+++ dovecot-crammd5/src/auth/passdb.c	Mon Nov 10 18:35:34 2003
@@ -24,6 +24,8 @@
 		return "PLAIN";
 	case PASSDB_CREDENTIALS_CRYPT:
 		return "CRYPT";
+	case PASSDB_CREDENTIALS_CRAM_MD5:
+		return "CRAM-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/src/auth/passdb.h dovecot-crammd5/src/auth/passdb.h
--- dovecot/src/auth/passdb.h	Thu Oct 30 01:10:20 2003
+++ dovecot-crammd5/src/auth/passdb.h	Mon Nov 10 18:35:34 2003
@@ -11,6 +11,7 @@
 
 	PASSDB_CREDENTIALS_PLAINTEXT,
 	PASSDB_CREDENTIALS_CRYPT,
+	PASSDB_CREDENTIALS_CRAM_MD5,
 	PASSDB_CREDENTIALS_DIGEST_MD5
 };
 
diff -ruN dovecot/src/auth/password-scheme.c dovecot-crammd5/src/auth/password-scheme.c
--- dovecot/src/auth/password-scheme.c	Fri Apr  4 09:42:54 2003
+++ dovecot-crammd5/src/auth/password-scheme.c	Mon Nov 10 18:36:51 2003
@@ -7,6 +7,7 @@
 #include "mycrypt.h"
 #include "randgen.h"
 #include "password-scheme.h"
+#include "safe-memset.h"
 
 static const char *salt_chars =
 	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -86,7 +87,8 @@
 			      const char *scheme)
 {
 	const char *realm, *str;
-	unsigned char digest[16];
+	unsigned char digest[16], ipad[64], opad[64], context_digest[32], *cdp;
+	struct md5_context ctxo, ctxi;
 	char salt[9];
 	int i;
 
@@ -108,6 +110,51 @@
 
 	if (strcasecmp(scheme, "PLAIN") == 0)
 		return plaintext;
+
+	if (strcasecmp(scheme, "CRAM-MD5") == 0) {
+		safe_memset(ipad, '\0', sizeof(ipad));
+		safe_memset(opad, '\0', sizeof(opad));
+
+		/* Hash excessively long passwords */
+		i = strlen(plaintext);
+		if (i > 64) {
+			md5_get_digest(plaintext, i, digest);
+			memcpy(ipad, digest, 16);
+			memcpy(opad, digest, 16);
+		} else {
+			memcpy(ipad, plaintext, i);
+			memcpy(opad, plaintext, i);
+		}
+
+		/* 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) do {		\
+	*p++ = (c) & 0xff;	\
+	*p++ = (c)>>8 & 0xff;	\
+	*p++ = (c)>>16 & 0xff;	\
+	*p++ = (c)>>24 & 0xff;	\
+} while (0)
+		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));
+	}
 
 	if (strcasecmp(scheme, "DIGEST-MD5") == 0) {
 		/* user:realm:passwd */
-------------- next part --------------
diff -ruN dovecot-0.99.10/dovecot-example.conf dovecot-0.99.10-crammd5/dovecot-example.conf
--- dovecot-0.99.10/dovecot-example.conf	Tue Nov 11 02:08:28 2003
+++ dovecot-0.99.10-crammd5/dovecot-example.conf	Tue Nov 11 02:07:47 2003
@@ -357,7 +357,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/src/auth/Makefile.am dovecot-0.99.10-crammd5/src/auth/Makefile.am
--- dovecot-0.99.10/src/auth/Makefile.am	Sun May 18 22:26:28 2003
+++ dovecot-0.99.10-crammd5/src/auth/Makefile.am	Tue Nov 11 02:07:46 2003
@@ -28,6 +28,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/src/auth/auth-login-interface.h dovecot-0.99.10-crammd5/src/auth/auth-login-interface.h
--- dovecot-0.99.10/src/auth/auth-login-interface.h	Sun May 18 22:26:28 2003
+++ dovecot-0.99.10-crammd5/src/auth/auth-login-interface.h	Tue Nov 11 02:07:46 2003
@@ -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/src/auth/auth-mech-desc.h dovecot-0.99.10-crammd5/src/auth/auth-mech-desc.h
--- dovecot-0.99.10/src/auth/auth-mech-desc.h	Sun May 18 22:26:28 2003
+++ dovecot-0.99.10-crammd5/src/auth/auth-mech-desc.h	Tue Nov 11 02:07:46 2003
@@ -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/src/auth/mech-cram-md5.c dovecot-0.99.10-crammd5/src/auth/mech-cram-md5.c
--- dovecot-0.99.10/src/auth/mech-cram-md5.c	Thu Jan  1 10:00:00 1970
+++ dovecot-0.99.10-crammd5/src/auth/mech-cram-md5.c	Tue Nov 11 02:08:01 2003
@@ -0,0 +1,224 @@
+/* CRAM-MD5 SASL authentication, see RFC-2195
+   Joshua Goodall <joshua at roughtrade.net>
+
+   Derived from mech-digest-md5.c by Timo Sirainen.
+
+   Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall
+ */
+
+#include "common.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "md5.h"
+#include "randgen.h"
+#include "str.h"
+#include "mech.h"
+#include "passdb.h"
+#include "hostpid.h"
+#include "safe-memset.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 string_t *get_cram_challenge(struct cram_auth_request *auth)
+{
+	string_t *str;
+	struct {
+		uint64_t v1,v2;
+	} challenge;
+
+	random_fill(&challenge, sizeof(challenge));
+	hostpid_init();
+	str = t_str_new(256);
+	str_printfa(str, "<%llu%llu.%u@%s>", challenge.v1, challenge.v2, time(NULL), my_hostname);
+	auth->challenge = p_strdup(auth->pool, str_data(str));
+
+	return str;
+}
+
+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) do {		\
+	(c)  = (*p++);		\
+	(c) += (*p++ << 8);	\
+	(c) += (*p++ << 16);	\
+	(c) += (*p++ << 24);	\
+} while (0)
+
+	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 char *data, const char **error)
+{
+	char *digest;
+	int failed;
+
+	*error = NULL;
+	failed = FALSE;
+
+	digest = strchr(data, ' ');
+	if (digest != NULL) {
+		auth->username = p_strdup_until(auth->pool, data, digest);
+		digest++;
+		auth->response = p_strdup(auth->pool, digest);
+	} else {
+		*error = "missing digest";
+		failed = TRUE;
+	}
+
+	return !failed;
+}
+
+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,
+				const unsigned char *data,
+				mech_callback_t *callback)
+{
+	struct cram_auth_request *auth =
+		(struct cram_auth_request *)auth_request;
+	const char *error;
+
+	/* unused */
+	(void)request;
+
+	if (parse_cram_response(auth, (const char *) data, &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;
+	string_t *challenge;
+
+	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;
+
+	/* initialize reply */
+	mech_init_login_reply(&reply);
+	reply.id = id;
+	reply.result = AUTH_LOGIN_RESULT_CONTINUE;
+
+	/* send the initial challenge */
+	reply.reply_idx = 0;
+	challenge = get_cram_challenge(auth);
+	reply.data_size = str_len(challenge);
+	callback(&reply, str_data(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/src/auth/mech.c dovecot-0.99.10-crammd5/src/auth/mech.c
--- dovecot-0.99.10/src/auth/mech.c	Sun May 18 22:26:28 2003
+++ dovecot-0.99.10-crammd5/src/auth/mech.c	Tue Nov 11 02:07:47 2003
@@ -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/src/auth/passdb.c dovecot-0.99.10-crammd5/src/auth/passdb.c
--- dovecot-0.99.10/src/auth/passdb.c	Sun May 18 22:26:28 2003
+++ dovecot-0.99.10-crammd5/src/auth/passdb.c	Tue Nov 11 02:07:47 2003
@@ -24,6 +24,8 @@
 		return "PLAIN";
 	case PASSDB_CREDENTIALS_CRYPT:
 		return "CRYPT";
+	case PASSDB_CREDENTIALS_CRAM_MD5:
+		return "CRAM-MD5";
 	case PASSDB_CREDENTIALS_DIGEST_MD5:
 		return "DIGEST-MD5";
 	}
@@ -128,6 +130,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/src/auth/passdb.h dovecot-0.99.10-crammd5/src/auth/passdb.h
--- dovecot-0.99.10/src/auth/passdb.h	Fri Mar 21 02:46:33 2003
+++ dovecot-0.99.10-crammd5/src/auth/passdb.h	Tue Nov 11 02:07:47 2003
@@ -11,6 +11,7 @@
 
 	PASSDB_CREDENTIALS_PLAINTEXT,
 	PASSDB_CREDENTIALS_CRYPT,
+	PASSDB_CREDENTIALS_CRAM_MD5,
 	PASSDB_CREDENTIALS_DIGEST_MD5
 };
 
diff -ruN dovecot-0.99.10/src/auth/password-scheme.c dovecot-0.99.10-crammd5/src/auth/password-scheme.c
--- dovecot-0.99.10/src/auth/password-scheme.c	Sun May  4 04:32:59 2003
+++ dovecot-0.99.10-crammd5/src/auth/password-scheme.c	Tue Nov 11 02:07:47 2003
@@ -7,6 +7,7 @@
 #include "mycrypt.h"
 #include "randgen.h"
 #include "password-scheme.h"
+#include "safe-memset.h"
 
 static const char *salt_chars =
 	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -86,7 +87,8 @@
 			      const char *scheme)
 {
 	const char *realm, *str;
-	unsigned char digest[16];
+	unsigned char digest[16], ipad[64], opad[64], context_digest[32], *cdp;
+	struct md5_context ctxo, ctxi;
 	char salt[9];
 	int i;
 
@@ -108,6 +110,51 @@
 
 	if (strcasecmp(scheme, "PLAIN") == 0)
 		return plaintext;
+
+	if (strcasecmp(scheme, "CRAM-MD5") == 0) {
+		safe_memset(ipad, '\0', sizeof(ipad));
+		safe_memset(opad, '\0', sizeof(opad));
+
+		/* Hash excessively long passwords */
+		i = strlen(plaintext);
+		if (i > 64) {
+			md5_get_digest(plaintext, i, digest);
+			memcpy(ipad, digest, 16);
+			memcpy(opad, digest, 16);
+		} else {
+			memcpy(ipad, plaintext, i);
+			memcpy(opad, plaintext, i);
+		}
+
+		/* 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) do {		\
+	*p++ = (c) & 0xff;	\
+	*p++ = (c)>>8 & 0xff;	\
+	*p++ = (c)>>16 & 0xff;	\
+	*p++ = (c)>>24 & 0xff;	\
+} while (0)
+		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));
+	}
 
 	if (strcasecmp(scheme, "DIGEST-MD5") == 0) {
 		/* user:realm:passwd */
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 187 bytes
Desc: not available
URL: <http://dovecot.org/pipermail/dovecot/attachments/20031111/60b8f0b4/attachment-0001.bin>


More information about the dovecot mailing list