changeset 2428:abef2ac8843a HEAD

Added Compuserve RPA authentication. Patch by Andrey Panin.
author Timo Sirainen <tss@iki.fi>
date Thu, 19 Aug 2004 06:56:01 +0300
parents e1616067df5c
children 92f92b3c447b
files AUTHORS src/auth/Makefile.am src/auth/mech-rpa.c src/auth/mech.c src/auth/passdb.c src/auth/passdb.h src/auth/password-scheme-rpa.c src/auth/password-scheme.c src/auth/password-scheme.h
diffstat 9 files changed, 693 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Thu Aug 19 02:53:39 2004 +0300
+++ b/AUTHORS	Thu Aug 19 06:56:01 2004 +0300
@@ -7,7 +7,7 @@
 Matthew Reimer <mreimer@vpop.net> (src/auth/*-mysql.c)
 
 Andrey Panin <pazke@donpac.ru> (src/auth/mech-apop.c, src/auth/mech-login.c,
-  src/lib-ntlm/*, src/auth/mech-ntlm.c)
+  src/lib-ntlm/*, src/auth/mech-ntlm.c, src/auth/mech-rpa.c)
 
 Joshua Goodall <joshua@roughtrade.net> (src/auth/password-scheme-cram-md5.c,
   src/util/dovecotpw.c)
--- a/src/auth/Makefile.am	Thu Aug 19 02:53:39 2004 +0300
+++ b/src/auth/Makefile.am	Thu Aug 19 06:56:01 2004 +0300
@@ -17,7 +17,8 @@
 	password-scheme.c \
 	password-scheme-md5crypt.c \
 	password-scheme-cram-md5.c \
-	password-scheme-ntlm.c
+	password-scheme-ntlm.c \
+	password-scheme-rpa.c
 
 dovecot_auth_LDADD = \
 	libpassword.a \
@@ -45,6 +46,7 @@
 	mech-cram-md5.c \
 	mech-digest-md5.c \
 	mech-ntlm.c \
+	mech-rpa.c \
 	mech-apop.c \
 	passdb.c \
 	passdb-bsdauth.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/mech-rpa.c	Thu Aug 19 06:56:01 2004 +0300
@@ -0,0 +1,627 @@
+/*
+ * Compuserve RPA authentication mechanism.
+ *
+ * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published 
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "common.h"
+#include "mech.h"
+#include "passdb.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "safe-memset.h"
+#include "randgen.h"
+#include "buffer.h"
+#include "hostpid.h"
+#include "hex-binary.h"
+#include "md5.h"
+
+struct rpa_auth_request {
+	struct auth_request auth_request;
+
+	pool_t pool;
+
+	int phase;
+
+	/* cached: */
+	unsigned char *pwd_md5;
+	size_t service_len;
+	const unsigned char *service_ucs2be;
+	size_t username_len;
+	const unsigned char *username_ucs2be;
+	size_t realm_len;
+	const unsigned char *realm_ucs2be;
+
+	/* requested: */
+	unsigned char *service_challenge;
+	unsigned char *service_timestamp;
+
+	/* received: */
+	unsigned int user_challenge_len;
+	unsigned char *user_challenge;
+	unsigned char *user_response;
+	unsigned char *session_key;
+};
+
+#define RPA_SCHALLENGE_LEN	32
+#define RPA_UCHALLENGE_LEN	16
+#define RPA_TIMESTAMP_LEN	14
+
+#define ASN1_APPLICATION	0x60
+
+/* Object id encoded using ASN.1 DER */
+static const unsigned char rpa_oid[] = {
+	0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x73, 0x01, 0x01
+};
+
+void *ucs2be_str(pool_t pool, const char *str, size_t *size);
+
+/*
+ * Compute client -> server authentication response.
+ */
+static void rpa_user_response(struct rpa_auth_request *auth,
+			      unsigned char *digest)
+{
+	struct md5_context ctx;
+	unsigned char z[48];
+
+	memset(z, 0, sizeof(z));
+
+	md5_init(&ctx);
+	md5_update(&ctx, auth->pwd_md5, 16);
+	md5_update(&ctx, z, sizeof(z));
+	md5_update(&ctx, auth->username_ucs2be, auth->username_len);
+	md5_update(&ctx, auth->service_ucs2be, auth->service_len);
+	md5_update(&ctx, auth->realm_ucs2be, auth->realm_len);
+	md5_update(&ctx, auth->user_challenge, auth->user_challenge_len);
+	md5_update(&ctx, auth->service_challenge, RPA_SCHALLENGE_LEN);
+	md5_update(&ctx, auth->service_timestamp, RPA_TIMESTAMP_LEN);
+	md5_update(&ctx, auth->pwd_md5, 16);
+	md5_final(&ctx, digest);
+}
+
+/*
+ * Compute server -> client authentication response.
+ */
+static void rpa_server_response(struct rpa_auth_request *auth,
+				unsigned char *digest)
+{
+	struct md5_context ctx;
+	unsigned char tmp[16];
+	unsigned char z[48];
+	int i;
+
+	memset(z, 0, sizeof(z));
+
+	md5_init(&ctx);
+	md5_update(&ctx, auth->pwd_md5, 16);
+	md5_update(&ctx, z, sizeof(z));
+	md5_update(&ctx, auth->service_ucs2be, auth->service_len);
+	md5_update(&ctx, auth->username_ucs2be, auth->username_len);
+	md5_update(&ctx, auth->realm_ucs2be, auth->realm_len);
+	md5_update(&ctx, auth->service_challenge, RPA_SCHALLENGE_LEN);
+	md5_update(&ctx, auth->user_challenge, auth->user_challenge_len);
+	md5_update(&ctx, auth->service_timestamp, RPA_TIMESTAMP_LEN);
+	md5_update(&ctx, auth->pwd_md5, 16);
+	md5_final(&ctx, tmp);
+
+	for (i = 0; i < 16; i++)
+		tmp[i] = auth->session_key[i] ^ tmp[i];
+
+	md5_init(&ctx);
+	md5_update(&ctx, auth->pwd_md5, 16);
+	md5_update(&ctx, z, sizeof(z));
+	md5_update(&ctx, auth->service_ucs2be, auth->service_len);
+	md5_update(&ctx, auth->username_ucs2be, auth->username_len);
+	md5_update(&ctx, auth->realm_ucs2be, auth->realm_len);
+	md5_update(&ctx, auth->session_key, 16);
+	md5_update(&ctx, auth->service_challenge, RPA_SCHALLENGE_LEN);
+	md5_update(&ctx, auth->user_challenge, auth->user_challenge_len);
+	md5_update(&ctx, auth->service_timestamp, RPA_TIMESTAMP_LEN);
+	md5_update(&ctx, tmp, 16);
+	md5_update(&ctx, auth->pwd_md5, 16);
+	md5_final(&ctx, digest);
+}
+
+static const unsigned char *
+rpa_check_message(const unsigned char *data, const unsigned char *end,
+		  char **error)
+{
+	const unsigned char *p = data;
+	unsigned int len = 0;
+
+	if (p + 2 > end) {
+		*error = "message too short";
+		return NULL;
+	}
+
+	if (*p++ != ASN1_APPLICATION) {
+		*error = "invalid data type";
+		return NULL;
+	}
+
+	if ((*p & 0x80) != 0) {
+		unsigned int nbytes = *p++ & 0x7f;
+
+		while (nbytes-- > 0) {
+			if (p >= end) {
+				*error = "invalid structure length";
+				return NULL;
+			}
+
+			len = (len << 8) | *p++;
+		}
+	} else
+		len = *p++;
+
+	if ((size_t)(end - p) != len) {
+		*error = "structure length disagrees with data size";
+		return NULL;
+	}
+
+	if (p + sizeof(rpa_oid) > end) {
+		*error = "not enough space for object id";
+		return NULL;
+	}
+
+	if (memcmp(p, rpa_oid, sizeof(rpa_oid)) != 0) {
+		*error = "invalid object id";
+		return NULL;
+	}
+
+	return p + sizeof(rpa_oid);
+}
+
+static int
+rpa_parse_token1(const void *data, size_t data_size, char **error)
+{
+	const unsigned char *end = ((unsigned char *) data) + data_size;
+	const unsigned char *p;
+	unsigned int version_lo, version_hi;
+
+	p = rpa_check_message(data, end, error);
+	if (p == NULL)
+		return FALSE;
+
+	if (p + 6 > end) {
+		*error = "message too short";
+		return FALSE;
+	}
+
+	version_lo = p[0] + (p[1] << 8);
+	version_hi = p[2] + (p[3] << 8);
+
+	if ((version_lo > 3) || (version_hi < 3)) {
+		*error = "protocol version mismatch";
+		return FALSE;
+	}
+	p += 4;
+
+	if ((p[0] != 0) || (p[1] != 1)) {
+		*error = "invalid message flags";
+		return FALSE;
+	}
+	p += 2;
+
+	if (p != end) {
+		*error = "unneeded data found";
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static unsigned int
+rpa_read_buffer(pool_t pool, const unsigned char **data,
+		const unsigned char *end, unsigned char **buffer)
+{
+	const unsigned char *p = *data;
+	unsigned int len;
+
+	if (p > end)
+		return 0;
+
+	len = *p++;
+	if (p + len > end)
+		return 0;
+
+	*buffer = p_malloc(pool, len);
+	memcpy(*buffer, p, len);
+
+	*data += 1 + len;
+
+	return len;
+}
+
+static char *
+rpa_parse_username(pool_t pool, const char *username)
+{
+	const char *p = strrchr(username, '@');
+
+	return p == NULL ? p_strdup(pool, username) :
+		p_strdup_until(pool, username, p);
+}
+
+static int
+rpa_parse_token3(struct rpa_auth_request *auth, const void *data,
+		 size_t data_size, char **error)
+{
+	struct auth_request *auth_request = (struct auth_request *)auth;
+	const unsigned char *end = ((unsigned char *)data) + data_size;
+	const unsigned char *p;
+	unsigned int len;
+	const char *user;
+
+	p = rpa_check_message(data, end, error);
+	if (p == NULL)
+		return FALSE;
+
+	/* Read username@realm */
+	if (p + 2 > end) {
+		*error = "message too short";
+		return FALSE;
+	}
+
+	len = (p[0] >> 8) + p[1];
+	if (p + 2 + len > end) {
+		*error = "message too short";
+		return FALSE;
+	}
+	p += 2;
+
+	user = t_strndup(p, len);
+	p += len;
+
+	auth_request->user = rpa_parse_username(auth->pool, user);
+
+	auth->username_ucs2be = ucs2be_str(auth->pool, auth_request->user,
+					   &auth->username_len);
+
+	/* Read user challenge */
+	auth->user_challenge_len = rpa_read_buffer(auth->pool, &p, end,
+						   &auth->user_challenge);
+	if (auth->user_challenge_len == 0) {
+		*error = "invalid user challenge";
+		return FALSE;
+	}
+
+	/* Read user response */
+	len = rpa_read_buffer(auth->pool, &p, end, &auth->user_response);
+	if (len != RPA_UCHALLENGE_LEN) {
+		*error = "invalid user response";
+		return FALSE;
+	}
+
+	if (p != end) {
+		*error = "unneeded data found";
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+buffer_append_asn1_length(buffer_t *buf, unsigned int length)
+{
+	if (length < 0x80) {
+		buffer_append_c(buf, length);
+	} else if (length < 0x100) {
+		buffer_append_c(buf, 0x81);
+		buffer_append_c(buf, length);
+	} else {
+		buffer_append_c(buf, 0x82);
+		buffer_append_c(buf, length >> 8);
+		buffer_append_c(buf, length & 0xff);
+	}
+}
+
+static const char *
+mech_rpa_build_token2(struct rpa_auth_request *auth,
+		      const char *realms, size_t *size)
+{
+	unsigned int realms_len;
+	unsigned int length;
+	buffer_t *buf;
+	unsigned char timestamp[RPA_TIMESTAMP_LEN / 2];
+
+	realms_len = strlen(realms);
+        length = sizeof(rpa_oid) + 3 + RPA_SCHALLENGE_LEN +
+		RPA_TIMESTAMP_LEN + 2 + realms_len;
+
+	buf = buffer_create_dynamic(auth->pool, length + 4, (size_t)-1);
+
+	buffer_append_c(buf, ASN1_APPLICATION);
+	buffer_append_asn1_length(buf, length);
+	buffer_append(buf, rpa_oid, sizeof(rpa_oid));
+
+	/* Protocol version */
+	buffer_append_c(buf, 3);
+	buffer_append_c(buf, 0);
+
+	/* Service challenge */
+	auth->service_challenge = p_malloc(auth->pool, RPA_SCHALLENGE_LEN);
+	random_fill(auth->service_challenge, RPA_SCHALLENGE_LEN);
+	buffer_append_c(buf, RPA_SCHALLENGE_LEN);
+	buffer_append(buf, auth->service_challenge, RPA_SCHALLENGE_LEN);
+
+	/* Timestamp, looks like clients accept anything we send */
+	random_fill(timestamp, sizeof(timestamp));
+	auth->service_timestamp = p_malloc(auth->pool, RPA_TIMESTAMP_LEN);
+	memcpy(auth->service_timestamp,
+	       binary_to_hex(timestamp, sizeof(timestamp)),
+	       RPA_TIMESTAMP_LEN);
+	buffer_append(buf, auth->service_timestamp, RPA_TIMESTAMP_LEN);
+
+	/* Realm list */
+	buffer_append_c(buf, realms_len >> 8);
+	buffer_append_c(buf, realms_len & 0xff);
+	buffer_append(buf, realms, realms_len);
+
+	*size = buffer_get_used_size(buf);
+	return buffer_free_without_data(buf);
+}
+
+static const char *
+mech_rpa_build_token4(struct rpa_auth_request *auth, size_t *size)
+{
+	unsigned int length = sizeof(rpa_oid) + 17 + 17 + 1;
+	buffer_t *buf;
+	unsigned char server_response[16];
+
+	buf = buffer_create_dynamic(auth->pool, length + 4, (size_t)-1);
+
+	buffer_append_c(buf, ASN1_APPLICATION);
+	buffer_append_asn1_length(buf, length);
+	buffer_append(buf, rpa_oid, sizeof(rpa_oid));
+
+	/* Generate random session key */
+	auth->session_key = p_malloc(auth->pool, 16);
+	random_fill(auth->session_key, 16);
+
+	/* Server authentication response */
+	rpa_server_response(auth, server_response);
+	buffer_append_c(buf, 16);
+	buffer_append(buf, server_response, 16);
+
+	buffer_append_c(buf, 16);
+	buffer_append(buf, auth->session_key, 16);
+
+	/* Status, 0 - success */
+	buffer_append_c(buf, 0);
+
+	*size = buffer_get_used_size(buf);
+	return buffer_free_without_data(buf);
+}
+
+static void
+rpa_credentials_callback(const char *credentials,
+			 struct auth_request *auth_request)
+{
+	struct rpa_auth_request *auth =
+		(struct rpa_auth_request *)auth_request;
+	buffer_t *hash_buffer;
+
+	if (credentials == NULL)
+		return;
+
+	auth->pwd_md5 = p_malloc(auth->pool, 16);
+
+	hash_buffer = buffer_create_data(auth->pool, auth->pwd_md5, 16);
+
+	hex_to_binary(credentials, hash_buffer);
+}
+
+static int
+mech_rpa_auth_phase1(struct auth_request *auth_request,
+		     const unsigned char *data, size_t data_size,
+		     mech_callback_t *callback)
+{
+	struct rpa_auth_request *auth =
+		(struct rpa_auth_request *)auth_request;
+	struct auth_client_request_reply reply;
+	const unsigned char *token2;
+	size_t token2_size;
+	const char *service;
+	char *error;
+
+	if (!rpa_parse_token1(data, data_size, &error)) {
+		if (verbose) {
+			i_info("rpa(%s): invalid token 1, %s",
+			       get_log_prefix(auth_request), error);
+		}
+		mech_auth_finish(auth_request, NULL, 0, FALSE);
+		return TRUE;
+	}
+
+	service = t_str_lcase(auth_request->protocol);
+
+	token2 = mech_rpa_build_token2(auth, t_strconcat(service, "@",
+				       my_hostname, NULL), &token2_size);
+
+	auth->service_ucs2be = ucs2be_str(auth->pool, service,
+					  &auth->service_len);
+	auth->realm_ucs2be = ucs2be_str(auth->pool, my_hostname,
+					&auth->realm_len);
+
+	mech_init_auth_client_reply(&reply);
+	reply.id = auth_request->id;
+	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
+
+	reply.reply_idx = 0;
+	reply.data_size = token2_size;
+	callback(&reply, token2, auth_request->conn);
+
+	auth->phase = 1;
+
+	return TRUE;
+}
+
+static int
+mech_rpa_auth_phase2(struct auth_request *auth_request,
+		     const unsigned char *data, size_t data_size,
+		     mech_callback_t *callback)
+{
+	struct rpa_auth_request *auth =
+		(struct rpa_auth_request *)auth_request;
+	struct auth_client_request_reply reply;
+	unsigned char response[16];
+	const unsigned char *token4;
+	size_t token4_size;
+	char *error;
+
+	if (!rpa_parse_token3(auth, data, data_size, &error)) {
+		if (verbose) {
+			i_info("rpa(%s): invalid token 3, %s",
+			       get_log_prefix(auth_request), error);
+		}
+		mech_auth_finish(auth_request, NULL, 0, FALSE);
+		return TRUE;
+	}
+
+	if (!mech_is_valid_username(auth_request->user)) {
+		if (verbose) {
+			i_info("rpa(%s): invalid username",
+			       get_log_prefix(auth_request));
+		}
+		mech_auth_finish(auth_request, NULL, 0, FALSE);
+		return TRUE;
+	}
+
+	passdb->lookup_credentials(auth_request, PASSDB_CREDENTIALS_RPA,
+				   rpa_credentials_callback);
+	if (auth->pwd_md5 == NULL) {
+		mech_auth_finish(auth_request, NULL, 0, FALSE);
+		return TRUE;
+	}
+
+	rpa_user_response(auth, response);
+	if (memcmp(response, auth->user_response, 16) != 0) {
+		mech_auth_finish(auth_request, NULL, 0, FALSE);
+		return TRUE;
+	}
+
+	token4 = mech_rpa_build_token4(auth, &token4_size);
+
+	mech_init_auth_client_reply(&reply);
+	reply.id = auth_request->id;
+	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
+
+	reply.reply_idx = 0;
+	reply.data_size = token4_size;
+	callback(&reply, token4, auth_request->conn);
+
+	auth->phase = 2;
+
+	return TRUE;
+}
+
+static int
+mech_rpa_auth_phase3(struct auth_request *auth_request,
+		     const unsigned char *data, size_t data_size,
+		     mech_callback_t *callback __attr_unused__)
+{
+	static const unsigned char client_ack[3] = { 0x60, 0x01, 0x00 };
+	int ret = TRUE;
+
+	if ((data_size != sizeof(client_ack)) ||
+	    (memcmp(data, client_ack, sizeof(client_ack)) != 0)) {
+		if (verbose) {
+			i_info("rpa(%s): invalid token 5 or client rejects us",
+			       get_log_prefix(auth_request));
+		}
+		ret = FALSE;
+	}
+
+	mech_auth_finish(auth_request, NULL, 0, ret);
+	return TRUE;
+}
+
+static int
+mech_rpa_auth_continue(struct auth_request *auth_request,
+			const unsigned char *data, size_t data_size,
+			mech_callback_t *callback)
+{
+	struct rpa_auth_request *auth =
+		(struct rpa_auth_request *)auth_request;
+
+	auth_request->callback = callback;
+
+	switch (auth->phase) {
+		case 0:	return mech_rpa_auth_phase1(auth_request, data,
+						    data_size, callback);
+		case 1:	return mech_rpa_auth_phase2(auth_request, data,
+						    data_size, callback);
+		case 2:	return mech_rpa_auth_phase3(auth_request, data,
+						    data_size, callback);
+	}
+
+	mech_auth_finish(auth_request, NULL, 0, FALSE);
+	return TRUE;
+}
+
+static int
+mech_rpa_auth_initial(struct auth_request *auth_request,
+		      struct auth_client_request_new *request,
+		      const unsigned char *data __attr_unused__,
+		      mech_callback_t *callback)
+{
+	struct auth_client_request_reply reply;
+
+	mech_init_auth_client_reply(&reply);
+	reply.id = request->id;
+	reply.result = AUTH_CLIENT_RESULT_CONTINUE;
+
+	reply.reply_idx = 0;
+	reply.data_size = 0;
+	callback(&reply, "", auth_request->conn);
+
+	return TRUE;
+}
+
+static void
+mech_rpa_auth_free(struct auth_request *auth_request)
+{
+	struct rpa_auth_request *auth =
+		(struct rpa_auth_request *)auth_request;
+
+	if (auth->pwd_md5 != NULL)
+		safe_memset(auth->pwd_md5, 0, 16);
+
+	pool_unref(auth_request->pool);
+}
+
+static struct auth_request *mech_rpa_auth_new(void)
+{
+	struct rpa_auth_request *auth;
+	pool_t pool;
+
+	pool = pool_alloconly_create("rpa_auth_request", 256);
+	auth = p_new(pool, struct rpa_auth_request, 1);
+	auth->pool = pool;
+	auth->phase = 0;
+
+	auth->auth_request.refcount = 1;
+	auth->auth_request.pool = pool;
+	auth->auth_request.auth_initial = mech_rpa_auth_initial;
+	auth->auth_request.auth_continue = mech_rpa_auth_continue;
+	auth->auth_request.auth_free = mech_rpa_auth_free;
+
+	return &auth->auth_request;
+}
+
+const struct mech_module mech_rpa = {
+	"RPA",
+
+	MEMBER(plaintext) FALSE,
+	MEMBER(advertise) TRUE,
+
+	MEMBER(passdb_need_plain) FALSE,
+	MEMBER(passdb_need_credentials) TRUE,
+
+	mech_rpa_auth_new,
+};
--- a/src/auth/mech.c	Thu Aug 19 02:53:39 2004 +0300
+++ b/src/auth/mech.c	Thu Aug 19 06:56:01 2004 +0300
@@ -393,6 +393,7 @@
 extern struct mech_module mech_cram_md5;
 extern struct mech_module mech_digest_md5;
 extern struct mech_module mech_ntlm;
+extern struct mech_module mech_rpa;
 extern struct mech_module mech_anonymous;
 
 void mech_init(void)
@@ -428,6 +429,8 @@
 			mech_register_module(&mech_digest_md5);
 		else if (strcasecmp(*mechanisms, "NTLM") == 0)
 			mech_register_module(&mech_ntlm);
+		else if (strcasecmp(*mechanisms, "RPA") == 0)
+			mech_register_module(&mech_rpa);
 		else if (strcasecmp(*mechanisms, "ANONYMOUS") == 0) {
 			if (anonymous_username == NULL) {
 				i_fatal("ANONYMOUS listed in mechanisms, "
@@ -489,5 +492,6 @@
 	mech_unregister_module(&mech_cram_md5);
 	mech_unregister_module(&mech_digest_md5);
 	mech_unregister_module(&mech_ntlm);
+	mech_unregister_module(&mech_rpa);
 	mech_unregister_module(&mech_anonymous);
 }
--- a/src/auth/passdb.c	Thu Aug 19 02:53:39 2004 +0300
+++ b/src/auth/passdb.c	Thu Aug 19 06:56:01 2004 +0300
@@ -32,6 +32,8 @@
 		return "LANMAN";
 	case PASSDB_CREDENTIALS_NTLM:
 		return "NTLM";
+	case PASSDB_CREDENTIALS_RPA:
+		return "RPA";
 	}
 
 	return "??";
--- a/src/auth/passdb.h	Thu Aug 19 02:53:39 2004 +0300
+++ b/src/auth/passdb.h	Thu Aug 19 06:56:01 2004 +0300
@@ -14,7 +14,8 @@
 	PASSDB_CREDENTIALS_CRAM_MD5,
 	PASSDB_CREDENTIALS_DIGEST_MD5,
 	PASSDB_CREDENTIALS_LANMAN,
-	PASSDB_CREDENTIALS_NTLM
+	PASSDB_CREDENTIALS_NTLM,
+	PASSDB_CREDENTIALS_RPA
 };
 
 enum passdb_result {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/password-scheme-rpa.c	Thu Aug 19 06:56:01 2004 +0300
@@ -0,0 +1,40 @@
+
+#include "lib.h"
+#include "buffer.h"
+#include "md5.h"
+#include "hex-binary.h"
+#include "safe-memset.h"
+#include "password-scheme.h"
+
+void *ucs2be_str(pool_t pool, const char *str, size_t *size);
+
+/*
+ * Convert string to big-endian ucs2.
+ */
+void *ucs2be_str(pool_t pool, const char *str, size_t *size)
+{
+	buffer_t *buf = buffer_create_dynamic(pool, 32, (size_t)-1);
+
+	while (*str) {
+		buffer_append_c(buf, '\0');
+		buffer_append_c(buf, *str++);
+	}
+
+	*size = buffer_get_used_size(buf);
+	return buffer_free_without_data(buf);
+}
+
+const char *password_generate_rpa(const char *pw)
+{
+	unsigned char hash[16];
+	unsigned char *ucs2be_pw;
+	size_t size;
+
+	ucs2be_pw = ucs2be_str(unsafe_data_stack_pool, pw, &size);
+
+	md5_get_digest(ucs2be_pw, size, hash);
+
+	safe_memset(ucs2be_pw, 0, size);
+
+	return binary_to_hex(hash, sizeof(hash));
+}
--- a/src/auth/password-scheme.c	Thu Aug 19 02:53:39 2004 +0300
+++ b/src/auth/password-scheme.c	Thu Aug 19 06:56:01 2004 +0300
@@ -424,6 +424,18 @@
 	return password_generate_ntlm(plaintext);
 }
 
+static int rpa_verify(const char *plaintext, const char *password,
+		      const char *user __attr_unused__)
+{
+	return strcasecmp(password, password_generate_rpa(plaintext)) == 0;
+}
+
+static const char *rpa_generate(const char *plaintext,
+				const char *user __attr_unused__)
+{
+	return password_generate_rpa(plaintext);
+}
+
 static const struct password_scheme default_schemes[] = {
 	{ "CRYPT", crypt_verify, crypt_generate },
 	{ "MD5", md5_verify, md5_generate },
@@ -439,6 +451,7 @@
 	{ "LDAP-MD5", ldap_md5_verify, ldap_md5_generate },
 	{ "LANMAN", lm_verify, lm_generate },
 	{ "NTLM", ntlm_verify, ntlm_generate },
+	{ "RPA", rpa_verify, rpa_generate },
 	{ NULL, NULL, NULL }
 };
 
--- a/src/auth/password-scheme.h	Thu Aug 19 02:53:39 2004 +0300
+++ b/src/auth/password-scheme.h	Thu Aug 19 06:56:01 2004 +0300
@@ -32,5 +32,6 @@
 const char *password_generate_cram_md5(const char *pw);
 const char *password_generate_lm(const char *pw);
 const char *password_generate_ntlm(const char *pw);
+const char *password_generate_rpa(const char *pw);
 
 #endif