view src/auth/password-scheme.c @ 21322:5ab8dc1a4a6f

global: Change string position/length from unsigned int to size_t Mainly to avoid truncating >4GB strings, which might potentially cause some security holes. Normally there are other limits, which prevent such excessive strings from being created in the first place. I'm sure this didn't find everything. Maybe everything could be found with compiler warnings. -Wconversion kind of does it, but it gives way too many unnecessary warnings. These were mainly found with: grep " = strlen" egrep "unsigned int.*(size|len)"
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Mon, 12 Dec 2016 07:19:55 +0200
parents 89e2abf6b828
children 2e2563132d5f
line wrap: on
line source

/* Copyright (c) 2003-2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "base64.h"
#include "hex-binary.h"
#include "md4.h"
#include "md5.h"
#include "hmac.h"
#include "hmac-cram-md5.h"
#include "ntlm.h"
#include "mycrypt.h"
#include "randgen.h"
#include "sha1.h"
#include "sha2.h"
#include "otp.h"
#include "str.h"
#include "password-scheme.h"

static const char salt_chars[] =
	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

ARRAY_TYPE(password_scheme_p) password_schemes;

static const struct password_scheme *
password_scheme_lookup_name(const char *name)
{
	const struct password_scheme *const *schemes;

	array_foreach(&password_schemes, schemes) {
		const struct password_scheme *scheme = *schemes;

		if (strcasecmp(scheme->name, name) == 0)
			return scheme;
	}
	return NULL;
}

/* Lookup scheme and encoding by given name. The encoding is taken from
   ".base64", ".b64" or ".hex" suffix if it exists, otherwise the default
   encoding is used. */
static const struct password_scheme *
password_scheme_lookup(const char *name, enum password_encoding *encoding_r)
{
	const struct password_scheme *scheme;
	const char *encoding = NULL;
	size_t scheme_len;

	*encoding_r = PW_ENCODING_NONE;

	for (scheme_len = 0; name[scheme_len] != '\0'; scheme_len++) {
		if (name[scheme_len] == '.') {
			encoding = name + scheme_len + 1;
			name = t_strndup(name, scheme_len);
			break;
		}
	}

	scheme = password_scheme_lookup_name(name);
	if (scheme == NULL)
		return NULL;

	if (encoding == NULL)
		*encoding_r = scheme->default_encoding;
	else if (strcasecmp(encoding, "b64") == 0 ||
		 strcasecmp(encoding, "base64") == 0)
		*encoding_r = PW_ENCODING_BASE64;
	else if (strcasecmp(encoding, "hex") == 0)
		*encoding_r = PW_ENCODING_HEX;
	else {
		/* unknown encoding. treat as invalid scheme. */
		return NULL;
	}
	return scheme;
}

int password_verify(const char *plaintext, const char *user, const char *scheme,
		    const unsigned char *raw_password, size_t size,
		    const char **error_r)
{
	const struct password_scheme *s;
	enum password_encoding encoding;
	const unsigned char *generated;
	size_t generated_size;
	int ret;

	s = password_scheme_lookup(scheme, &encoding);
	if (s == NULL) {
		*error_r = "Unknown password scheme";
		return -1;
	}

	if (s->password_verify != NULL) {
		ret = s->password_verify(plaintext, user, raw_password, size,
					 error_r);
	} else {
		/* generic verification handler: generate the password and
		   compare it to the one in database */
		s->password_generate(plaintext, user,
				     &generated, &generated_size);
		ret = size != generated_size ? 0 :
			memcmp(generated, raw_password, size) == 0 ? 1 : 0;
	}

	if (ret == 0)
		*error_r = "Password mismatch";
	return ret;
}

const char *password_get_scheme(const char **password)
{
	const char *p, *scheme;

	if (*password == NULL)
		return NULL;

	if (strncmp(*password, "$1$", 3) == 0) {
		/* $1$<salt>$<password>[$<ignored>] */
		p = strchr(*password + 3, '$');
		if (p != NULL) {
			/* stop at next '$' after password */
			p = strchr(p+1, '$');
			if (p != NULL)
				*password = t_strdup_until(*password, p);
			return "MD5-CRYPT";
		}
	}

	if (**password != '{')
		return NULL;

	p = strchr(*password, '}');
	if (p == NULL)
		return NULL;

	scheme = t_strdup_until(*password + 1, p);
	*password = p + 1;
	return scheme;
}

