changeset 1873:ed5e808d934f HEAD

CRAM-MD5 mechanism by Joshua Goodall, plus some cleanups.
author Timo Sirainen <tss@iki.fi>
date Mon, 10 Nov 2003 22:36:02 +0200
parents f83bf8214723
children 4bff66d9b834
files src/auth/Makefile.am src/auth/auth-client-interface.h src/auth/auth-mech-desc.h src/auth/md5crypt.c src/auth/md5crypt.h src/auth/mech-cram-md5.c src/auth/mech-digest-md5.c src/auth/mech.c src/auth/passdb.c src/auth/passdb.h src/auth/password-scheme-cram-md5.c src/auth/password-scheme-md5crypt.c src/auth/password-scheme.c src/auth/password-scheme.h
diffstat 14 files changed, 465 insertions(+), 163 deletions(-) [+]
line wrap: on
line diff
--- a/src/auth/Makefile.am	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/Makefile.am	Mon Nov 10 22:36:02 2003 +0200
@@ -23,11 +23,11 @@
 	db-pgsql.c \
 	db-passwd-file.c \
 	main.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 \
@@ -40,6 +40,8 @@
 	passdb-vpopmail.c \
 	passdb-pgsql.c \
 	password-scheme.c \
+	password-scheme-md5crypt.c \
+	password-scheme-cram-md5.c \
 	userdb.c \
 	userdb-ldap.c \
 	userdb-passwd.c \
@@ -59,7 +61,6 @@
 	db-pgsql.h \
 	db-passwd-file.h \
 	common.h \
-	md5crypt.h \
 	mech.h \
 	mycrypt.h \
 	passdb.h \
--- a/src/auth/auth-client-interface.h	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/auth-client-interface.h	Mon Nov 10 22:36:02 2003 +0200
@@ -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
 };
--- a/src/auth/auth-mech-desc.h	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/auth-mech-desc.h	Mon Nov 10 22:36:02 2003 +0200
@@ -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 }
 };
