summaryrefslogtreecommitdiffstats
path: root/src/auth/password-scheme.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth/password-scheme.c')
-rw-r--r--src/auth/password-scheme.c822
1 files changed, 822 insertions, 0 deletions
diff --git a/src/auth/password-scheme.c b/src/auth/password-scheme.c
new file mode 100644
index 0000000..c572cd3
--- /dev/null
+++ b/src/auth/password-scheme.c
@@ -0,0 +1,822 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "base64.h"
+#include "hex-binary.h"
+#include "md4.h"
+#include "md5.h"
+#include "hmac.h"
+#include "hmac-cram-md5.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";
+
+static HASH_TABLE(const char*, const struct password_scheme *) password_schemes;
+
+static const struct password_scheme *
+password_scheme_lookup_name(const char *name)
+{
+ return hash_table_lookup(password_schemes, name);
+}
+
+/* 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;
+
+ *encoding_r = PW_ENCODING_NONE;
+ if ((encoding = strchr(name, '.')) != NULL) {
+ name = t_strdup_until(name, encoding);
+ encoding++;
+ }
+
+ 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 struct password_generate_params *params,
+ 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, params, raw_password, size,
+ error_r);
+ } else {
+ /* generic verification handler: generate the password and
+ compare it to the one in database */
+ s->password_generate(plaintext, params,
+ &generated, &generated_size);
+ ret = size != generated_size ? 0 :
+ mem_equals_timing_safe(generated, raw_password, size) ? 1 : 0;
+ }
+
+ if (ret == 0)
+ *error_r = AUTH_LOG_MSG_PASSWORD_MISMATCH;
+ return ret;
+}
+
+const char *password_get_scheme(const char **password)
+{
+ const char *p, *scheme;
+
+ if (*password == NULL)
+ return NULL;
+
+ if (str_begins(*password, "$1$")) {
+ /* $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 = t_buffer_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;
+ }
+ /* check if it's base64-encoded after all. some input lengths
+ produce matching hex and base64 encoded lengths. */
+ /* fall through */
+ case PW_ENCODING_BASE64:
+ buf = t_buffer_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 struct password_generate_params *params,
+ 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, params, raw_password_r, size_r);
+ return TRUE;
+}
+
+bool password_generate_encoded(const char *plaintext, const struct password_generate_params *params,
+ 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, params, &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)
+{
+ char *salt;
+ salt = t_malloc_no0(len + 1);
+ for (size_t i = 0; i < len; i++)
+ salt[i] = salt_chars[i_rand_limit(sizeof(salt_chars) - 1)];
+ salt[len] = '\0';
+ return salt;
+}
+
+bool password_scheme_is_alias(const char *scheme1, const char *scheme2)
+{
+ const struct password_scheme *s1 = NULL, *s2 = NULL;
+
+ if (*scheme1 == '\0' || *scheme2 == '\0')
+ return FALSE;
+
+ scheme1 = t_strcut(scheme1, '.');
+ scheme2 = t_strcut(scheme2, '.');
+
+ if (strcasecmp(scheme1, scheme2) == 0)
+ return TRUE;
+
+ s1 = hash_table_lookup(password_schemes, scheme1);
+ s2 = hash_table_lookup(password_schemes, scheme2);
+
+ /* 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 struct password_generate_params *params)
+{
+ struct hash_iterate_context *ctx;
+ const char *key;
+ const struct password_scheme *scheme;
+ const unsigned char *raw_password;
+ size_t raw_password_size;
+ const char *error;
+
+ ctx = hash_table_iterate_init(password_schemes);
+ while (hash_table_iterate(ctx, password_schemes, &key, &scheme)) {
+ if (password_decode(crypted_password, scheme->name,
+ &raw_password, &raw_password_size,
+ &error) <= 0)
+ continue;
+
+ if (password_verify(plain_password, params, scheme->name,
+ raw_password, raw_password_size,
+ &error) > 0)
+ break;
+ key = NULL;
+ }
+ hash_table_iterate_deinit(&ctx);
+ return key;
+}
+
+int crypt_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r)
+{
+ const char *password, *crypted;
+
+ if (size > 4 && raw_password[0] == '$' && raw_password[1] == '2' &&
+ raw_password[3] == '$')
+ return password_verify(plaintext, params, "BLF-CRYPT",
+ raw_password, size, error_r);
+
+ 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 str_equals_timing_almost_safe(crypted, password) ? 1 : 0;
+}
+
+static int
+md5_verify(const char *plaintext, const struct password_generate_params *params,
+ 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 (str_begins(password, "$1$")) {
+ /* MD5-CRYPT */
+ str = password_generate_md5_crypt(plaintext, password);
+ return str_equals_timing_almost_safe(str, password) ? 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, params, "PLAIN-MD5",
+ md5_password, md5_size, error_r);
+ }
+}
+
+static int
+md5_crypt_verify(const char *plaintext, const struct password_generate_params *params 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 str_equals_timing_almost_safe(str, password) ? 1 : 0;
+}
+
+static void
+md5_crypt_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ const char *password;
+ const char *salt;
+
+ salt = password_generate_salt(8);
+
+ 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 struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(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 struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(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 struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(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 struct password_generate_params *params 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_no0(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 struct password_generate_params *params 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 mem_equals_timing_safe(sha1_digest, raw_password, SHA1_RESULTLEN) ? 1 : 0;
+}
+
+static void
+ssha256_generate(const char *plaintext, const struct password_generate_params *params 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_no0(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 struct password_generate_params *params 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 mem_equals_timing_safe(sha256_digest, raw_password,
+ SHA256_RESULTLEN) ? 1 : 0;
+}
+
+static void
+ssha512_generate(const char *plaintext, const struct password_generate_params *params 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_no0(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 struct password_generate_params *params 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 mem_equals_timing_safe(sha512_digest, raw_password,
+ SHA512_RESULTLEN) ? 1 : 0;
+}
+
+static void
+smd5_generate(const char *plaintext, const struct password_generate_params *params 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_no0(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 struct password_generate_params *params 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 mem_equals_timing_safe(md5_digest, raw_password, MD5_RESULTLEN) ? 1 : 0;
+}
+
+static void
+plain_generate(const char *plaintext, const struct password_generate_params *params 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_verify(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char *raw_password, size_t size,
+ const char **error_r ATTR_UNUSED)
+{
+ size_t plaintext_len = strlen(plaintext);
+
+ if (plaintext_len != size)
+ return 0;
+ return mem_equals_timing_safe(plaintext, raw_password, size) ? 1 : 0;
+}
+
+static int
+plain_trunc_verify(const char *plaintext, const struct password_generate_params *params 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 mem_equals_timing_safe(raw_password+i, plaintext, trunc_len) ? 1 : 0;
+ }
+ return plaintext_len == size-i &&
+ mem_equals_timing_safe(raw_password+i, plaintext, plaintext_len) ? 1 : 0;
+}
+
+static void
+cram_md5_generate(const char *plaintext, const struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ struct hmac_context ctx;
+ unsigned char *context_digest;
+
+ context_digest = t_malloc_no0(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 struct password_generate_params *params,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ const char *realm, *str, *user;
+ unsigned char *digest;
+
+ if (params->user == NULL)
+ i_fatal("digest_md5_generate(): username not given");
+
+ user = params->user;
+
+
+ /* 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_no0(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 struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(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 struct password_generate_params *params ATTR_UNUSED,
+ const unsigned char **raw_password_r, size_t *size_r)
+{
+ unsigned char *digest;
+
+ digest = t_malloc_no0(MD5_RESULTLEN);
+ md5_get_digest(plaintext, strlen(plaintext), digest);
+
+ *raw_password_r = digest;
+ *size_r = MD5_RESULTLEN;
+}
+
+static int otp_verify(const char *plaintext, const struct password_generate_params *params 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, UINT_MAX, &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 struct password_generate_params *params 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 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, plain_verify, plain_generate },
+ { "CLEAR", PW_ENCODING_NONE, 0, plain_verify, plain_generate },
+ { "CLEARTEXT", PW_ENCODING_NONE, 0, plain_verify, 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},
+ { "SCRAM-SHA-256", PW_ENCODING_NONE, 0, scram_sha256_verify,
+ scram_sha256_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 },
+ { "OTP", PW_ENCODING_NONE, 0, otp_verify, otp_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);
+ }
+ hash_table_insert(password_schemes, scheme->name, scheme);
+}
+
+void password_scheme_unregister(const struct password_scheme *scheme)
+{
+ if (!hash_table_try_remove(password_schemes, scheme->name))
+ i_panic("password_scheme_unregister(%s): Not registered", scheme->name);
+}
+
+void password_schemes_get(ARRAY_TYPE(password_scheme_p) *schemes_r)
+{
+ struct hash_iterate_context *ctx;
+ const char *key;
+ const struct password_scheme *scheme;
+ ctx = hash_table_iterate_init(password_schemes);
+ while(hash_table_iterate(ctx, password_schemes, &key, &scheme)) {
+ array_push_back(schemes_r, &scheme);
+ }
+ hash_table_iterate_deinit(&ctx);
+}
+
+void password_schemes_init(void)
+{
+ unsigned int i;
+
+ hash_table_create(&password_schemes, default_pool,
+ N_ELEMENTS(builtin_schemes)*2, strfastcase_hash,
+ strcasecmp);
+ for (i = 0; i < N_ELEMENTS(builtin_schemes); i++)
+ password_scheme_register(&builtin_schemes[i]);
+ password_scheme_register_crypt();
+#ifdef HAVE_LIBSODIUM
+ password_scheme_register_sodium();
+#endif
+}
+
+void password_schemes_deinit(void)
+{
+ hash_table_destroy(&password_schemes);
+}