int password_decode(const char *password, const char *scheme,
		    const unsigned char **raw_password_r, size_t *size_r,
		    const char **error_r)
{
	const struct password_scheme *s;
	enum password_encoding encoding;
	buffer_t *buf;
	size_t len;
	bool guessed_encoding;

	*error_r = NULL;

	s = password_scheme_lookup(scheme, &encoding);
	if (s == NULL) {
		*error_r = "Unknown scheme";
		return 0;
	}

	len = strlen(password);
	if (encoding != PW_ENCODING_NONE && s->raw_password_len != 0 &&
	    strchr(scheme, '.') == NULL) {
		/* encoding not specified. we can guess quite well between
		   base64 and hex encodings. the only problem is distinguishing
		   2 character strings, but there shouldn't be any that short
		   raw_password_lens. */
		encoding = len == s->raw_password_len * 2 ?
			PW_ENCODING_HEX : PW_ENCODING_BASE64;
		guessed_encoding = TRUE;
	} else {
		guessed_encoding = FALSE;
	}

	switch (encoding) {
	case PW_ENCODING_NONE:
		*raw_password_r = (const unsigned char *)password;
		*size_r = len;
		break;
	case PW_ENCODING_HEX:
		buf = buffer_create_dynamic(pool_datastack_create(),
					    len / 2 + 1);
		if (hex_to_binary(password, buf) == 0) {
			*raw_password_r = buf->data;
			*size_r = buf->used;
			break;
		}
		if (!guessed_encoding) {
			*error_r = "Input isn't valid HEX encoded data";
			return -1;
		}
		/* fall through, just in case it was base64-encoded after
		   all. some input lengths produce matching hex and base64
		   encoded lengths. */
	case PW_ENCODING_BASE64:
		buf = buffer_create_dynamic(pool_datastack_create(),
					    MAX_BASE64_DECODED_SIZE(len));
		if (base64_decode(password, len, NULL, buf) < 0) {
			*error_r = "Input isn't valid base64 encoded data";
			return -1;
		}

		*raw_password_r = buf->data;
		*size_r = buf->used;
		break;
	}
	if (s->raw_password_len != *size_r && s->raw_password_len != 0) {
		/* password has invalid length */
		*error_r = t_strdup_printf(
			"Input length isn't valid (%u instead of %u)",
			(unsigned int)*size_r, s->raw_password_len);
		return -1;
	}
	return 1;
}

bool password_generate(const char *plaintext, const char *user,
		       const char *scheme,
		       const unsigned char **raw_password_r, size_t *size_r)
{
	const struct password_scheme *s;
	enum password_encoding encoding;

	s = password_scheme_lookup(scheme, &encoding);
	if (s == NULL)
		return FALSE;

	s->password_generate(plaintext, user, raw_password_r, size_r);
	return TRUE;
}

bool password_generate_encoded(const char *plaintext, const char *user,
			       const char *scheme, const char **password_r)
{
	const struct password_scheme *s;
	const unsigned char *raw_password;
	enum password_encoding encoding;
	string_t *str;
	size_t size;

	s = password_scheme_lookup(scheme, &encoding);
	if (s == NULL)
		return FALSE;

	s->password_generate(plaintext, user, &raw_password, &size);
	switch (encoding) {
	case PW_ENCODING_NONE:
		*password_r = t_strndup(raw_password, size);
		break;
	case PW_ENCODING_BASE64:
		str = t_str_new(MAX_BASE64_ENCODED_SIZE(size) + 1);
		base64_encode(raw_password, size, str);
		*password_r = str_c(str);
		break;
	case PW_ENCODING_HEX:
		*password_r = binary_to_hex(raw_password, size);
		break;
	}
	return TRUE;
}

const char *password_generate_salt(size_t len)
{
	unsigned int i;
	char *salt;

	salt = t_malloc(len + 1);
	random_fill(salt, len);
	for (i = 0; i < len; i++)
		salt[i] = salt_chars[salt[i] % (sizeof(salt_chars)-1)];
	salt[len] = '\0';
	return salt;
}