--- a/src/auth/md5crypt.c	Mon Nov 10 20:53:40 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-/*
- * ----------------------------------------------------------------------------
- * "THE BEER-WARE LICENSE" (Revision 42):
- * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
- * can do whatever you want with this stuff. If we meet some day, and you think
- * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
- * ----------------------------------------------------------------------------
- */
-
-/*
- * Ported from FreeBSD to Linux, only minimal changes.  --marekm
- */
-
-/*
- * Adapted from shadow-19990607 by Tudor Bosman, tudorb@jm.nu
- */
-
-#include "lib.h"
-#include "safe-memset.h"
-#include "str.h"
-#include "md5.h"
-#include "md5crypt.h"
-
-static unsigned char itoa64[] =		/* 0 ... 63 => ascii - 64 */
-	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
-static char	*magic = "$1$";	/*
-				 * This string is magic for
-				 * this algorithm.  Having
-				 * it this way, we can get
-				 * get better later on
-				 */
-
-static void
-to64(string_t *str, unsigned long v, int n)
-{
-	while (--n >= 0) {
-		str_append_c(str, itoa64[v&0x3f]);
-		v >>= 6;
-	}
-}
-
-/*
- * UNIX password
- *
- * Use MD5 for what it is best at...
- */
-
-const char *
-md5_crypt(const char *pw, const char *salt)
-{
-	const char *sp,*ep;
-	unsigned char	final[16];
-	int sl,pl,i,j;
-	struct md5_context ctx,ctx1;
-	unsigned long l;
-	string_t *passwd;
-	size_t pw_len = strlen(pw);
-
-	/* Refine the Salt first */
-	sp = salt;
-
-	/* If it starts with the magic string, then skip that */
-	if (strncmp(sp, magic, sizeof(magic)-1) == 0)
-		sp += sizeof(magic)-1;
-
-	/* It stops at the first '$', max 8 chars */
-	for(ep=sp;*ep && *ep != '$' && ep < (sp+8);ep++)
-		continue;
-
-	/* get the length of the true salt */
-	sl = ep - sp;
-
-	md5_init(&ctx);
-
-	/* The password first, since that is what is most unknown */
-	md5_update(&ctx,pw,pw_len);
-
-	/* Then our magic string */
-	md5_update(&ctx,magic,sizeof(magic)-1);
-
-	/* Then the raw salt */
-	md5_update(&ctx,sp,sl);
-
-	/* Then just as many characters of the MD5(pw,salt,pw) */
-	md5_init(&ctx1);
-	md5_update(&ctx1,pw,pw_len);
-	md5_update(&ctx1,sp,sl);
-	md5_update(&ctx1,pw,pw_len);
-	md5_final(&ctx1,final);
-	for(pl = pw_len; pl > 0; pl -= 16)
-		md5_update(&ctx,final,pl>16 ? 16 : pl);
-
-	/* Don't leave anything around in vm they could use. */
-	safe_memset(final, 0, sizeof(final));
-
-	/* Then something really weird... */
-	for (j=0,i = pw_len; i ; i >>= 1)
-		if(i&1)
-		    md5_update(&ctx, final+j, 1);
-		else
-		    md5_update(&ctx, pw+j, 1);
-
-	/* Now make the output string */
-	passwd = t_str_new(sl + 64);
-	str_append(passwd, magic);
-	str_append_n(passwd, sp, sl);
-	str_append_c(passwd, '$');
-
-	md5_final(&ctx,final);
-
-	/*
-	 * and now, just to make sure things don't run too fast
-	 * On a 60 Mhz Pentium this takes 34 msec, so you would
-	 * need 30 seconds to build a 1000 entry dictionary...
-	 */
-	for(i=0;i<1000;i++) {
-		md5_init(&ctx1);
-		if(i & 1)
-			md5_update(&ctx1,pw,pw_len);
-		else
-			md5_update(&ctx1,final,16);
-
-		if(i % 3)
-			md5_update(&ctx1,sp,sl);
-
-		if(i % 7)
-			md5_update(&ctx1,pw,pw_len);
-
-		if(i & 1)
-			md5_update(&ctx1,final,16);
-		else
-			md5_update(&ctx1,pw,pw_len);
-		md5_final(&ctx1,final);
-	}
-
-	l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(passwd,l,4);
-	l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(passwd,l,4);
-	l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(passwd,l,4);
-	l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(passwd,l,4);
-	l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(passwd,l,4);
-	l =                    final[11]                ; to64(passwd,l,2);
-
-	/* Don't leave anything around in vm they could use. */
-	safe_memset(final, 0, sizeof(final));
-
-	return str_c(passwd);
-}
--- a/src/auth/md5crypt.h	Mon Nov 10 20:53:40 2003 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-#ifndef __MD5CRYPT_H
-#define __MD5CRYPT_H
-
-const char *md5_crypt(const char *pw, const char *salt);
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/mech-cram-md5.c	Mon Nov 10 22:36:02 2003 +0200
@@ -0,0 +1,230 @@
+/* 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)
+{
+	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(pool_datastack_create(),
+				   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 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;
+
+	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;
+
+	auth->challenge = p_strdup(auth->pool, get_cram_challenge());
+
+	/* 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;
+	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
+};
--- a/src/auth/mech-digest-md5.c	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/mech-digest-md5.c	Mon Nov 10 22:36:02 2003 +0200
@@ -464,7 +464,7 @@
 }
 
 static int parse_digest_response(struct digest_auth_request *auth,
-				 const char *data, size_t size,
+				 const unsigned char *data, size_t size,
 				 const char **error)
 {
 	char *copy, *key, *value;
@@ -569,8 +569,7 @@
 		return TRUE;
 	}
 
-	if (parse_digest_response(auth, (const char *) data,
-				  request->data_size, &error)) {
+	if (parse_digest_response(auth, data, request->data_size, &error)) {
 		auth_request->callback = callback;
 
 		realm = auth->realm != NULL ? auth->realm : default_realm;
--- a/src/auth/mech.c	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/mech.c	Mon Nov 10 22:36:02 2003 +0200
@@ -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);
 }
--- a/src/auth/passdb.c	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/passdb.c	Mon Nov 10 22:36:02 2003 +0200
@@ -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";
 	}
@@ -133,6 +135,10 @@
 	    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)
 		i_fatal("Passdb %s doesn't support DIGEST-MD5 method", name);
--- a/src/auth/passdb.h	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/passdb.h	Mon Nov 10 22:36:02 2003 +0200
@@ -11,6 +11,7 @@
 
 	PASSDB_CREDENTIALS_PLAINTEXT,
 	PASSDB_CREDENTIALS_CRYPT,
+	PASSDB_CREDENTIALS_CRAM_MD5,
 	PASSDB_CREDENTIALS_DIGEST_MD5
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/password-scheme-cram-md5.c	Mon Nov 10 22:36:02 2003 +0200
@@ -0,0 +1,58 @@
+/* Copyright (C) 2003 Timo Sirainen */
+
+#include "lib.h"
+#include "md5.h"
+#include "hex-binary.h"
+#include "password-scheme.h"
+
+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));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/password-scheme-md5crypt.c	Mon Nov 10 22:36:02 2003 +0200
@@ -0,0 +1,147 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * Ported from FreeBSD to Linux, only minimal changes.  --marekm
+ */
+
+/*
+ * Adapted from shadow-19990607 by Tudor Bosman, tudorb@jm.nu
+ */
+
+#include "lib.h"
+#include "safe-memset.h"
+#include "str.h"
+#include "md5.h"
+#include "password-scheme.h"
+
+static unsigned char itoa64[] =		/* 0 ... 63 => ascii - 64 */
+	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static char	*magic = "$1$";	/*
+				 * This string is magic for
+				 * this algorithm.  Having
+				 * it this way, we can get
+				 * get better later on
+				 */
+
+static void
+to64(string_t *str, unsigned long v, int n)
+{
+	while (--n >= 0) {
+		str_append_c(str, itoa64[v&0x3f]);
+		v >>= 6;
+	}
+}
+
+/*
+ * UNIX password
+ *
+ * Use MD5 for what it is best at...
+ */
+
+const char *password_generate_md5_crypt(const char *pw, const char *salt)
+{
+	const char *sp,*ep;
+	unsigned char	final[16];
+	int sl,pl,i,j;
+	struct md5_context ctx,ctx1;
+	unsigned long l;
+	string_t *passwd;
+	size_t pw_len = strlen(pw);
+
+	/* Refine the Salt first */
+	sp = salt;
+
+	/* If it starts with the magic string, then skip that */
+	if (strncmp(sp, magic, sizeof(magic)-1) == 0)
+		sp += sizeof(magic)-1;
+
+	/* It stops at the first '$', max 8 chars */
+	for(ep=sp;*ep && *ep != '$' && ep < (sp+8);ep++)
+		continue;
+
+	/* get the length of the true salt */
+	sl = ep - sp;
+
+	md5_init(&ctx);
+
+	/* The password first, since that is what is most unknown */
+	md5_update(&ctx,pw,pw_len);
+
+	/* Then our magic string */
+	md5_update(&ctx,magic,sizeof(magic)-1);
+
+	/* Then the raw salt */
+	md5_update(&ctx,sp,sl);
+
+	/* Then just as many characters of the MD5(pw,salt,pw) */
+	md5_init(&ctx1);
+	md5_update(&ctx1,pw,pw_len);
+	md5_update(&ctx1,sp,sl);
+	md5_update(&ctx1,pw,pw_len);
+	md5_final(&ctx1,final);
+	for(pl = pw_len; pl > 0; pl -= 16)
+		md5_update(&ctx,final,pl>16 ? 16 : pl);
+
+	/* Don't leave anything around in vm they could use. */
+	safe_memset(final, 0, sizeof(final));
+
+	/* Then something really weird... */
+	for (j=0,i = pw_len; i ; i >>= 1)
+		if(i&1)
+		    md5_update(&ctx, final+j, 1);
+		else
+		    md5_update(&ctx, pw+j, 1);
+
+	/* Now make the output string */
+	passwd = t_str_new(sl + 64);
+	str_append(passwd, magic);
+	str_append_n(passwd, sp, sl);
+	str_append_c(passwd, '$');
+
+	md5_final(&ctx,final);
+
+	/*
+	 * and now, just to make sure things don't run too fast
+	 * On a 60 Mhz Pentium this takes 34 msec, so you would
+	 * need 30 seconds to build a 1000 entry dictionary...
+	 */
+	for(i=0;i<1000;i++) {
+		md5_init(&ctx1);
+		if(i & 1)
+			md5_update(&ctx1,pw,pw_len);
+		else
+			md5_update(&ctx1,final,16);
+
+		if(i % 3)
+			md5_update(&ctx1,sp,sl);
+
+		if(i % 7)
+			md5_update(&ctx1,pw,pw_len);
+
+		if(i & 1)
+			md5_update(&ctx1,final,16);
+		else
+			md5_update(&ctx1,pw,pw_len);
+		md5_final(&ctx1,final);
+	}
+
+	l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(passwd,l,4);
+	l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(passwd,l,4);
+	l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(passwd,l,4);
+	l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(passwd,l,4);
+	l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(passwd,l,4);
+	l =                    final[11]                ; to64(passwd,l,2);
+
+	/* Don't leave anything around in vm they could use. */
+	safe_memset(final, 0, sizeof(final));
+
+	return str_c(passwd);
+}
--- a/src/auth/password-scheme.c	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/password-scheme.c	Mon Nov 10 22:36:02 2003 +0200
@@ -3,7 +3,6 @@
 #include "lib.h"
 #include "hex-binary.h"
 #include "md5.h"
