password-scheme.c revision 9c4d433f42e4d04aca2375c112a57826cd85dfed
76b43e4417bab52e913da39b5f5bc2a130d3f149Timo Sirainen/* Copyright (c) 2003-2007 Dovecot authors, see the included COPYING file */
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "lib.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "array.h"
2fbc2a7c65d30e46803195ebb4547176b85c22c7Timo Sirainen#include "base64.h"
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen#include "hex-binary.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "md4.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "md5.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "hmac-md5.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "ntlm.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "mycrypt.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "randgen.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "sha1.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "sha2.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "otp.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "str.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "password-scheme.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainenstatic const char salt_chars[] =
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo SirainenARRAY_TYPE(password_scheme_p) password_schemes;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainenstatic const struct password_scheme *
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainenpassword_scheme_lookup_name(const char *name)
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen{
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen const struct password_scheme *const *schemes;
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen unsigned int i, count;
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen schemes = array_get(&password_schemes, &count);
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen for (i = 0; i < count; i++) {
356303df200c991580bd24041996a070ad08c05eTimo Sirainen if (strcasecmp(schemes[i]->name, name) == 0)
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen return schemes[i];
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen }
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen return NULL;
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen}
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen/* Lookup scheme and encoding by given name. The encoding is taken from
d9b73f3eb3d5f608dfd438cee89ce2b27547173fTimo Sirainen ".base64", ".b64" or ".hex" suffix if it exists, otherwise the default
00bde9ae9eab9e720462bf6ec9a4dd85e88c3bbfTimo Sirainen encoding is used. */
00bde9ae9eab9e720462bf6ec9a4dd85e88c3bbfTimo Sirainenstatic const struct password_scheme *
00bde9ae9eab9e720462bf6ec9a4dd85e88c3bbfTimo Sirainenpassword_scheme_lookup(const char *name, enum password_encoding *encoding_r)
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen{
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen const struct password_scheme *scheme;
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen const char *encoding = NULL;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen unsigned int scheme_len;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen *encoding_r = PW_ENCODING_NONE;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen for (scheme_len = 0; name[scheme_len] != '\0'; scheme_len++) {
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen if (name[scheme_len] == '.') {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen encoding = name + scheme_len + 1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen name = t_strndup(name, scheme_len);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen break;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen }
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen }
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen scheme = password_scheme_lookup_name(name);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen if (scheme == NULL)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen return NULL;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen if (encoding == NULL)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen *encoding_r = scheme->default_encoding;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen else if (strcasecmp(encoding, "b64") == 0 ||
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen strcasecmp(encoding, "base64") == 0)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen *encoding_r = PW_ENCODING_BASE64;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen else if (strcasecmp(encoding, "hex") == 0)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen *encoding_r = PW_ENCODING_HEX;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen else {
3b94ff5951db4d4eddb7a80ed4e3f61207202635Timo Sirainen /* unknown encoding. treat as invalid scheme. */
66d2db642fe24d555d113ba463e446b038d476efTimo Sirainen return NULL;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen return scheme;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen}
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainenint password_verify(const char *plaintext, const char *user, const char *scheme,
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen const unsigned char *raw_password, size_t size)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen{
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen const struct password_scheme *s;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen enum password_encoding encoding;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen const unsigned char *generated;
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen size_t generated_size;
22535a9e685e29214082878e37a267157044618eTimo Sirainen
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen s = password_scheme_lookup(scheme, &encoding);
22535a9e685e29214082878e37a267157044618eTimo Sirainen if (s == NULL)
22535a9e685e29214082878e37a267157044618eTimo Sirainen return -1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (s->password_verify != NULL)
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return s->password_verify(plaintext, user, raw_password, size);
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
356303df200c991580bd24041996a070ad08c05eTimo Sirainen /* generic verification handler: generate the password and compare it
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen to the one in database */
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen s->password_generate(plaintext, user, &generated, &generated_size);
22535a9e685e29214082878e37a267157044618eTimo Sirainen return size != generated_size ? 0 :
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen memcmp(generated, raw_password, size) == 0;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen}
e15f1d736c225c7ce6f3d08a37c1b2ae66b57c50Timo Sirainen
e15f1d736c225c7ce6f3d08a37c1b2ae66b57c50Timo Sirainenconst char *password_get_scheme(const char **password)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen{
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen const char *p, *scheme;
e15f1d736c225c7ce6f3d08a37c1b2ae66b57c50Timo Sirainen
e15f1d736c225c7ce6f3d08a37c1b2ae66b57c50Timo Sirainen if (*password == NULL)
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return NULL;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
356303df200c991580bd24041996a070ad08c05eTimo Sirainen if (strncmp(*password, "$1$", 3) == 0) {
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen /* $1$<salt>$<password>[$<ignored>] */
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen p = strchr(*password + 3, '$');
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (p != NULL) {
356303df200c991580bd24041996a070ad08c05eTimo Sirainen /* stop at next '$' after password */
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen p = strchr(p+1, '$');
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen if (p != NULL)
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen *password = t_strdup_until(*password, p);
22535a9e685e29214082878e37a267157044618eTimo Sirainen return "MD5-CRYPT";
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (**password != '{')
22535a9e685e29214082878e37a267157044618eTimo Sirainen return NULL;
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen
22535a9e685e29214082878e37a267157044618eTimo Sirainen p = strchr(*password, '}');
22535a9e685e29214082878e37a267157044618eTimo Sirainen if (p == NULL)
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return NULL;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen scheme = t_strdup_until(*password + 1, p);
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen *password = p + 1;
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen return scheme;
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen}
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainenint password_decode(const char *password, const char *scheme,
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen const unsigned char **raw_password_r, size_t *size_r)
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen{
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen const struct password_scheme *s;
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen enum password_encoding encoding;
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen buffer_t *buf;
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen unsigned int len;
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen s = password_scheme_lookup(scheme, &encoding);
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (s == NULL)
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen return 0;
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen len = strlen(password);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen if (encoding != PW_ENCODING_NONE && s->raw_password_len != 0 &&
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen strchr(scheme, '.') == NULL) {
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen /* encoding not specified. we can autodetect between
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen base64 and hex encodings. */
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen encoding = len == s->raw_password_len * 2 ? PW_ENCODING_HEX :
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen PW_ENCODING_BASE64;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen }
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen switch (encoding) {
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen case PW_ENCODING_NONE:
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen *raw_password_r = (const unsigned char *)password;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen *size_r = len;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen break;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen case PW_ENCODING_BASE64:
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen buf = buffer_create_static_hard(pool_datastack_create(),
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen MAX_BASE64_DECODED_SIZE(len));
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen if (base64_decode(password, len, NULL, buf) < 0)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen return -1;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen *raw_password_r = buf->data;
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen *size_r = buf->used;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen break;
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen case PW_ENCODING_HEX:
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen buf = buffer_create_static_hard(pool_datastack_create(),
b42f37ae6f65ed986315b6885568d32115e589b1Timo Sirainen len / 2 + 1);
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen if (hex_to_binary(password, buf) < 0)
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen return -1;
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen *raw_password_r = buf->data;
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen *size_r = buf->used;
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen break;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (s->raw_password_len != *size_r && s->raw_password_len != 0) {
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen /* password has invalid length */
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen return -1;
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen }
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen return 1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen}
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;
}
bool password_scheme_is_alias(const char *scheme1, const char *scheme2)
{
const struct password_scheme *const *schemes, *s1 = NULL, *s2 = NULL;
unsigned int i, count;
scheme1 = t_strcut(scheme1, '.');
scheme2 = t_strcut(scheme2, '.');
if (strcasecmp(scheme1, scheme2) == 0)
return TRUE;
schemes = array_get(&password_schemes, &count);
for (i = 0; i < count; i++) {
if (strcasecmp(schemes[i]->name, scheme1) == 0)
s1 = schemes[i];
else if (strcasecmp(schemes[i]->name, scheme2) == 0)
s2 = schemes[i];
}
/* if they've the same generate function, they're equivalent */
return s1 != NULL && s2 != NULL &&
s1->password_generate == s2->password_generate;
}
static bool
crypt_verify(const char *plaintext, const char *user ATTR_UNUSED,
const unsigned char *raw_password, size_t size)
{
const char *password;
if (size == 0) {
/* the default mycrypt() handler would return match */
return FALSE;
}
password = t_strndup(raw_password, size);
return strcmp(mycrypt(plaintext, password), password) == 0;
}
static void
crypt_generate(const char *plaintext, const char *user ATTR_UNUSED,
const unsigned char **raw_password_r, size_t *size_r)
{
char salt[3];
const char *password;
random_fill(salt, sizeof(salt)-1);
salt[0] = salt_chars[salt[0] % (sizeof(salt_chars)-1)];
salt[1] = salt_chars[salt[1] % (sizeof(salt_chars)-1)];
salt[2] = '\0';
password = t_strdup(mycrypt(plaintext, salt));
*raw_password_r = (const unsigned char *)password;
*size_r = strlen(password);
}
static bool
md5_crypt_verify(const char *plaintext, const char *user ATTR_UNUSED,
const unsigned char *raw_password, size_t size)
{
const char *password, *str;
password = t_strndup(raw_password, size);
str = password_generate_md5_crypt(plaintext, password);
return strcmp(str, password) == 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
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 bool ssha_verify(const char *plaintext, const char *user,
const unsigned char *raw_password, size_t size)
{
unsigned char sha1_digest[SHA1_RESULTLEN];
struct sha1_ctxt ctx;
/* format: <SHA1 hash><salt> */
if (size <= SHA1_RESULTLEN) {
i_error("ssha_verify(%s): SSHA password too short", user);
return FALSE;
}
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;
}
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 bool smd5_verify(const char *plaintext, const char *user,
const unsigned char *raw_password, size_t size)
{
unsigned char md5_digest[MD5_RESULTLEN];
struct md5_context ctx;
/* format: <MD5 hash><salt> */
if (size <= MD5_RESULTLEN) {
i_error("smd5_verify(%s): SMD5 password too short", user);
return FALSE;
}
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;
}
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 void
cram_md5_generate(const char *plaintext, const char *user ATTR_UNUSED,
const unsigned char **raw_password_r, size_t *size_r)
{
struct hmac_md5_context ctx;
unsigned char *context_digest;
context_digest = t_malloc(CRAM_MD5_CONTEXTLEN);
hmac_md5_init(&ctx, (const unsigned char *)plaintext,
strlen(plaintext));
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");
/* user:realm:passwd */
realm = strchr(user, '@');
if (realm != NULL) realm++; else realm = "";
digest = t_malloc(MD5_RESULTLEN);
str = t_strdup_printf("%s:%s:%s", t_strcut(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 bool otp_verify(const char *plaintext, const char *user ATTR_UNUSED,
const unsigned char *raw_password, size_t size)
{
const char *password;
password = t_strndup(raw_password, size);
return strcasecmp(password,
password_generate_otp(plaintext, password, -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;
password = password_generate_otp(plaintext, NULL, OTP_HASH_SHA1);
*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;
password = password_generate_otp(plaintext, NULL, OTP_HASH_MD4);
*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[] = {
{ "CRYPT", PW_ENCODING_NONE, 0, crypt_verify, crypt_generate },
{ "MD5", PW_ENCODING_NONE, 0, md5_crypt_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 },
{ "SMD5", PW_ENCODING_BASE64, 0, smd5_verify, smd5_generate },
{ "SSHA", PW_ENCODING_BASE64, 0, ssha_verify, ssha_generate },
{ "PLAIN", PW_ENCODING_NONE, 0, NULL, plain_generate },
{ "CLEARTEXT", PW_ENCODING_NONE, 0, NULL, plain_generate },
{ "CRAM-MD5", PW_ENCODING_HEX, 0, NULL, cram_md5_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 }
};
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 i, count;
schemes = array_get(&password_schemes, &count);
for (i = 0; i < count; i++) {
if (strcasecmp(schemes[i]->name, scheme->name) == 0) {
array_delete(&password_schemes, i, 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]);
}
void password_schemes_deinit(void)
{
array_free(&password_schemes);
}