bool password_scheme_is_alias(const char *scheme1, const char *scheme2)
{
	const struct password_scheme *const *schemes, *s1 = NULL, *s2 = NULL;

	scheme1 = t_strcut(scheme1, '.');
	scheme2 = t_strcut(scheme2, '.');

	if (strcasecmp(scheme1, scheme2) == 0)
		return TRUE;

	array_foreach(&password_schemes, schemes) {
		const struct password_scheme *scheme = *schemes;

		if (strcasecmp(scheme->name, scheme1) == 0)
			s1 = scheme;
		else if (strcasecmp(scheme->name, scheme2) == 0)
			s2 = scheme;
	}

	/* if they've the same generate function, they're equivalent */
	return s1 != NULL && s2 != NULL &&
		s1->password_generate == s2->password_generate;
}

const char *
password_scheme_detect(const char *plain_password, const char *crypted_password,
		       const char *user)
{
	const struct password_scheme *const *schemes;
	unsigned int i, count;
	const unsigned char *raw_password;
	size_t raw_password_size;
	const char *error;

	schemes = array_get(&password_schemes, &count);
	for (i = 0; i < count; i++) {
		if (password_decode(crypted_password, schemes[i]->name,
				    &raw_password, &raw_password_size,
				    &error) <= 0)
			continue;

		if (password_verify(plain_password, user, schemes[i]->name,
				    raw_password, raw_password_size,
				    &error) > 0)
			return schemes[i]->name;
	}
	return NULL;
}

int crypt_verify(const char *plaintext, const char *user ATTR_UNUSED,
		 const unsigned char *raw_password, size_t size,
		 const char **error_r)
{
	const char *password, *crypted;

	if (size == 0) {
		/* the default mycrypt() handler would return match */
		return 0;
	}

	password = t_strndup(raw_password, size);
	crypted = mycrypt(plaintext, password);
	if (crypted == NULL) {
		/* really shouldn't happen unless the system is broken */
		*error_r = t_strdup_printf("crypt() failed: %m");
		return -1;
	}

	return strcmp(crypted, password) == 0 ? 1 : 0;
}

static int
md5_verify(const char *plaintext, const char *user,
	   const unsigned char *raw_password, size_t size, const char **error_r)
{
	const char *password, *str, *error;
	const unsigned char *md5_password;
	size_t md5_size;

	password = t_strndup(raw_password, size);
	if (strncmp(password, "$1$", 3) == 0) {
		/* MD5-CRYPT */
		str = password_generate_md5_crypt(plaintext, password);
		return strcmp(str, password) == 0 ? 1 : 0;
	} else if (password_decode(password, "PLAIN-MD5",
				   &md5_password, &md5_size, &error) <= 0) {
		*error_r = "Not a valid MD5-CRYPT or PLAIN-MD5 password";
		return -1;
	} else {
		return password_verify(plaintext, user, "PLAIN-MD5",
				       md5_password, md5_size, error_r);
	}
}

static int
md5_crypt_verify(const char *plaintext, const char *user ATTR_UNUSED,
		 const unsigned char *raw_password, size_t size,
		 const char **error_r ATTR_UNUSED)
{
	const char *password, *str;

	password = t_strndup(raw_password, size);
	str = password_generate_md5_crypt(plaintext, password);
	return strcmp(str, password) == 0 ? 1 : 0;
}

static void
md5_crypt_generate(const char *plaintext, const char *user ATTR_UNUSED,
		   const unsigned char **raw_password_r, size_t *size_r)
{
	const char *password;
	char salt[9];
	unsigned int i;

	random_fill(salt, sizeof(salt)-1);
	for (i = 0; i < sizeof(salt)-1; i++)
		salt[i] = salt_chars[salt[i] % (sizeof(salt_chars)-1)];
	salt[sizeof(salt)-1] = '\0';

	password = password_generate_md5_crypt(plaintext, salt);
	*raw_password_r = (const unsigned char *)password;
	*size_r = strlen(password);
}

static void
sha1_generate(const char *plaintext, const char *user ATTR_UNUSED,
	      const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(SHA1_RESULTLEN);
	sha1_get_digest(plaintext, strlen(plaintext), digest);

	*raw_password_r = digest;
	*size_r = SHA1_RESULTLEN;
}