-#include "md5crypt.h"
 #include "mycrypt.h"
 #include "randgen.h"
 #include "password-scheme.h"
@@ -23,8 +22,10 @@
 	if (strcasecmp(scheme, "CRYPT") == 0)
 		return strcmp(mycrypt(plaintext, password), password) == 0;
 
-	if (strcasecmp(scheme, "MD5") == 0)
-		return strcmp(md5_crypt(plaintext, password), password) == 0;
+	if (strcasecmp(scheme, "MD5") == 0) {
+                str = password_generate_md5_crypt(plaintext, password);
+		return strcmp(str, password) == 0;
+	}
 
 	if (strcasecmp(scheme, "PLAIN") == 0)
 		return strcmp(password, plaintext) == 0;
@@ -103,12 +104,15 @@
 		for (i = 0; i < 8; i++)
 			salt[i] = salt_chars[salt[i] % (sizeof(salt_chars)-1)];
 		salt[8] = '\0';
-		return t_strdup(md5_crypt(plaintext, salt));
+		return password_generate_md5_crypt(plaintext, salt);
 	}
 
 	if (strcasecmp(scheme, "PLAIN") == 0)
 		return plaintext;
 
+	if (strcasecmp(scheme, "CRAM-MD5") == 0)
+		return password_generate_cram_md5(plaintext);
+
 	if (strcasecmp(scheme, "DIGEST-MD5") == 0) {
 		/* user:realm:passwd */
 		realm = strchr(user, '@');
--- a/src/auth/password-scheme.h	Mon Nov 10 20:53:40 2003 +0200
+++ b/src/auth/password-scheme.h	Mon Nov 10 22:36:02 2003 +0200
@@ -12,4 +12,8 @@
 const char *password_generate(const char *plaintext, const char *user,
 			      const char *scheme);
 
+/* INTERNAL: */
+const char *password_generate_md5_crypt(const char *pw, const char *salt);
+const char *password_generate_cram_md5(const char *pw);
+
 #endif