/* 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$$[$] */ 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: */ 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: */ 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: */ 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: */ 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: - */ 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); }