static void
sha256_generate(const char *plaintext, const char *user ATTR_UNUSED,
		const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(SHA256_RESULTLEN);
	sha256_get_digest(plaintext, strlen(plaintext), digest);

	*raw_password_r = digest;
	*size_r = SHA256_RESULTLEN;
}

static void
sha512_generate(const char *plaintext, const char *user ATTR_UNUSED,
		const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(SHA512_RESULTLEN);
	sha512_get_digest(plaintext, strlen(plaintext), digest);

	*raw_password_r = digest;
	*size_r = SHA512_RESULTLEN;
}

static void
ssha_generate(const char *plaintext, const char *user ATTR_UNUSED,
	      const unsigned char **raw_password_r, size_t *size_r)
{
#define SSHA_SALT_LEN 4
	unsigned char *digest, *salt;
	struct sha1_ctxt ctx;

	digest = t_malloc(SHA1_RESULTLEN + SSHA_SALT_LEN);
	salt = digest + SHA1_RESULTLEN;
	random_fill(salt, SSHA_SALT_LEN);

	sha1_init(&ctx);
	sha1_loop(&ctx, plaintext, strlen(plaintext));
	sha1_loop(&ctx, salt, SSHA_SALT_LEN);
	sha1_result(&ctx, digest);

	*raw_password_r = digest;
	*size_r = SHA1_RESULTLEN + SSHA_SALT_LEN;
}

static int ssha_verify(const char *plaintext, const char *user ATTR_UNUSED,
		       const unsigned char *raw_password, size_t size,
		       const char **error_r)
{
	unsigned char sha1_digest[SHA1_RESULTLEN];
	struct sha1_ctxt ctx;

	/* format: <SHA1 hash><salt> */
	if (size <= SHA1_RESULTLEN) {
		*error_r = "SSHA password is too short";
		return -1;
	}

	sha1_init(&ctx);
	sha1_loop(&ctx, plaintext, strlen(plaintext));
	sha1_loop(&ctx, raw_password + SHA1_RESULTLEN, size - SHA1_RESULTLEN);
	sha1_result(&ctx, sha1_digest);
	return memcmp(sha1_digest, raw_password, SHA1_RESULTLEN) == 0 ? 1 : 0;
}

static void
ssha256_generate(const char *plaintext, const char *user ATTR_UNUSED,
		 const unsigned char **raw_password_r, size_t *size_r)
{
#define SSHA256_SALT_LEN 4
	unsigned char *digest, *salt;
	struct sha256_ctx ctx;

	digest = t_malloc(SHA256_RESULTLEN + SSHA256_SALT_LEN);
	salt = digest + SHA256_RESULTLEN;
	random_fill(salt, SSHA256_SALT_LEN);

	sha256_init(&ctx);
	sha256_loop(&ctx, plaintext, strlen(plaintext));
	sha256_loop(&ctx, salt, SSHA256_SALT_LEN);
	sha256_result(&ctx, digest);

	*raw_password_r = digest;
	*size_r = SHA256_RESULTLEN + SSHA256_SALT_LEN;
}

static int ssha256_verify(const char *plaintext, const char *user ATTR_UNUSED,
			  const unsigned char *raw_password, size_t size,
			  const char **error_r)
{
	unsigned char sha256_digest[SHA256_RESULTLEN];
	struct sha256_ctx ctx;

	/* format: <SHA256 hash><salt> */
	if (size <= SHA256_RESULTLEN) {
		*error_r = "SSHA256 password is too short";
		return -1;
	}

	sha256_init(&ctx);
	sha256_loop(&ctx, plaintext, strlen(plaintext));
	sha256_loop(&ctx, raw_password + SHA256_RESULTLEN,
		    size - SHA256_RESULTLEN);
	sha256_result(&ctx, sha256_digest);
	return memcmp(sha256_digest, raw_password,
		      SHA256_RESULTLEN) == 0 ? 1 : 0;
}

static void
ssha512_generate(const char *plaintext, const char *user ATTR_UNUSED,
		 const unsigned char **raw_password_r, size_t *size_r)
{
#define SSHA512_SALT_LEN 4
	unsigned char *digest, *salt;
	struct sha512_ctx ctx;

	digest = t_malloc(SHA512_RESULTLEN + SSHA512_SALT_LEN);
	salt = digest + SHA512_RESULTLEN;
	random_fill(salt, SSHA512_SALT_LEN);

	sha512_init(&ctx);
	sha512_loop(&ctx, plaintext, strlen(plaintext));
	sha512_loop(&ctx, salt, SSHA512_SALT_LEN);
	sha512_result(&ctx, digest);

	*raw_password_r = digest;
	*size_r = SHA512_RESULTLEN + SSHA512_SALT_LEN;
}

static int ssha512_verify(const char *plaintext, const char *user ATTR_UNUSED,
			  const unsigned char *raw_password, size_t size,
			  const char **error_r)
{
	unsigned char sha512_digest[SHA512_RESULTLEN];
	struct sha512_ctx ctx;

	/* format: <SHA512 hash><salt> */
	if (size <= SHA512_RESULTLEN) {
		*error_r = "SSHA512 password is too short";
		return -1;
	}

	sha512_init(&ctx);
	sha512_loop(&ctx, plaintext, strlen(plaintext));
	sha512_loop(&ctx, raw_password + SHA512_RESULTLEN,
		    size - SHA512_RESULTLEN);
	sha512_result(&ctx, sha512_digest);
	return memcmp(sha512_digest, raw_password,
		      SHA512_RESULTLEN) == 0 ? 1 : 0;
}

static void
smd5_generate(const char *plaintext, const char *user ATTR_UNUSED,
	      const unsigned char **raw_password_r, size_t *size_r)
{
#define SMD5_SALT_LEN 4
	unsigned char *digest, *salt;
	struct md5_context ctx;

	digest = t_malloc(MD5_RESULTLEN + SMD5_SALT_LEN);
	salt = digest + MD5_RESULTLEN;
	random_fill(salt, SMD5_SALT_LEN);

	md5_init(&ctx);
	md5_update(&ctx, plaintext, strlen(plaintext));
	md5_update(&ctx, salt, SMD5_SALT_LEN);
	md5_final(&ctx, digest);

	*raw_password_r = digest;
	*size_r = MD5_RESULTLEN + SMD5_SALT_LEN;
}

static int smd5_verify(const char *plaintext, const char *user ATTR_UNUSED,
		       const unsigned char *raw_password, size_t size,
		       const char **error_r)
{
	unsigned char md5_digest[MD5_RESULTLEN];
	struct md5_context ctx;

	/* format: <MD5 hash><salt> */
	if (size <= MD5_RESULTLEN) {
		*error_r = "SMD5 password is too short";
		return -1;
	}

	md5_init(&ctx);
	md5_update(&ctx, plaintext, strlen(plaintext));
	md5_update(&ctx, raw_password + MD5_RESULTLEN, size - MD5_RESULTLEN);
	md5_final(&ctx, md5_digest);
	return memcmp(md5_digest, raw_password, MD5_RESULTLEN) == 0 ? 1 : 0;
}

static void
plain_generate(const char *plaintext, const char *user ATTR_UNUSED,
	       const unsigned char **raw_password_r, size_t *size_r)
{
	*raw_password_r = (const unsigned char *)plaintext,
	*size_r = strlen(plaintext);
}

static int
plain_trunc_verify(const char *plaintext, const char *user ATTR_UNUSED,
		   const unsigned char *raw_password, size_t size,
		   const char **error_r)
{
	size_t i, plaintext_len, trunc_len = 0;

	/* format: <length>-<password> */
	for (i = 0; i < size; i++) {
		if (raw_password[i] >= '0' && raw_password[i] <= '9')
			trunc_len = trunc_len*10 + raw_password[i]-'0';
		else
			break;
	}
	if (i == size || raw_password[i] != '-') {
		*error_r = "PLAIN-TRUNC missing length: prefix";
		return -1;
	}
	i++;

	plaintext_len = strlen(plaintext);
	if (size-i == trunc_len && plaintext_len >= trunc_len) {
		/* possibly truncated password. allow the given password as
		   long as the prefix matches. */
		return memcmp(raw_password+i, plaintext, trunc_len) == 0 ? 1 : 0;
	}
	return plaintext_len == size-i &&
		memcmp(raw_password+i, plaintext, plaintext_len) == 0 ? 1 : 0;
}

static void
cram_md5_generate(const char *plaintext, const char *user ATTR_UNUSED,
		  const unsigned char **raw_password_r, size_t *size_r)
{
	struct hmac_context ctx;
	unsigned char *context_digest;

	context_digest = t_malloc(CRAM_MD5_CONTEXTLEN);
	hmac_init(&ctx, (const unsigned char *)plaintext,
		  strlen(plaintext), &hash_method_md5);
	hmac_md5_get_cram_context(&ctx, context_digest);

	*raw_password_r = context_digest;
	*size_r = CRAM_MD5_CONTEXTLEN;
}

static void
digest_md5_generate(const char *plaintext, const char *user,
		    const unsigned char **raw_password_r, size_t *size_r)
{
	const char *realm, *str;
	unsigned char *digest;

	if (user == NULL)
		i_fatal("digest_md5_generate(): username not given");


	/* assume user@realm format for username. If user@domain is wanted
	   in the username, allow also user@domain@realm. */
	realm = strrchr(user, '@');
	if (realm != NULL) {
		user = t_strdup_until(user, realm);
		realm++;
	} else {
		realm = "";
	}

	/* user:realm:passwd */
	digest = t_malloc(MD5_RESULTLEN);
	str = t_strdup_printf("%s:%s:%s", user, realm, plaintext);
	md5_get_digest(str, strlen(str), digest);

	*raw_password_r = digest;
	*size_r = MD5_RESULTLEN;
}

static void
plain_md4_generate(const char *plaintext, const char *user ATTR_UNUSED,
		   const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(MD4_RESULTLEN);
	md4_get_digest(plaintext, strlen(plaintext), digest);

	*raw_password_r = digest;
	*size_r = MD4_RESULTLEN;
}

static void
plain_md5_generate(const char *plaintext, const char *user ATTR_UNUSED,
		   const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(MD5_RESULTLEN);
	md5_get_digest(plaintext, strlen(plaintext), digest);

	*raw_password_r = digest;
	*size_r = MD5_RESULTLEN;
}

static void
lm_generate(const char *plaintext, const char *user ATTR_UNUSED,
	    const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(LM_HASH_SIZE);
	lm_hash(plaintext, digest);

	*raw_password_r = digest;
	*size_r = LM_HASH_SIZE;
}

static void
ntlm_generate(const char *plaintext, const char *user ATTR_UNUSED,
	      const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(NTLMSSP_HASH_SIZE);
	ntlm_v1_hash(plaintext, digest);

	*raw_password_r = digest;
	*size_r = NTLMSSP_HASH_SIZE;
}

static int otp_verify(const char *plaintext, const char *user ATTR_UNUSED,
		      const unsigned char *raw_password, size_t size,
		      const char **error_r)
{
	const char *password, *generated;

	password = t_strndup(raw_password, size);
	if (password_generate_otp(plaintext, password, -1, &generated) < 0) {
		*error_r = "Invalid OTP data in passdb";
		return -1;
	}

	return strcasecmp(password, generated) == 0 ? 1 : 0;
}

static void
otp_generate(const char *plaintext, const char *user ATTR_UNUSED,
	     const unsigned char **raw_password_r, size_t *size_r)
{
	const char *password;

	if (password_generate_otp(plaintext, NULL, OTP_HASH_SHA1, &password) < 0)
		i_unreached();
	*raw_password_r = (const unsigned char *)password;
	*size_r = strlen(password);
}

static void
skey_generate(const char *plaintext, const char *user ATTR_UNUSED,
	      const unsigned char **raw_password_r, size_t *size_r)
{
	const char *password;

	if (password_generate_otp(plaintext, NULL, OTP_HASH_MD4, &password) < 0)
		i_unreached();
	*raw_password_r = (const unsigned char *)password;
	*size_r = strlen(password);
}

static void
rpa_generate(const char *plaintext, const char *user ATTR_UNUSED,
	     const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(MD5_RESULTLEN);
	password_generate_rpa(plaintext, digest);

	*raw_password_r = digest;
	*size_r = MD5_RESULTLEN;
}

static const struct password_scheme builtin_schemes[] = {
	{ "MD5", PW_ENCODING_NONE, 0, md5_verify, md5_crypt_generate },
	{ "MD5-CRYPT", PW_ENCODING_NONE, 0,
	  md5_crypt_verify, md5_crypt_generate },
 	{ "SHA", PW_ENCODING_BASE64, SHA1_RESULTLEN, NULL, sha1_generate },
 	{ "SHA1", PW_ENCODING_BASE64, SHA1_RESULTLEN, NULL, sha1_generate },
 	{ "SHA256", PW_ENCODING_BASE64, SHA256_RESULTLEN,
	  NULL, sha256_generate },
 	{ "SHA512", PW_ENCODING_BASE64, SHA512_RESULTLEN,
	  NULL, sha512_generate },
	{ "SMD5", PW_ENCODING_BASE64, 0, smd5_verify, smd5_generate },
	{ "SSHA", PW_ENCODING_BASE64, 0, ssha_verify, ssha_generate },
	{ "SSHA256", PW_ENCODING_BASE64, 0, ssha256_verify, ssha256_generate },
	{ "SSHA512", PW_ENCODING_BASE64, 0, ssha512_verify, ssha512_generate },
	{ "PLAIN", PW_ENCODING_NONE, 0, NULL, plain_generate },
	{ "CLEAR", PW_ENCODING_NONE, 0, NULL, plain_generate },
	{ "CLEARTEXT", PW_ENCODING_NONE, 0, NULL, plain_generate },
	{ "PLAIN-TRUNC", PW_ENCODING_NONE, 0, plain_trunc_verify, plain_generate },
	{ "CRAM-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN,
	  NULL, cram_md5_generate },
	{ "SCRAM-SHA-1", PW_ENCODING_NONE, 0, scram_sha1_verify,
	  scram_sha1_generate},
	{ "HMAC-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN,
	  NULL, cram_md5_generate },
	{ "DIGEST-MD5", PW_ENCODING_HEX, MD5_RESULTLEN,
	  NULL, digest_md5_generate },
	{ "PLAIN-MD4", PW_ENCODING_HEX, MD4_RESULTLEN,
	  NULL, plain_md4_generate },
	{ "PLAIN-MD5", PW_ENCODING_HEX, MD5_RESULTLEN,
	  NULL, plain_md5_generate },
	{ "LDAP-MD5", PW_ENCODING_BASE64, MD5_RESULTLEN,
	  NULL, plain_md5_generate },
	{ "LANMAN", PW_ENCODING_HEX, LM_HASH_SIZE, NULL, lm_generate },
	{ "NTLM", PW_ENCODING_HEX, NTLMSSP_HASH_SIZE, NULL, ntlm_generate },
	{ "OTP", PW_ENCODING_NONE, 0, otp_verify, otp_generate },
	{ "SKEY", PW_ENCODING_NONE, 0, otp_verify, skey_generate },
	{ "RPA", PW_ENCODING_HEX, MD5_RESULTLEN, NULL, rpa_generate },
        { "PBKDF2", PW_ENCODING_NONE, 0, pbkdf2_verify, pbkdf2_generate },
};

void password_scheme_register(const struct password_scheme *scheme)
{
	if (password_scheme_lookup_name(scheme->name) != NULL) {
		i_panic("password_scheme_register(%s): Already registered",
			scheme->name);
	}
	array_append(&password_schemes, &scheme, 1);
}

void password_scheme_unregister(const struct password_scheme *scheme)
{
	const struct password_scheme *const *schemes;
	unsigned int idx;

	array_foreach(&password_schemes, schemes) {
		if (strcasecmp((*schemes)->name, scheme->name) == 0) {
			idx = array_foreach_idx(&password_schemes, schemes);
			array_delete(&password_schemes, idx, 1);
			return;
		}
	}
	i_panic("password_scheme_unregister(%s): Not registered", scheme->name);
}

void password_schemes_init(void)
{
	unsigned int i;

	i_array_init(&password_schemes, N_ELEMENTS(builtin_schemes) + 4);
	for (i = 0; i < N_ELEMENTS(builtin_schemes); i++)
		password_scheme_register(&builtin_schemes[i]);
	password_scheme_register_crypt();
}

void password_schemes_deinit(void)
{
	array_free(&password_schemes);
}