]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Various fixes in password.c, rlm_pap and rlm_chap
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 29 Aug 2019 01:37:08 +0000 (21:37 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 29 Aug 2019 01:37:57 +0000 (21:37 -0400)
- Move password attributes under Password-Root
- Apply tighter bounds checks on hash length in password.c
- Failover between "known good" password types in password.c if one type can't be normalised or is the wrong length.
- Merge password info arrays into one table in password.c
- Provide two public functions in password.c, one provides a cannonical version of a password attribute, the other rewrites all the password attributes in the request->control list to their canonical form.
- s/(S?)SHA-Password/\1SHA1-Password/
- Support multiple levels of preprocessing callbacks
- Support restricting password types password.c can return
- Use Password-Root TLV to be more efficient about locating candidate "known good" passwords (should be MUCH faster, and no longer quadratic).
- Use proper hash length macros in the hash info table in password.c
- Split SHA2-Password and SHA3-Password values into SHA[23]-(224|256|348|512)-Password attributes automatically.
- Provide macro wrappers to allow us to easily add additional EVP based hashing mechanisms.
- Fix pap_auth_func signature so known_good is also constified.
- Prevent normalisation of Cleartext-Password, that's probably always the wrong thing to do.

share/dictionary/freeradius/dictionary.freeradius.internal.password
src/lib/server/password.c
src/lib/server/password.h
src/modules/rlm_chap/rlm_chap.c
src/modules/rlm_pap/rlm_pap.c
src/tests/keywords/pap-ssha2

index 68f0db540ac4f11022c52816f639d5ee81c7cbae..35a3712634948df94d1c3c2b0ac9ff5284e266cc 100644 (file)
 #
 FLAGS  internal
 
-#
-#      Password with header *MUST* always be first, and all attributes that
-#      come after it should use consecutive numbers.
-#
-#      If you change this, the server will refuse to compile.
-#
-ATTRIBUTE      Password-With-Header                    5000    string
-ATTRIBUTE      Cleartext-Password                      5001    string
-ATTRIBUTE      Crypt-Password                          5002    string
-ATTRIBUTE      LM-Password                             5003    octets
-ATTRIBUTE      MD5-Password                            5004    octets
-ATTRIBUTE      MS-CHAP-Password                        5005    string
-ATTRIBUTE      NS-MTA-MD5-Password                     5006    string
-ATTRIBUTE      NT-Password                             5007    octets
-ATTRIBUTE      PBKDF2-Password                         5008    octets
-ATTRIBUTE      SHA-Password                            5009    octets
-ATTRIBUTE      SHA1-Password                           5010    octets
-ATTRIBUTE      SHA2-Password                           5011    octets
-ATTRIBUTE      SHA3-Password                           5012    octets
-ATTRIBUTE      SMD5-Password                           5013    octets
-ATTRIBUTE      SSHA-Password                           5014    octets
-ATTRIBUTE      SSHA1-Password                          5015    octets
-ATTRIBUTE      SSHA2-224-Password                      5016    octets
-ATTRIBUTE      SSHA2-256-Password                      5017    octets
-ATTRIBUTE      SSHA2-384-Password                      5018    octets
-ATTRIBUTE      SSHA2-512-Password                      5019    octets
-ATTRIBUTE      SSHA3-224-Password                      5020    octets
-ATTRIBUTE      SSHA3-256-Password                      5021    octets
-ATTRIBUTE      SSHA3-384-Password                      5022    octets
-ATTRIBUTE      SSHA3-512-Password                      5023    octets
+ATTRIBUTE      Password-Root                           2004    tlv
+BEGIN-TLV      Password-Root
+ATTRIBUTE      Password-With-Header                    1       string
+
+ATTRIBUTE      Cleartext-Password                      2       string
+
+ATTRIBUTE      Crypt-Password                          3       string
+
+ATTRIBUTE      LM-Password                             4       octets
+
+ATTRIBUTE      MD5-Password                            5       octets
+
+ATTRIBUTE      MS-CHAP-Password                        6       string
+
+ATTRIBUTE      NS-MTA-MD5-Password                     7       string
+
+ATTRIBUTE      NT-Password                             8       octets
+
+ATTRIBUTE      PBKDF2-Password                         9       octets
+
+ATTRIBUTE      SHA1-Password                           10      octets
+
+ATTRIBUTE      SHA2-Password                           11      octets
+ATTRIBUTE      SHA2-224-Password                       12      octets
+ATTRIBUTE      SHA2-256-Password                       13      octets
+ATTRIBUTE      SHA2-384-Password                       14      octets
+ATTRIBUTE      SHA2-512-Password                       15      octets
+
+ATTRIBUTE      SHA3-Password                           16      octets
+ATTRIBUTE      SHA3-224-Password                       17      octets
+ATTRIBUTE      SHA3-256-Password                       18      octets
+ATTRIBUTE      SHA3-384-Password                       19      octets
+ATTRIBUTE      SHA3-512-Password                       20      octets
+
+ATTRIBUTE      SMD5-Password                           21      octets
+
+ATTRIBUTE      SSHA1-Password                          22      octets
+
+ATTRIBUTE      SSHA2-224-Password                      23      octets
+ATTRIBUTE      SSHA2-256-Password                      24      octets
+ATTRIBUTE      SSHA2-384-Password                      25      octets
+ATTRIBUTE      SSHA2-512-Password                      26      octets
+
+ATTRIBUTE      SSHA3-224-Password                      27      octets
+ATTRIBUTE      SSHA3-256-Password                      28      octets
+ATTRIBUTE      SSHA3-384-Password                      29      octets
+ATTRIBUTE      SSHA3-512-Password                      30      octets
+
+END-TLV                Password-Root
index 18b497d1d554a8a3f951d222af3332d2cadc646c..a6ed3e5fb04c37dc9bed125a651e57ec1275cbc8 100644 (file)
@@ -25,34 +25,80 @@ RCSID("$Id$")
 
 #include <freeradius-devel/server/password.h>
 
-#include <freeradius-devel/util/value.h>
-#include <freeradius-devel/util/misc.h>
 #include <freeradius-devel/util/base64.h>
+#include <freeradius-devel/util/md4.h>
+#include <freeradius-devel/util/md5.h>
+#include <freeradius-devel/util/misc.h>
+#include <freeradius-devel/util/sha1.h>
+#include <freeradius-devel/util/value.h>
 
 #include <freeradius-devel/protocol/freeradius/freeradius.internal.password.h>
 
 #ifdef HAVE_OPENSSL_EVP_H
 #  include <openssl/evp.h>
+#  include <openssl/sha.h>
 #endif
 
+typedef enum {
+       PASSWORD_CLEARTEXT = 0,                 //!< Variable length.
+       PASSWORD_HASH,                          //!< Fixed lenth.
+       PASSWORD_HASH_SALTED,                   //!< Fixed length hash, variable length salt.
+       PASSWORD_HASH_VARIABLE                  //!< Variable length everything.
+} password_type_t;
+
+/** Apply preprocessing logic to a password value
+ *
+ * @param[in] ctx      to allocate returned value in.
+ * @
+ */
+typedef VALUE_PAIR *(*password_preprocess_t)(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *in);
+
+/** Password information
+ *
+ */
+typedef struct {
+       password_type_t         type;           //!< What type of password value this is.
+       fr_dict_attr_t const    **da;           //!< Dictionary attribute representing this type of password.
+       password_preprocess_t   func;           //!< Preprocessing function.
+       size_t                  min_hash_len;   //!< Minimum length of the decoded string if normifying.
+                                               ///< If 0, will be ignored.
+       size_t                  max_hash_len;   //!< Maximum length of the decoded string if normifying.
+                                               ///< If 0, will be ignored.
+       bool                    no_normify;     //!< Don't attempt to normalise the contents of this
+                                               ///< attribute using the hex/base64 decoders.
+} password_info_t;
+
 static fr_dict_t *dict_freeradius = NULL;
+static fr_dict_t *dict_radius = NULL;
 
 static fr_dict_attr_t const *attr_cleartext_password;
 static fr_dict_attr_t const *attr_password_with_header;
+static fr_dict_attr_t const *attr_password_root;
 
 static fr_dict_attr_t const *attr_md5_password;
 static fr_dict_attr_t const *attr_smd5_password;
 static fr_dict_attr_t const *attr_crypt_password;
-static fr_dict_attr_t const *attr_sha_password;
-static fr_dict_attr_t const *attr_ssha_password;
+
+static fr_dict_attr_t const *attr_sha1_password;
+static fr_dict_attr_t const *attr_ssha1_password;
 
 static fr_dict_attr_t const *attr_sha2_password;
+static fr_dict_attr_t const *attr_sha2_224_password;
+static fr_dict_attr_t const *attr_sha2_256_password;
+static fr_dict_attr_t const *attr_sha2_384_password;
+static fr_dict_attr_t const *attr_sha2_512_password;
+
 static fr_dict_attr_t const *attr_ssha2_224_password;
 static fr_dict_attr_t const *attr_ssha2_256_password;
 static fr_dict_attr_t const *attr_ssha2_384_password;
 static fr_dict_attr_t const *attr_ssha2_512_password;
 
 static fr_dict_attr_t const *attr_sha3_password;
+static fr_dict_attr_t const *attr_sha3_224_password;
+static fr_dict_attr_t const *attr_sha3_256_password;
+static fr_dict_attr_t const *attr_sha3_384_password;
+static fr_dict_attr_t const *attr_sha3_512_password;
+
 static fr_dict_attr_t const *attr_ssha3_224_password;
 static fr_dict_attr_t const *attr_ssha3_256_password;
 static fr_dict_attr_t const *attr_ssha3_384_password;
@@ -63,9 +109,12 @@ static fr_dict_attr_t const *attr_lm_password;
 static fr_dict_attr_t const *attr_nt_password;
 static fr_dict_attr_t const *attr_ns_mta_md5_password;
 
+static fr_dict_attr_t const *attr_user_password;
+
 extern fr_dict_autoload_t password_dict[];
 fr_dict_autoload_t password_dict[] = {
        { .out = &dict_freeradius, .proto = "freeradius" },
+       { .out = &dict_radius, .proto = "radius" },
        { NULL }
 };
 
@@ -73,20 +122,31 @@ extern fr_dict_attr_autoload_t password_dict_attr[];
 fr_dict_attr_autoload_t password_dict_attr[] = {
        { .out = &attr_cleartext_password, .name = "Cleartext-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
        { .out = &attr_password_with_header, .name = "Password-With-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
+       { .out = &attr_password_root, .name = "Password-Root", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
 
        { .out = &attr_md5_password, .name = "MD5-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_smd5_password, .name = "SMD5-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_crypt_password, .name = "Crypt-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
-       { .out = &attr_sha_password, .name = "SHA-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha_password, .name = "SSHA-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha1_password, .name = "SHA1-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_ssha1_password, .name = "SSHA1-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
 
        { .out = &attr_sha2_password, .name = "SHA2-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha2_224_password, .name = "SHA2-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha2_256_password, .name = "SHA2-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha2_384_password, .name = "SHA2-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha2_512_password, .name = "SHA2-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+
        { .out = &attr_ssha2_224_password, .name = "SSHA2-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_ssha2_256_password, .name = "SSHA2-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_ssha2_384_password, .name = "SSHA2-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_ssha2_512_password, .name = "SSHA2-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
 
        { .out = &attr_sha3_password, .name = "SHA3-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha3_224_password, .name = "SHA3-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha3_256_password, .name = "SHA3-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha3_384_password, .name = "SHA3-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+       { .out = &attr_sha3_512_password, .name = "SHA3-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
+
        { .out = &attr_ssha3_224_password, .name = "SSHA3-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_ssha3_256_password, .name = "SSHA3-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_ssha3_384_password, .name = "SSHA3-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
@@ -97,10 +157,11 @@ fr_dict_attr_autoload_t password_dict_attr[] = {
        { .out = &attr_nt_password, .name = "NT-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
        { .out = &attr_ns_mta_md5_password, .name = "NS-MTA-MD5-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
 
+       { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius },
+
        { NULL }
 };
 
-
 typedef enum {
        NORMALISED_NOTHING = 0,
        NORMALISED_B64,
@@ -114,9 +175,228 @@ static fr_table_num_sorted_t const normalise_table[] = {
 };
 static size_t normalise_table_len = NUM_ELEMENTS(normalise_table);
 
+/*
+ *     Headers for the Password-with-Header attribute
+ *
+ *     @note Header comparison is case insensitive.
+ */
+static fr_table_num_sorted_t const password_header_table[] = {
+       { "{base64_md5}",       FR_MD5_PASSWORD },
+       { "{clear}",            FR_CLEARTEXT_PASSWORD },
+       { "{cleartext}",        FR_CLEARTEXT_PASSWORD },
+       { "{crypt}",            FR_CRYPT_PASSWORD },
+       { "{md4}",              FR_NT_PASSWORD },
+       { "{md5}",              FR_MD5_PASSWORD },
+       { "{ns-mta-md5}",       FR_NS_MTA_MD5_PASSWORD },
+       { "{nt}",               FR_NT_PASSWORD },
+       { "{nthash}",           FR_NT_PASSWORD },
+
+#ifdef HAVE_OPENSSL_EVP_H
+       { "{sha224}",           FR_SHA2_PASSWORD },
+       { "{sha256}",           FR_SHA2_PASSWORD },
+       { "{sha2}",             FR_SHA2_PASSWORD },
+       { "{sha384}",           FR_SHA2_384_PASSWORD },
+       { "{sha512}",           FR_SHA2_512_PASSWORD },
+#endif
+       { "{sha}",              FR_SHA1_PASSWORD },
+       { "{smd5}",             FR_SMD5_PASSWORD },
+#ifdef HAVE_OPENSSL_EVP_H
+       { "{ssha224}",          FR_SSHA2_224_PASSWORD },
+       { "{ssha256}",          FR_SSHA2_256_PASSWORD },
+#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
+       { "{ssha3-224}",        FR_SSHA3_224_PASSWORD },
+       { "{ssha3-256}",        FR_SSHA3_256_PASSWORD },
+       { "{ssha3-384}",        FR_SSHA3_384_PASSWORD },
+       { "{ssha3-512}",        FR_SSHA3_512_PASSWORD },
+#  endif
+       { "{ssha384}",          FR_SSHA2_384_PASSWORD },
+       { "{ssha512}",          FR_SSHA2_512_PASSWORD },
+#endif
+       { "{ssha}",             FR_SSHA1_PASSWORD },
+       { "{x- orcllmv}",       FR_LM_PASSWORD },
+       { "{x- orclntv}",       FR_NT_PASSWORD },
+       { "{x-nthash}",         FR_NT_PASSWORD },
+       { "{x-pbkdf2}",         FR_PBKDF2_PASSWORD },
+};
+static size_t password_header_table_len = NUM_ELEMENTS(password_header_table);
+
+#ifdef HAVE_OPENSSL_EVP_H
+static VALUE_PAIR *password_process_sha2(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good);
+#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
+static VALUE_PAIR *password_process_sha3(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good);
+#  endif
+#endif
+static VALUE_PAIR *password_process_header(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good);
+
+/** Metdata for various password attributes
+ *
+ */
+static password_info_t password_info[] = {
+       [FR_CLEARTEXT_PASSWORD]         = {
+                                               .type = PASSWORD_CLEARTEXT,
+                                               .da = &attr_cleartext_password,
+                                               .no_normify = true
+                                       },
+       [FR_CRYPT_PASSWORD]             = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_crypt_password
+                                       },
+       [FR_LM_PASSWORD]                = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_lm_password,
+                                               .min_hash_len = MD4_DIGEST_LENGTH
+                                       },
+       [FR_MD5_PASSWORD]               = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_md5_password,
+                                               .min_hash_len = MD5_DIGEST_LENGTH
+                                       },
+       [FR_NS_MTA_MD5_PASSWORD]        = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_ns_mta_md5_password
+                                       },
+       [FR_NT_PASSWORD]                = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_nt_password,
+                                               .min_hash_len = MD4_DIGEST_LENGTH
+                                       },
+       [FR_PASSWORD_WITH_HEADER]       = {
+                                               .type = PASSWORD_HASH_VARIABLE,
+                                               .da = &attr_password_with_header,
+                                               .func = password_process_header
+                                       },
+       [FR_PBKDF2_PASSWORD]            = {
+                                               .type = PASSWORD_HASH_VARIABLE,
+                                               .da = &attr_pbkdf2_password
+                                       },
+       [FR_SHA1_PASSWORD]              = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha1_password,
+                                               .min_hash_len = SHA1_DIGEST_LENGTH
+                                       },
+#ifdef HAVE_OPENSSL_EVP_H
+       [FR_SHA2_PASSWORD]              = {
+                                               .type = PASSWORD_HASH_VARIABLE,
+                                               .da = &attr_sha2_password,
+                                               .func = password_process_sha2,
+                                               .min_hash_len = SHA224_DIGEST_LENGTH,
+                                               .max_hash_len = SHA512_DIGEST_LENGTH
+                                       },
+       [FR_SHA2_224_PASSWORD]          = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha2_224_password,
+                                               .min_hash_len = SHA224_DIGEST_LENGTH,
+                                       },
+       [FR_SHA2_256_PASSWORD]          = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha2_256_password,
+                                               .min_hash_len = SHA256_DIGEST_LENGTH,
+                                       },
+       [FR_SHA2_384_PASSWORD]          = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha2_384_password,
+                                               .min_hash_len = SHA384_DIGEST_LENGTH,
+                                       },
+       [FR_SHA2_512_PASSWORD]          = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha2_512_password,
+                                               .min_hash_len = SHA512_DIGEST_LENGTH,
+                                       },
+#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
+       [FR_SHA3_PASSWORD]              = {
+                                               .type = PASSWORD_HASH_VARIABLE,
+                                               .da = &attr_sha3_password,
+                                               .func = password_process_sha3,
+                                               .min_hash_len = SHA224_DIGEST_LENGTH,
+                                       },
+       [FR_SHA3_224_PASSWORD]          = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha3_224_password,
+                                               .min_hash_len = SHA224_DIGEST_LENGTH,
+                                       },
+       [FR_SHA3_256_PASSWORD]          = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha3_256_password,
+                                               .min_hash_len = SHA256_DIGEST_LENGTH,
+                                       },
+       [FR_SHA3_384_PASSWORD]          = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha3_384_password,
+                                               .min_hash_len = SHA384_DIGEST_LENGTH,
+                                       },
+       [FR_SHA3_512_PASSWORD]          = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_sha3_512_password,
+                                               .min_hash_len = SHA512_DIGEST_LENGTH
+                                       },
+#  endif
+#endif
+       [FR_SMD5_PASSWORD]              = {
+                                               .type = PASSWORD_HASH,
+                                               .da = &attr_smd5_password,
+                                               .min_hash_len = MD5_DIGEST_LENGTH
+                                       },
+       [FR_SSHA1_PASSWORD]             = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha1_password,
+                                               .min_hash_len = SHA1_DIGEST_LENGTH
+                                       },
+#ifdef HAVE_OPENSSL_EVP_H
+       [FR_SSHA2_224_PASSWORD]         = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha2_224_password,
+                                               .min_hash_len = SHA224_DIGEST_LENGTH
+                                       },
+       [FR_SSHA2_256_PASSWORD]         = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha2_256_password,
+                                               .min_hash_len = SHA256_DIGEST_LENGTH
+                                       },
+       [FR_SSHA2_384_PASSWORD]         = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha2_384_password,
+                                               .min_hash_len = SHA384_DIGEST_LENGTH
+                                       },
+       [FR_SSHA2_512_PASSWORD]         = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha2_512_password,
+                                               .min_hash_len = SHA512_DIGEST_LENGTH
+                                       },
+#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
+       [FR_SSHA3_224_PASSWORD]         = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha3_224_password,
+                                               .min_hash_len = SHA224_DIGEST_LENGTH,
+                                       },
+       [FR_SSHA3_256_PASSWORD]         = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha3_256_password,
+                                               .min_hash_len = SHA256_DIGEST_LENGTH
+                                       },
+       [FR_SSHA3_384_PASSWORD]         = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha3_384_password,
+                                               .min_hash_len = SHA384_DIGEST_LENGTH
+                                       },
+       [FR_SSHA3_512_PASSWORD]         = {
+                                               .type = PASSWORD_HASH_SALTED,
+                                               .da = &attr_ssha3_512_password,
+                                               .min_hash_len = SHA512_DIGEST_LENGTH
+                                       }
+#  endif
+#endif
+};
+
+#define MIN_LEN(_info) (info->type == PASSWORD_HASH_SALTED ? (info->min_hash_len + 1) : info->min_hash_len)
+
 static ssize_t normify(normalise_t *action, uint8_t *buffer, size_t bufflen,
                       char const *known_good, size_t len, size_t min_len)
 {
+       /*
+        *      Else unknown encoding, or already binary.  Leave it.
+        */
+       if (action) *action = NORMALISED_NOTHING;
+
        if (min_len >= bufflen) return 0; /* paranoia */
 
        /*
@@ -148,10 +428,6 @@ static ssize_t normify(normalise_t *action, uint8_t *buffer, size_t bufflen,
                }
        }
 
-       /*
-        *      Else unknown encoding, or already binary.  Leave it.
-        */
-       if (action) *action = NORMALISED_NOTHING;
        return 0;
 }
 
@@ -170,18 +446,23 @@ static ssize_t normify(normalise_t *action, uint8_t *buffer, size_t bufflen,
  * @param[in] ctx              to allocate new pairs in.
  * @param[in] request          The current request.
  * @param[in] known_good       password to normify.
- * @param[in] min_len          we expect the decoded version to be.
  * @return
- *     - NULL if known_good was already normalised.
+ *     - NULL if known_good was already normalised, or couldn't be normalised.
  *     - A new normalised password pair.
  */
-VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const *known_good, size_t min_len)
+static VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const *known_good)
 {
        uint8_t                 buffer[256];
        ssize_t                 decoded;
        VALUE_PAIR              *out;
-       normalise_t             normalised = NORMALISED_NOTHING;
+       normalise_t             normalised;
+       password_info_t         *info;
+       size_t                  min_len;
+
+       if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL;
 
+       info = &password_info[known_good->da->attr];
+       min_len = MIN_LEN(info);
        if (min_len >= sizeof(buffer)) return NULL; /* paranoia */
 
        switch (known_good->da->type) {
@@ -205,6 +486,7 @@ VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const
                        known_good->vp_length, decoded);
                MEM(out = fr_pair_afrom_da(ctx, known_good->da));
                fr_pair_value_memcpy(out, buffer, decoded, known_good->vp_tainted);
+               return out;
        }
 
        /*
@@ -213,38 +495,127 @@ VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const
        return NULL;
 }
 
+#ifdef HAVE_OPENSSL_EVP_H
+/** Split SHA2 hashes into separate attributes based on their length
+ *
+ * @param[in] ctx              to allocate attributes in.
+ * @param[in] request          The current request.
+ * @param[in] known_good       attribute to split.
+ * @return
+ *     - A SHA2 length specific attribute.
+ *     - NULL on error.
+ */
+static VALUE_PAIR *password_process_sha2(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good)
+{
+       VALUE_PAIR      *out, *normalised;
+
+       switch (known_good->vp_length) {
+       case SHA224_DIGEST_LENGTH:
+               out = fr_pair_afrom_da(ctx, attr_sha2_224_password);
+               fr_pair_value_copy(out, known_good);
+               return out;
+
+       case SHA256_DIGEST_LENGTH:
+               out = fr_pair_afrom_da(ctx, attr_sha2_256_password);
+               fr_pair_value_copy(out, known_good);
+               return out;
+
+       case SHA384_DIGEST_LENGTH:
+               out = fr_pair_afrom_da(ctx, attr_sha2_384_password);
+               fr_pair_value_copy(out, known_good);
+               return out;
+
+       case SHA512_DIGEST_LENGTH:
+               out = fr_pair_afrom_da(ctx, attr_sha2_512_password);
+               fr_pair_value_copy(out, known_good);
+               return out;
+
+       default:
+               out = password_normify(ctx, request, known_good);
+               if (!out) return NULL;
+
+               normalised = password_process_sha2(ctx, request, out);
+               talloc_list_free(&out);
+
+               return normalised;
+       }
+}
+
+#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
+/** Split SHA3 hashes into separate attributes based on their length
+ *
+ * @param[in] ctx              to allocate attributes in.
+ * @param[in] request          The current request.
+ * @param[in] known_good       attribute to split.
+ * @return
+ *     - A SHA3 length specific attribute.
+ *     - NULL on error.
+ */
+static VALUE_PAIR *password_process_sha3(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good)
+{
+       VALUE_PAIR      *out, *normalised;
+
+       switch (known_good->vp_length) {
+       case SHA224_DIGEST_LENGTH:
+               out = fr_pair_afrom_da(ctx, attr_sha3_224_password);
+               fr_pair_value_copy(out, known_good);
+               return out;
+
+       case SHA256_DIGEST_LENGTH:
+               out = fr_pair_afrom_da(ctx, attr_sha3_256_password);
+               fr_pair_value_copy(out, known_good);
+               return out;
+
+       case SHA384_DIGEST_LENGTH:
+               out = fr_pair_afrom_da(ctx, attr_sha3_384_password);
+               fr_pair_value_copy(out, known_good);
+               return out;
+
+       case SHA512_DIGEST_LENGTH:
+               out = fr_pair_afrom_da(ctx, attr_sha3_512_password);
+               fr_pair_value_copy(out, known_good);
+               return out;
+
+       default:
+               out = password_normify(ctx, request, known_good);
+               if (!out) return NULL;
+
+               normalised = password_process_sha3(ctx, request, out);
+               talloc_list_free(out);
+
+               return normalised;
+       }
+}
+#  endif
+#endif
+
 /** Convert a Password-With-Header attribute to the correct type
  *
  * Attribute may be base64 encoded, in which case it will be decoded
  * first, then evaluated.
  *
- * @note The buffer for octets type attributes is extended by one byte
+ * @note The buffer for octets types\ attributes is extended by one byte
  *     and '\0' terminated, to allow it to be used as a char buff.
  *
  * @param[in] ctx              to allocate new pairs in.
  * @param[in] request          Current request.
  * @param[in] known_good       Password-With-Header attribute to convert.
- * @param[in] func             to convert header strings to fr_dict_attr_t.
- * @param[in] def              Default attribute to copy value to if we
- *                             don't recognise the header.
  * @return
- *     - New #VALUE_PAIR on success.
+ *     - Buffer containing normified value on success.
  *     - NULL on error.
  */
-VALUE_PAIR *password_normify_with_header(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good,
-                                        password_header_lookup_t func, fr_dict_attr_t const *def)
+static VALUE_PAIR *password_process_header(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good)
 {
-       char const              *p, *q, *end;
+       char const                      *p, *q, *end;
 
-       uint8_t                 n1[256], n2[256];
-       ssize_t                 decoded;
+       uint8_t                         n1[256], n2[256];
+       ssize_t                         decoded;
 
-       char                    header[128];
-       normalise_t             normalised;
+       char                            header[128];
+       normalise_t                     normalised;
 
-       int                     i;
-
-       VALUE_PAIR              *new;
+       VALUE_PAIR                      *new;
+       fr_dict_attr_t const            *def = attr_cleartext_password;
 
        VP_VERIFY(known_good);
 
@@ -258,315 +629,323 @@ VALUE_PAIR *password_normify_with_header(TALLOC_CTX *ctx, REQUEST *request, VALU
        end = p + known_good->vp_length;
 
        /*
-        *      Only allow one additional level of
-        *      normification and header parsing.
+        *      Has a header {...} prefix
         */
-       for (i = 0; i <= 1; i++) {
-               /*
-                *      Has a header {...} prefix
-                */
-               if ((*p == '{') && (q = memchr(p, '}', end - p))) {
-                       size_t                  hlen;
-                       fr_dict_attr_t const    *da;
-                       ssize_t                 slen;
-
-                       hlen = (q - p) + 1;
-                       if (hlen >= sizeof(header)) {
-                               REDEBUG("Password header too long.  Got %zu bytes must be less than %zu bytes",
-                                       hlen, sizeof(header));
-                               return NULL;
-                       }
-
-                       memcpy(header, p, hlen);
-                       header[hlen] = '\0';
-
-                       slen = func(&da, header);
-                       if (slen <= 0) {
-                               /*
-                                *      header buffer retains { and }
-                                */
-                               if (RDEBUG_ENABLED3) {
-                                       RDEBUG3("Unknown header %s in %pP, re-writing to %s",
-                                               header, known_good, def->name);
-                               } else {
-                                       RDEBUG2("Unknown header %s in %s, re-writing to %s",
-                                               header, known_good->da->name, def->name);
-                               }
-                               goto unknown_header;
-                       }
+do_header:
+       if ((*p == '{') && (q = memchr(p, '}', end - p))) {
+               size_t                  hlen;
+               int                     attr;
+               password_info_t         *info;
+
+               hlen = (q - p) + 1;
+               if (hlen >= sizeof(header)) {
+                       REDEBUG("Password header too long.  Got %zu bytes must be less than %zu bytes",
+                               hlen, sizeof(header));
+                       return NULL;
+               }
 
-                       p = q + 1;
+               memcpy(header, p, hlen);
+               header[hlen] = '\0';
 
+               attr = fr_table_value_by_substr(password_header_table, header, hlen, -1);
+               if (attr < 0) {
                        /*
-                        *      Try and base64 decode, and if we can
-                        *      use the decoded value.
-                        *
-                        *      FIXME: Should pass in min length for
-                        *      password hash da represents.
+                        *      header buffer retains { and }
                         */
-                       decoded = normify(&normalised, n1, sizeof(n1), p, end - p, 1);
-                       if (decoded > 0) {
-                               RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes",
-                                       da->name, fr_table_str_by_value(normalise_table, normalised, 0),
-                                       (end - p), decoded);
-                               p = (char const *)n1;
-                               end = p + decoded;
+                       if (RDEBUG_ENABLED3) {
+                               RDEBUG3("Unknown header %s in %pP, re-writing to %s",
+                                       header, known_good, def->name);
+                       } else {
+                               RDEBUG2("Unknown header %s in %s, re-writing to %s",
+                                       header, known_good->da->name, def->name);
                        }
+                       p = q + 1;
+                       goto bad_header;
+               }
 
-                       new = fr_pair_afrom_da(ctx, da);
-                       switch (da->type) {
-                       case FR_TYPE_OCTETS:
-                               fr_pair_value_memcpy(new, (uint8_t const *)p, end - p, true);
-                               break;
+               p = q + 1;
 
-                       case FR_TYPE_STRING:
-                               fr_pair_value_bstrncpy(new, (uint8_t const *)p, end - p);
-                               break;
+               if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL;
+               info = &password_info[attr];
 
-                       default:
-                               if (!fr_cond_assert(0)) return NULL;
-                       }
-                       return new;
-               }
+               new = fr_pair_afrom_da(ctx, *(info->da));
+               switch ((*(info->da))->type) {
+               case FR_TYPE_OCTETS:
+                       fr_pair_value_memcpy(new, (uint8_t const *)p, end - p, true);
+                       break;
 
-               /*
-                *      Doesn't have a header {...} prefix
-                *
-                *      See if it's base64 or hex, if it is, decode it and check again!
-                */
-               decoded = normify(&normalised, n1, sizeof(n1), p, end - p, 1);
-               if (decoded > 0) {
-                       if ((n1[0] == '{') && (memchr(n1, '}', decoded) != NULL)) {
-                               RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes",
-                                       known_good->da->name, fr_table_str_by_value(normalise_table, normalised, 0),
-                                       known_good->vp_length, decoded);
+               case FR_TYPE_STRING:
+                       fr_pair_value_bstrncpy(new, (uint8_t const *)p, end - p);
+                       break;
 
-                               /*
-                                *      Password-With-Header is a string attribute.
-                                *      Even though we're handling binary data, the header
-                                *      must be \0 terminated.
-                                */
-                               memcpy(n2, n1, decoded);
-                               p = (char const *)n2;
-                               end = p + decoded;
-                               continue;
-                       }
+               default:
+                       if (!fr_cond_assert(0)) return NULL;
                }
+               return new;
+       }
 
-               break;
+       /*
+        *      Doesn't have a header {...} prefix
+        *
+        *      See if it's base64 or hex, if it is, decode it and check again!
+        *
+        *      We ignore request not to normify, as curly braces aren't
+        *      in either of the character sets for the encoding schemes
+        *      we're normifying, so there's not the possibility for error
+        *      as there is normifying other password hashes.
+        */
+       decoded = normify(&normalised, n1, sizeof(n1), p, end - p, 4);  /* { + <char> + } + <char> */
+       if (decoded > 0) {
+               if ((n1[0] == '{') && (memchr(n1, '}', decoded) != NULL)) {
+                       RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes",
+                               known_good->da->name, fr_table_str_by_value(normalise_table, normalised, 0),
+                               known_good->vp_length, decoded);
+
+                       /*
+                        *      Password-With-Header is a string attribute.
+                        *      Even though we're handling binary data, the header
+                        *      must be \0 terminated.
+                        */
+                       memcpy(n2, n1, decoded);
+                       p = (char const *)n2;
+                       end = p + decoded;
+                       goto do_header;
+               }
        }
 
+       /*
+        *      Rewrite to the default attribute type
+        *      currently Cleartext-Password.
+        *
+        *      This is usually correct if there's no
+        *      header to indicate hash type.
+        */
        if (RDEBUG_ENABLED3) {
-               RDEBUG3("No {...} in &%pP, re-writing to %s", known_good, def->name);
+               RDEBUG3("No {...} in &control:%pP, re-writing to %s", known_good, def->name);
        } else {
-               RDEBUG2("No {...} in &%s, re-writing to %s", known_good->da->name, def->name);
+               RDEBUG2("No {...} in &control:%s, re-writing to %s", known_good->da->name, def->name);
        }
 
-unknown_header:
+bad_header:
        new = fr_pair_afrom_da(request, def);
        fr_pair_value_bstrncpy(new, p, end - p);
 
        return new;
 }
 
-
-/*
- *     For auto-header discovery.
- *
- *     @note Header comparison is case insensitive.
- *
- *     We don't put the *value* of "attr_foo" here, as those
- *     values are loaded at run time.  Instead, we point to
- *     the attr_foo definition, which is then a static pointer
- *     to a known variable.
- */
-static fr_table_ptr_ordered_t const header_names[] = {
-       { "X- orclntv}",        &attr_nt_password },
-       { "{base64_md5}",       &attr_md5_password },
-       { "{cleartext}",        &attr_cleartext_password },
-       { "{clear}",            &attr_cleartext_password },
-       { "{crypt}",            &attr_crypt_password },
-       { "{md4}",              &attr_nt_password },
-       { "{md5}",              &attr_md5_password },
-       { "{ns-mta-md5}",       &attr_ns_mta_md5_password },
-       { "{nthash}",           &attr_nt_password },
-       { "{nt}",               &attr_nt_password },
-#ifdef HAVE_OPENSSL_EVP_H
-       { "{sha224}",           &attr_sha2_password },
-       { "{sha256}",           &attr_sha2_password },
-       { "{sha2}",             &attr_sha2_password },
-       { "{sha384}",           &attr_sha2_password },
-       { "{sha512}",           &attr_sha2_password },
-#endif
-       { "{sha}",              &attr_sha_password },
-       { "{smd5}",             &attr_smd5_password },
-#ifdef HAVE_OPENSSL_EVP_H
-       { "{ssha224}",          &attr_ssha2_224_password },
-       { "{ssha256}",          &attr_ssha2_256_password },
-#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
-       { "{ssha3-224}",        &attr_ssha3_224_password },
-       { "{ssha3-256}",        &attr_ssha3_256_password },
-       { "{ssha3-384}",        &attr_ssha3_384_password },
-       { "{ssha3-512}",        &attr_ssha3_512_password },
-#  endif
-       { "{ssha384}",          &attr_ssha2_384_password },
-       { "{ssha512}",          &attr_ssha2_512_password },
-#endif
-       { "{ssha}",             &attr_ssha_password },
-       { "{x- orcllmv}",       &attr_lm_password },
-       { "{x-nthash}",         &attr_nt_password },
-       { "{x-pbkdf2}",         &attr_pbkdf2_password },
-};
-static size_t header_names_len = NUM_ELEMENTS(header_names);
-
-
-static ssize_t known_password_header(fr_dict_attr_t const **out, char const *header)
-{
-       fr_dict_attr_t const **da;
-
-       da = fr_table_value_by_str(header_names, header, NULL);
-       if (!da || !*da) return -1;
-
-       *out = *da;
-       return strlen(header);
-}
-
-static const size_t fr_password_length[] = {
-       [FR_LM_PASSWORD - 5000 ]        = 16,
-       [FR_MD5_PASSWORD - 5000 ]       = 16,
-       [FR_MS_CHAP_PASSWORD - 5000 ]   = 0, /* used only by radclient */
-       [FR_NS_MTA_MD5_PASSWORD - 5000 ] = 0, /* not used at all by anyone/// */
-       [FR_NT_PASSWORD - 5000 ]        = 16,
-       [FR_PBKDF2_PASSWORD - 5000 ]    = 0, /* already normalized */
-       [FR_SHA_PASSWORD - 5000 ]       = 20,
-       [FR_SHA1_PASSWORD - 5000 ]      = 20,
-       [FR_SHA2_PASSWORD - 5000 ]      = 28,
-       [FR_SHA3_PASSWORD - 5000 ]      = 28,
-       [FR_SMD5_PASSWORD - 5000 ]      = 16,
-       [FR_SSHA_PASSWORD - 5000 ]      = 20,
-       [FR_SSHA1_PASSWORD - 5000 ]     = 20,
-       [FR_SSHA2_224_PASSWORD - 5000 ] = 28,
-       [FR_SSHA2_256_PASSWORD - 5000 ] = 32,
-       [FR_SSHA2_384_PASSWORD - 5000 ] = 48,
-       [FR_SSHA2_512_PASSWORD - 5000 ] = 64,
-       [FR_SSHA3_224_PASSWORD - 5000 ] = 28,
-       [FR_SSHA3_256_PASSWORD - 5000 ] = 32,
-       [FR_SSHA3_384_PASSWORD - 5000 ] = 48,
-       [FR_SSHA3_512_PASSWORD - 5000 ] = 64,
-};
-#define MAX_KNOWN_PASSWORD (sizeof(fr_password_length) / sizeof(fr_password_length[0]))
-
-
-/** Normalise passwords.
- *
- * @param request the request to process
- * @param normalise whether or not we normalise the passwords
+/** Apply any processing and normification
  *
  */
-VALUE_PAIR *password_normalise(REQUEST *request, bool normalise)
+static VALUE_PAIR *password_process(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good, bool normify)
 {
-       VALUE_PAIR              *vp, *found_pw = NULL;
-       fr_cursor_t             cursor;
-       fr_dict_attr_t const    *root;
-
-       root = fr_dict_root(dict_freeradius);
-       if (!root) return NULL;
+       password_info_t         *info;
+       VALUE_PAIR              *out;
 
-       for (vp = fr_cursor_init(&cursor, &request->control);
-            vp;
-            vp = fr_cursor_next(&cursor)) {
-               VP_VERIFY(vp);
-               VALUE_PAIR *new;
+       info = &password_info[known_good->da->attr];
+       if (info->func) {
+               VALUE_PAIR      *from_func, *from_recurse;
 
-       next:
                /*
-                *      Look only for out attributes.  Note that we no
-                *      longer complain about User-Password being in
-                *      the control list.  That functionality has been
-                *      deprecated for 10 years.  If the admins still
-                *      do it, too bad.
+                *      Pass our input attribute to a custom preprocessing
+                *      function to manipulate it.
                 */
-               if (vp->da->parent != root) continue;
+               from_func = info->func(ctx, request, known_good);
+               if (!from_func) return NULL;
 
-               if ((vp->da->attr < 5000) || (vp->da->attr >= (5000 + MAX_KNOWN_PASSWORD))) continue;
+               /*
+                *      Processing function may have produced a different
+                *      password type, recurse to deal with it...
+                */
+               from_recurse = password_process(ctx, request, from_func, normify);
 
                /*
-                *      Remove the header, and convert it to something sane.
+                *      Cleanup any intermediary password attributes created
+                *      from running the different normalisation and parsing
+                *      operations.
                 */
-               if (vp->da->attr == FR_PASSWORD_WITH_HEADER) {
-                       /*
-                        *      Password already exists: use that instead of this one.
-                        */
-                       if (fr_pair_find_by_da(request->control, attr_cleartext_password, TAG_ANY)) {
-                               RWDEBUG("Config already contains a \"known good\" password "
-                                       "(&control:%s).  Ignoring &control:%s",
-                                       attr_cleartext_password->name, vp->da->name);
-                               break;
-                       }
+               if (!from_recurse) {
+                       if (from_func != known_good) talloc_list_free(&from_func);
+                       return NULL;
+               }
+               if ((from_func != known_good) && (from_recurse != from_func)) talloc_list_free(&from_func);
 
-                       MEM(new = password_normify_with_header(request, request, vp,
-                                                              known_password_header,
-                                                              attr_cleartext_password));
-                       if (RDEBUG_ENABLED3) {
-                               RDEBUG3("Normalized &control:%pP -> &control:%pP", vp, new);
-                       } else {
-                               RDEBUG2("Normalized &control:%s -> &control:%s", vp->da->name, new->da->name);
-                       }
+               return from_recurse;
+       }
 
-                       RDEBUG2("Removing &control:%s", vp->da->name);
-                       fr_cursor_free_item(&cursor);                   /* advances the cursor for us */
-                       fr_cursor_append(&cursor, new);                 /* inserts at the end of the list */
+       /*
+        *      Only normify if we're told to, and we have more data
+        *      than the minimum length.
+        */
+       if (normify && !info->no_normify && (known_good->vp_length > info->min_hash_len)) {
+               VALUE_PAIR *from_normify;
 
-                       found_pw = new;
+               from_normify = password_normify(ctx, request, known_good);
+               out = from_normify ? from_normify : known_good;
+       } else {
+               out = known_good;
+       }
 
-                       vp = fr_cursor_current(&cursor);
-                       if (vp) goto next;
-                       break;
+       /*
+        *      Sanity checks - Too short
+        */
+       if (info->min_hash_len && (out->vp_length < MIN_LEN(info))) {
+               if (RDEBUG_ENABLED3) {
+                       RWDEBUG3("&control:%pP too short, expected %zu bytes, got %zu bytes",
+                                out, MIN_LEN(info), out->vp_length);
+               } else {
+                       RWDEBUG2("&control:%s too short, expected %zu bytes, got %zu bytes",
+                                out->da->name, MIN_LEN(info), out->vp_length);
                }
+       invalid:
+               if (out != known_good) talloc_list_free(&out);  /* Free attribute we won't be returning */
+               return NULL;
+       }
 
-               found_pw = vp;
+       /*
+        *      Sanity checks - Too long
+        */
+       if (info->max_hash_len && (out->vp_length > info->max_hash_len)) {
+               if (RDEBUG_ENABLED3) {
+                       RWDEBUG3("&control:%pP too long, expected %zu bytes, got %zu bytes",
+                                out, info->max_hash_len, out->vp_length);
+               } else {
+                       RWDEBUG2("&control:%s too long, expected %zu bytes, got %zu bytes",
+                                out->da->name, info->max_hash_len, out->vp_length);
+               }
+               goto invalid;
+       }
 
-               /*
-                *      Don't normalise Cleartext Passwords
-                */
-               if (vp->da->attr == FR_CLEARTEXT_PASSWORD) break;
+       /*
+        *      Sanity checks - Hashes are a fixed length
+        */
+       if ((info->type == PASSWORD_HASH) && (out->vp_length != info->min_hash_len)) {
 
-               if (!normalise) break;
+               if (RDEBUG_ENABLED3) {
+                       RWDEBUG3("&control:%pP incorrect length, expected %zu bytes, got %zu bytes",
+                                out, info->min_hash_len, out->vp_length);
+               } else {
+                       RWDEBUG2("&control:%s incorrect length, expected %zu bytes, got %zu bytes",
+                                out->da->name, info->min_hash_len, out->vp_length);
+               }
+               goto invalid;
+       }
 
-               if (!fr_password_length[vp->da->attr - 5000]) break;
+       return out;
+}
 
-               new = password_normify(request, request, vp, fr_password_length[vp->da->attr - 5000]);
-               if (new) {
-                       fr_cursor_free_item(&cursor);           /* free the old pasword */
-                       fr_cursor_append(&cursor, new);         /* inserts at the end of the list */
+/** Find all password attributes in the control list of a request and normalise them
+ *
+ * @param[in] request  The current request.
+ * @param[in] normify  Apply hex/base64 normalisation to attributes.
+ * @return the number of attributes normalised.
+ */
+int password_normalise_and_replace(REQUEST *request, bool normify)
+{
+       fr_cursor_t     cursor;
+       int             replaced = 0;
+       VALUE_PAIR      *known_good, *new;
 
-                       vp = fr_cursor_current(&cursor);
-                       if (vp) goto next;
-                       break;
-               }
+       for (known_good = fr_cursor_iter_by_ancestor_init(&cursor, &request->control, attr_password_root);
+            known_good;
+            known_good = fr_cursor_next(&cursor)) {
+               if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return -1;
 
                /*
-                *      Can't normalise this one, but there might be
-                *      another one that we can normalise.
+                *      Apply preprocessing steps and normalisation.
                 */
-       }
+               new = password_process(request, request, known_good, normify);
+               if (!new) break;                /* Process next input attribute */
 
-       /*
-        *      Print helpful warnings if there was no password.
-        */
-       if (found_pw) {
                if (RDEBUG_ENABLED3) {
-                       RDEBUG("Found \"known good\" password in &control:%s = \"%pV\"", found_pw->da->name, &found_pw->data);
+                       RDEBUG3("Replacing &control:%pP with &control:%pP",
+                               known_good, new);
+
                } else {
-                       RDEBUG("Found \"known good\" password in &control:%s", found_pw->da->name);
+                       RDEBUG2("Replacing &control:%s with &control:%s",
+                               known_good->da->name, new->da->name);
                }
+               fr_cursor_free_item(&cursor);
+               fr_cursor_prepend(&cursor, new);
+               replaced++;
+       }
 
-       } else {
-               RWDEBUG("No \"known good\" password found for the user.");
-               RWDEBUG("Authentication may fail unless a \"known good\" password is available");
+       return replaced;
+}
+
+/** Find a "known good" password in the control list of a request
+ *
+ * Searches for a "known good" password attribute, and applies any processing
+ * and normification operations to it, returning a new mormalised VALUE_PAIR.
+ *
+ * The ctx passed in should be freed when the caller is done with the returned
+ * VALUE_PAIR.  The returned pair *MUST NOT BE FREED*, it may be an attribute
+ * in the request->control list.
+ *
+ * @param[in] ctx              Ephemeral ctx to allocate new attributes in.
+ * @param[in] request          The current request.
+ * @param[in] allowed_attrs    Optional list of allowed attributes.
+ * @param[in] allowed_attrs_len        Length of allowed attributes list.
+ * @param[in] normify          Apply hex/base64 normalisation to attributes.
+ * @return
+ *     - A VALUE_PAIR containing a "known good" password.
+ *     - NULL on error, or if no usable password attributes were found.
+ */
+VALUE_PAIR const *password_find(TALLOC_CTX *ctx, REQUEST *request,
+                               fr_dict_attr_t const *allowed_attrs[], size_t allowed_attrs_len, bool normify)
+{
+       fr_cursor_t     cursor;
+       size_t          i, j;
+       VALUE_PAIR      *known_good;
+
+       for (known_good = fr_cursor_iter_by_ancestor_init(&cursor, &request->control, attr_password_root);
+            known_good;
+            known_good = fr_cursor_next(&cursor)) {
+               if (known_good->da == attr_user_password) {
+                       RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+                       RWDEBUG("!!! Ignoring control:User-Password.  Update your        !!!");
+                       RWDEBUG("!!! configuration so that the \"known good\" clear text !!!");
+                       RWDEBUG("!!! password is in Cleartext-Password and NOT in        !!!");
+                       RWDEBUG("!!! User-Password.                                      !!!");
+                       RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+                       continue;
+               }
+
+               for (i = 0; i < allowed_attrs_len; i++) {
+                       VALUE_PAIR *new;
+
+                       if (allowed_attrs[i] != known_good->da) continue;
+
+                       if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL;
+
+                       /*
+                        *      Apply preprocessing steps and normalisation.
+                        */
+                       new = password_process(ctx, request, known_good, normify);
+                       if (!new) break;                /* Process next input attribute */
+
+                       /*
+                        *      If new != known_good, then we need
+                        *      to check what was produced is still
+                        *      acceptable.
+                        */
+                       if (new->da != known_good->da) {
+                               for (j = 0; j < allowed_attrs_len; j++) if (allowed_attrs[j] == new->da) return new;
+
+                               /*
+                                *      New attribute not in our allowed list
+                                */
+                               talloc_list_free(&new); /* da didn't match, treat as ephemeral */
+                               break;                  /* Process next input attribute */
+                       }
+
+                       /*
+                        *      Return attribute for processing
+                        */
+                       return new;
+               }
        }
 
-       return found_pw;
+       return NULL;
 }
 
 /** Load our dictionaries
index f9a24b5f4cdc4a4caf418928fcaf87b278085078..5b58e205036e462db9d4fbfea120054dc03a103c 100644 (file)
@@ -30,17 +30,15 @@ extern "C" {
 
 #include <freeradius-devel/server/request.h>
 
-typedef ssize_t(*password_header_lookup_t)(fr_dict_attr_t const **out, char const *header);
+int                    password_normalise_and_replace(REQUEST *request, bool normify);
 
-VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const *known_good, size_t min_len);
+VALUE_PAIR const       *password_find(TALLOC_CTX *ctx, REQUEST *request,
+                                      fr_dict_attr_t const *allowed_attrs[],
+                                      size_t allowed_attrs_len, bool normify);
 
-VALUE_PAIR *password_normify_with_header(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *known_good,
-                                        password_header_lookup_t func, fr_dict_attr_t const *def);
+int                    password_init(void);
 
-VALUE_PAIR *password_normalise(REQUEST *request, bool normalise);
-
-int password_init(void);
-void password_free(void);
+void                   password_free(void);
 
 #ifdef __cplusplus
 }
index d16ffc669fc0c1e023cb2f3f301a15f0b138b72e..39a5e71abb0095a95cf96da8a9d886786aac30d1 100644 (file)
@@ -29,6 +29,7 @@ RCSID("$Id$")
 
 typedef struct {
        char const              *name;          //!< Auth-Type value for this module instance.
+       bool                    normify;
        fr_dict_enum_t          *auth_type;
 } rlm_chap_t;
 
@@ -44,6 +45,7 @@ fr_dict_autoload_t rlm_chap_dict[] = {
 
 static fr_dict_attr_t const *attr_auth_type;
 static fr_dict_attr_t const *attr_cleartext_password;
+static fr_dict_attr_t const *attr_password_with_header;
 
 static fr_dict_attr_t const *attr_chap_password;
 static fr_dict_attr_t const *attr_chap_challenge;
@@ -53,6 +55,7 @@ extern fr_dict_attr_autoload_t rlm_chap_dict_attr[];
 fr_dict_attr_autoload_t rlm_chap_dict_attr[] = {
        { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
        { .out = &attr_cleartext_password, .name = "Cleartext-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
+       { .out = &attr_password_with_header, .name = "Password-With-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
 
        { .out = &attr_chap_password, .name = "Chap-Password", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
        { .out = &attr_chap_challenge, .name = "Chap-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
@@ -60,6 +63,11 @@ fr_dict_attr_autoload_t rlm_chap_dict_attr[] = {
        { NULL }
 };
 
+static const CONF_PARSER module_config[] = {
+       { FR_CONF_OFFSET("normalise", FR_TYPE_BOOL, rlm_chap_t, normify), .dflt = "yes" },
+       CONF_PARSER_TERMINATOR
+};
+
 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *thread, REQUEST *request)
 {
        rlm_chap_t      *inst = instance;
@@ -104,8 +112,12 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t
  */
 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUSED void *thread, REQUEST *request)
 {
-       VALUE_PAIR      *known_good, *chap, *username;
-       uint8_t         pass_str[FR_MAX_STRING_LEN];
+       rlm_chap_t              *inst = instance;
+       VALUE_PAIR const        *known_good;
+       VALUE_PAIR              *chap, *username;
+       uint8_t                 pass_str[FR_MAX_STRING_LEN];
+       TALLOC_CTX              *tmp_ctx;
+       fr_dict_attr_t const    *allowed_passwords[] = { attr_password_with_header, attr_cleartext_password };
 
        username = fr_pair_find_by_da(request->packet->vps, attr_user_name, TAG_ANY);
        if (!username) {
@@ -131,18 +143,24 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUS
        }
 
        /*
-        *      Normalise passwords, if it hasn't already been done.
-        *
-        *      This function returns the "known good" password, and
-        *      prefers to return Cleartext-Password over everything
-        *      else.
+        *      Holds any ephemeral attributes
+        */
+       MEM(tmp_ctx = talloc_named_const(request, 0, "password_tmp_ctx"));
+
+       /*
+        *      Retrieve the normalised version of
+        *      the known_good password, without
+        *      mangling the current password attributes
+        *      in the request.
         */
-       known_good = password_normalise(request, true);
-       if (!known_good || (known_good->da != attr_cleartext_password)) {
-               REDEBUG("&control:Cleartext-Password is required for authentication");
+       known_good = password_find(tmp_ctx, request,
+                                  allowed_passwords, NUM_ELEMENTS(allowed_passwords),
+                                  inst->normify);
+       if (!known_good) {
+               REDEBUG("No \"known good\" password found for user");
+               talloc_free(tmp_ctx);
                return RLM_MODULE_FAIL;
        }
-
        fr_radius_encode_chap_password(pass_str, request->packet, chap->vp_octets[0], known_good);
 
        if (RDEBUG_ENABLED3) {
@@ -174,11 +192,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUS
 
        if (fr_digest_cmp(pass_str + 1, chap->vp_octets + 1, RADIUS_CHAP_CHALLENGE_LENGTH) != 0) {
                REDEBUG("Password comparison failed: password is incorrect");
+               talloc_free(tmp_ctx);
                return RLM_MODULE_REJECT;
        }
 
        RDEBUG2("CHAP user \"%pV\" authenticated successfully", &username->data);
 
+       talloc_free(tmp_ctx);
+
        return RLM_MODULE_OK;
 }
 
@@ -213,6 +234,7 @@ module_t rlm_chap = {
        .magic          = RLM_MODULE_INIT,
        .name           = "chap",
        .inst_size      = sizeof(rlm_chap_t),
+       .config         = module_config,
        .bootstrap      = mod_bootstrap,
        .dict           = &dict_radius,
        .methods = {
index 1bd237a3affabf36ee02bbe42b9c01c884a0a434..052057ba33f199e27c607fbea52f746bc7c8901e 100644 (file)
@@ -57,6 +57,8 @@ typedef struct {
        bool                    normify;
 } rlm_pap_t;
 
+typedef rlm_rcode_t (*pap_auth_func_t)(rlm_pap_t const *, REQUEST *, VALUE_PAIR const *, VALUE_PAIR const *);
+
 static const CONF_PARSER module_config[] = {
        { FR_CONF_OFFSET("normalise", FR_TYPE_BOOL, rlm_pap_t, normify), .dflt = "yes" },
        CONF_PARSER_TERMINATOR
@@ -65,72 +67,20 @@ static const CONF_PARSER module_config[] = {
 static fr_dict_t *dict_freeradius;
 static fr_dict_t *dict_radius;
 
-extern fr_dict_autoload_t rlm_pap_dict[];
-fr_dict_autoload_t rlm_pap_dict[] = {
+static fr_dict_autoload_t rlm_pap_dict[] = {
        { .out = &dict_freeradius, .proto = "freeradius" },
        { .out = &dict_radius, .proto = "radius" },
        { NULL }
 };
 
 static fr_dict_attr_t const *attr_auth_type;
-
-static fr_dict_attr_t const *attr_cleartext_password;
-static fr_dict_attr_t const *attr_password_with_header;
-
-static fr_dict_attr_t const *attr_md5_password;
-static fr_dict_attr_t const *attr_smd5_password;
-static fr_dict_attr_t const *attr_crypt_password;
-static fr_dict_attr_t const *attr_sha_password;
-static fr_dict_attr_t const *attr_ssha_password;
-
-static fr_dict_attr_t const *attr_sha2_password;
-static fr_dict_attr_t const *attr_ssha2_224_password;
-static fr_dict_attr_t const *attr_ssha2_256_password;
-static fr_dict_attr_t const *attr_ssha2_384_password;
-static fr_dict_attr_t const *attr_ssha2_512_password;
-
-static fr_dict_attr_t const *attr_sha3_password;
-static fr_dict_attr_t const *attr_ssha3_224_password;
-static fr_dict_attr_t const *attr_ssha3_256_password;
-static fr_dict_attr_t const *attr_ssha3_384_password;
-static fr_dict_attr_t const *attr_ssha3_512_password;
-
-static fr_dict_attr_t const *attr_pbkdf2_password;
-static fr_dict_attr_t const *attr_lm_password;
-static fr_dict_attr_t const *attr_nt_password;
-static fr_dict_attr_t const *attr_ns_mta_md5_password;
+static fr_dict_attr_t const *attr_password_root;
 
 static fr_dict_attr_t const *attr_user_password;
 
-extern fr_dict_attr_autoload_t rlm_pap_dict_attr[];
-fr_dict_attr_autoload_t rlm_pap_dict_attr[] = {
+static fr_dict_attr_autoload_t rlm_pap_dict_attr[] = {
        { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
-
-       { .out = &attr_cleartext_password, .name = "Cleartext-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
-       { .out = &attr_password_with_header, .name = "Password-With-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
-
-       { .out = &attr_md5_password, .name = "MD5-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_smd5_password, .name = "SMD5-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_crypt_password, .name = "Crypt-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
-       { .out = &attr_sha_password, .name = "SHA-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha_password, .name = "SSHA-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-
-       { .out = &attr_sha2_password, .name = "SHA2-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha2_224_password, .name = "SSHA2-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha2_256_password, .name = "SSHA2-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha2_384_password, .name = "SSHA2-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha2_512_password, .name = "SSHA2-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-
-       { .out = &attr_sha3_password, .name = "SHA3-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha3_224_password, .name = "SSHA3-224-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha3_256_password, .name = "SSHA3-256-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha3_384_password, .name = "SSHA3-384-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ssha3_512_password, .name = "SSHA3-512-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-
-       { .out = &attr_pbkdf2_password, .name = "PBKDF2-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_lm_password, .name = "LM-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_nt_password, .name = "NT-Password", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
-       { .out = &attr_ns_mta_md5_password, .name = "NS-MTA-MD5-Password", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
+       { .out = &attr_password_root, .name = "Password-Root", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
 
        { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius },
 
@@ -139,7 +89,7 @@ fr_dict_attr_autoload_t rlm_pap_dict_attr[] = {
 
 #ifdef HAVE_OPENSSL_EVP_H
 static fr_table_num_sorted_t const pbkdf2_crypt_names[] = {
-       { "HMACSHA1",           FR_SSHA_PASSWORD },
+       { "HMACSHA1",           FR_SSHA1_PASSWORD },
        { "HMACSHA2+224",       FR_SSHA2_224_PASSWORD },
        { "HMACSHA2+256",       FR_SSHA2_256_PASSWORD },
        { "HMACSHA2+384",       FR_SSHA2_384_PASSWORD },
@@ -154,13 +104,15 @@ static fr_table_num_sorted_t const pbkdf2_crypt_names[] = {
 static size_t pbkdf2_crypt_names_len = NUM_ELEMENTS(pbkdf2_crypt_names);
 
 static fr_table_num_sorted_t const pbkdf2_passlib_names[] = {
-       { "sha1",               FR_SSHA_PASSWORD },
+       { "sha1",               FR_SSHA1_PASSWORD },
        { "sha256",             FR_SSHA2_256_PASSWORD },
        { "sha512",             FR_SSHA2_512_PASSWORD }
 };
 static size_t pbkdf2_passlib_names_len = NUM_ELEMENTS(pbkdf2_passlib_names);
 #endif
 
+static fr_dict_attr_t const **pap_allowed_passwords;
+
 /*
  *     Authorize the user for PAP authentication.
  *
@@ -193,7 +145,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t
  */
 
 static rlm_rcode_t CC_HINT(nonnull) pap_auth_clear(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                  VALUE_PAIR *known_good, VALUE_PAIR const *password)
+                                                  VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        if ((known_good->vp_length != password->vp_length) ||
            (fr_digest_cmp(known_good->vp_octets, password->vp_octets, known_good->vp_length) != 0)) {
@@ -207,7 +159,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_clear(UNUSED rlm_pap_t const *inst,
 
 #ifdef HAVE_CRYPT
 static rlm_rcode_t CC_HINT(nonnull) pap_auth_crypt(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                  VALUE_PAIR *known_good, VALUE_PAIR const *password)
+                                                  VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        if (fr_crypt_check(password->vp_strvalue, known_good->vp_strvalue) != 0) {
                REDEBUG("Crypt digest does not match \"known good\" digest");
@@ -218,7 +170,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_crypt(UNUSED rlm_pap_t const *inst,
 #endif
 
 static rlm_rcode_t CC_HINT(nonnull) pap_auth_md5(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                VALUE_PAIR *known_good, VALUE_PAIR const *password)
+                                                VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        uint8_t digest[MD5_DIGEST_LENGTH];
 
@@ -242,7 +194,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_md5(UNUSED rlm_pap_t const *inst, R
 
 
 static rlm_rcode_t CC_HINT(nonnull) pap_auth_smd5(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                 VALUE_PAIR *known_good, VALUE_PAIR const *password)
+                                                 VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        fr_md5_ctx_t    *md5_ctx;
        uint8_t         digest[MD5_DIGEST_LENGTH];
@@ -272,8 +224,8 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_smd5(UNUSED rlm_pap_t const *inst,
        return RLM_MODULE_OK;
 }
 
-static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                VALUE_PAIR *known_good, VALUE_PAIR const *password)
+static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha1(UNUSED rlm_pap_t const *inst, REQUEST *request,
+                                                 VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        fr_sha1_ctx     sha1_context;
        uint8_t         digest[SHA1_DIGEST_LENGTH];
@@ -298,9 +250,8 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha(UNUSED rlm_pap_t const *inst, R
        return RLM_MODULE_OK;
 }
 
-#ifdef HAVE_OPENSSL_EVP_H
-static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                 VALUE_PAIR *known_good, VALUE_PAIR const *password)
+static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha1(UNUSED rlm_pap_t const *inst, REQUEST *request,
+                                                  VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        fr_sha1_ctx     sha1_context;
        uint8_t         digest[SHA1_DIGEST_LENGTH];
@@ -329,94 +280,15 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha(UNUSED rlm_pap_t const *inst,
        return RLM_MODULE_OK;
 }
 
-static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha_evp(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                    VALUE_PAIR *known_good, VALUE_PAIR const *password)
+#ifdef HAVE_OPENSSL_EVP_H
+static rlm_rcode_t CC_HINT(nonnull) pap_auth_evp_md(UNUSED rlm_pap_t const *inst, REQUEST *request,
+                                                   VALUE_PAIR const *known_good, VALUE_PAIR const *password,
+                                                   char const *name, EVP_MD const *md)
 {
        EVP_MD_CTX      *ctx;
-       EVP_MD const    *md;
-       char const      *name;
        uint8_t         digest[EVP_MAX_MD_SIZE];
        unsigned int    digest_len;
 
-       if (known_good->da == attr_sha2_password) {
-               /*
-                *      All the SHA-2 algorithms produce digests of different lengths,
-                *      so it's trivial to determine which EVP_MD to use.
-                */
-               switch (known_good->vp_length) {
-               /* SHA2-224 */
-               case SHA224_DIGEST_LENGTH:
-                       name = "SHA2-224";
-                       md = EVP_sha224();
-                       break;
-
-               /* SHA2-256 */
-               case SHA256_DIGEST_LENGTH:
-                       name = "SHA2-256";
-                       md = EVP_sha256();
-                       break;
-
-               /* SHA2-384 */
-               case SHA384_DIGEST_LENGTH:
-                       name = "SHA2-384";
-                       md = EVP_sha384();
-                       break;
-
-               /* SHA2-512 */
-               case SHA512_DIGEST_LENGTH:
-                       name = "SHA2-512";
-                       md = EVP_sha512();
-                       break;
-
-               default:
-                       REDEBUG("\"known good\" digest length (%zu) does not match output length of any SHA-2 digests",
-                               known_good->vp_length);
-                       return RLM_MODULE_INVALID;
-               }
-       }
-#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
-       else if (known_good->da == attr_sha3_password) {
-               /*
-                *      All the SHA-3 algorithms produce digests of different lengths,
-                *      so it's trivial to determine which EVP_MD to use.
-                */
-               switch (known_good->vp_length) {
-               /* SHA3-224 */
-               case SHA224_DIGEST_LENGTH:
-                       name = "SHA3-224";
-                       md = EVP_sha3_224();
-                       break;
-
-               /* SHA3-256 */
-               case SHA256_DIGEST_LENGTH:
-                       name = "SHA3-256";
-                       md = EVP_sha3_256();
-                       break;
-
-               /* SHA3-384 */
-               case SHA384_DIGEST_LENGTH:
-                       name = "SHA3-384";
-                       md = EVP_sha3_384();
-                       break;
-
-               /* SHA3-512 */
-               case SHA512_DIGEST_LENGTH:
-                       name = "SHA3-512";
-                       md = EVP_sha3_512();
-                       break;
-
-               default:
-                       REDEBUG("\"known good\" digest length (%zu) does not match output length of any SHA-3 digests",
-                               known_good->vp_length);
-                       return RLM_MODULE_INVALID;
-               }
-       }
-#  endif
-       else {
-               rad_assert(0);
-               return RLM_MODULE_INVALID;
-       }
-
        ctx = EVP_MD_CTX_create();
        EVP_DigestInit_ex(ctx, md, NULL);
        EVP_DigestUpdate(ctx, password->vp_octets, password->vp_length);
@@ -436,62 +308,15 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha_evp(UNUSED rlm_pap_t const *ins
        return RLM_MODULE_OK;
 }
 
-static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha_evp(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                     VALUE_PAIR *known_good, VALUE_PAIR const *password)
+static rlm_rcode_t CC_HINT(nonnull) pap_auth_evp_md_salted(UNUSED rlm_pap_t const *inst, REQUEST *request,
+                                                          VALUE_PAIR const *known_good, VALUE_PAIR const *password,
+                                                          char const *name, EVP_MD const *md)
 {
        EVP_MD_CTX      *ctx;
-       EVP_MD const    *md = NULL;
-       char const      *name = NULL;
        uint8_t         digest[EVP_MAX_MD_SIZE];
-       unsigned int    digest_len, min_len = 0;
-
-       if (known_good->da == attr_ssha2_224_password) {
-               name = "SSHA2-224";
-               md = EVP_sha224();
-               min_len = SHA224_DIGEST_LENGTH;
-       } else if (known_good->da == attr_ssha2_256_password) {
-               name = "SSHA2-256";
-               md = EVP_sha256();
-               min_len = SHA256_DIGEST_LENGTH;
-       } else if (known_good->da == attr_ssha2_384_password) {
-               name = "SSHA2-384";
-               md = EVP_sha384();
-               min_len = SHA384_DIGEST_LENGTH;
-       } else if (known_good->da == attr_ssha2_512_password) {
-               name = "SSHA2-512";
-               min_len = SHA512_DIGEST_LENGTH;
-               md = EVP_sha512();
-       }
-#if OPENSSL_VERSION_NUMBER >= 0x10101000L
-       else if (known_good->da == attr_ssha3_224_password) {
-               name = "SSHA3-224";
-               md = EVP_sha3_224();
-               min_len = SHA224_DIGEST_LENGTH;
-       } else if (known_good->da == attr_ssha3_256_password) {
-               name = "SSHA3-256";
-               md = EVP_sha3_256();
-               min_len = SHA256_DIGEST_LENGTH;
-       } else if (known_good->da == attr_ssha3_384_password) {
-               name = "SSHA3-384";
-               md = EVP_sha3_384();
-               min_len = SHA384_DIGEST_LENGTH;
-       } else if (known_good->da == attr_ssha3_512_password) {
-               name = "SSHA3-512";
-               min_len = SHA512_DIGEST_LENGTH;
-               md = EVP_sha3_512();
-       }
-#endif
-       else {
-               rad_assert(0);
-               return RLM_MODULE_INVALID;
-       }
-
-       if (known_good->vp_length <= min_len) {
-               REDEBUG("\"known-good\" %s-Password has incorrect length, got %zu bytes, need at least %u bytes",
-                       name, known_good->vp_length, min_len + 1);
-               return RLM_MODULE_INVALID;
-       }
+       unsigned int    digest_len, min_len;
 
+       min_len = EVP_MD_size(md);
        ctx = EVP_MD_CTX_create();
        EVP_DigestInit_ex(ctx, md, NULL);
        EVP_DigestUpdate(ctx, password->vp_octets, password->vp_length);
@@ -517,6 +342,36 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha_evp(UNUSED rlm_pap_t const *in
        return RLM_MODULE_OK;
 }
 
+/** Define a new OpenSSL EVP based password hashing function
+ *
+ */
+#define PAP_AUTH_EVP_MD(_func, _new_func, _name, _md) \
+static rlm_rcode_t CC_HINT(nonnull) _new_func(rlm_pap_t const *inst, REQUEST *request,                 \
+                                             VALUE_PAIR const *known_good, VALUE_PAIR const *password) \
+{                                                                                                      \
+       return _func(inst, request, known_good, password, _name, _md);                                  \
+}
+
+PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha2_224, "SHA2-224", EVP_sha224());
+PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha2_256, "SHA2-256", EVP_sha256());
+PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha2_384, "SHA2-384", EVP_sha384());
+PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha2_512, "SHA2-512", EVP_sha512());
+PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha2_224, "SSHA2-224", EVP_sha224());
+PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha2_256, "SSHA2-256", EVP_sha256());
+PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha2_384, "SSHA2-384", EVP_sha384());
+PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha2_512, "SSHA2-512", EVP_sha512());
+
+#  if OPENSSL_VERSION_NUMBER >= 0x10101000L
+PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha3_224, "SHA3-224", EVP_sha3_224());
+PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha3_256, "SHA3-256", EVP_sha3_256());
+PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha3_384, "SHA3-384", EVP_sha3_384());
+PAP_AUTH_EVP_MD(pap_auth_evp_md, pap_auth_sha3_512, "SHA3-512", EVP_sha3_512());
+PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha3_224, "SSHA3-224", EVP_sha3_224());
+PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha3_256, "SSHA3-256", EVP_sha3_256());
+PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha3_384, "SSHA3-384", EVP_sha3_384());
+PAP_AUTH_EVP_MD(pap_auth_evp_md_salted, pap_auth_ssha3_512, "SSHA3-512", EVP_sha3_512());
+#  endif
+
 /** Validates Crypt::PBKDF2 LDAP format strings
  *
  * @param[in] request  The current request.
@@ -568,7 +423,7 @@ static inline rlm_rcode_t CC_HINT(nonnull) pap_auth_pbkdf2_parse(REQUEST *reques
 
        digest_type = fr_table_value_by_substr(hash_names, (char const *)p, q - p, -1);
        switch (digest_type) {
-       case FR_SSHA_PASSWORD:
+       case FR_SSHA1_PASSWORD:
                evp_md = EVP_sha1();
                digest_len = SHA1_DIGEST_LENGTH;
                break;
@@ -742,7 +597,7 @@ finish:
 
 static inline rlm_rcode_t CC_HINT(nonnull) pap_auth_pbkdf2(UNUSED rlm_pap_t const *inst,
                                                           REQUEST *request,
-                                                          VALUE_PAIR *known_good, VALUE_PAIR const *password)
+                                                          VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        uint8_t const *p = known_good->vp_octets, *q, *end = p + known_good->vp_length;
 
@@ -803,7 +658,7 @@ static inline rlm_rcode_t CC_HINT(nonnull) pap_auth_pbkdf2(UNUSED rlm_pap_t cons
 #endif
 
 static rlm_rcode_t CC_HINT(nonnull) pap_auth_nt(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                               VALUE_PAIR *known_good, VALUE_PAIR const *password)
+                                               VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        ssize_t len;
        uint8_t digest[MD4_DIGEST_LENGTH];
@@ -838,7 +693,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_nt(UNUSED rlm_pap_t const *inst, RE
 }
 
 static rlm_rcode_t CC_HINT(nonnull) pap_auth_lm(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                               VALUE_PAIR *known_good, UNUSED VALUE_PAIR const *password)
+                                               VALUE_PAIR const *known_good, UNUSED VALUE_PAIR const *password)
 {
        uint8_t digest[MD4_DIGEST_LENGTH];
        char    charbuf[32 + 1];
@@ -866,7 +721,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_lm(UNUSED rlm_pap_t const *inst, RE
 }
 
 static rlm_rcode_t CC_HINT(nonnull) pap_auth_ns_mta_md5(UNUSED rlm_pap_t const *inst, REQUEST *request,
-                                                       VALUE_PAIR *known_good, VALUE_PAIR const *password)
+                                                       VALUE_PAIR const *known_good, VALUE_PAIR const *password)
 {
        uint8_t digest[128];
        uint8_t buff[FR_MAX_STRING_LEN];
@@ -875,7 +730,8 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ns_mta_md5(UNUSED rlm_pap_t const *
        RDEBUG2("Using NT-MTA-MD5-Password");
 
        if (known_good->vp_length != 64) {
-               REDEBUG("\"known good\" NS-MTA-MD5-Password has incorrect length, expected 64 got %zu", known_good->vp_length);
+               REDEBUG("\"known good\" NS-MTA-MD5-Password has incorrect length, expected 64 got %zu",
+                       known_good->vp_length);
                return RLM_MODULE_INVALID;
        }
 
@@ -923,53 +779,70 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ns_mta_md5(UNUSED rlm_pap_t const *
        return RLM_MODULE_OK;
 }
 
-typedef rlm_rcode_t (*pap_auth_func_t)(rlm_pap_t const *, REQUEST *, VALUE_PAIR *, VALUE_PAIR const *);
+/** Auth func for password types that should have been normalised away
+ *
+ */
+static rlm_rcode_t CC_HINT(nonnull) pap_auth_dummy(UNUSED rlm_pap_t const *inst, UNUSED REQUEST *request,
+                                                  UNUSED VALUE_PAIR const *known_good, UNUSED VALUE_PAIR const *password)
+{
+       return RLM_MODULE_FAIL;
+}
 
+/** Table of password types we can process
+ *
+ */
 static const pap_auth_func_t auth_func_table[] = {
-       [FR_CLEARTEXT_PASSWORD - 5000 ] = pap_auth_clear,
-       [FR_LM_PASSWORD - 5000 ]        = pap_auth_lm,
-       [FR_MD5_PASSWORD - 5000 ]       = pap_auth_md5,
-       [FR_SMD5_PASSWORD - 5000 ]      = pap_auth_smd5,
+       [FR_CLEARTEXT_PASSWORD] = pap_auth_clear,
+       [FR_LM_PASSWORD]        = pap_auth_lm,
+       [FR_MD5_PASSWORD]       = pap_auth_md5,
+       [FR_SMD5_PASSWORD]      = pap_auth_smd5,
 
 #ifdef HAVE_CRYPT
-       [FR_CRYPT_PASSWORD - 5000 ]     = pap_auth_crypt,
+       [FR_CRYPT_PASSWORD]     = pap_auth_crypt,
 #endif
-       [FR_NS_MTA_MD5_PASSWORD - 5000 ] = pap_auth_ns_mta_md5,
-       [FR_NT_PASSWORD - 5000 ]        = pap_auth_nt,
-       [FR_SHA_PASSWORD - 5000 ]       = pap_auth_sha,
-       [FR_SHA1_PASSWORD - 5000 ]      = pap_auth_sha,
+       [FR_NS_MTA_MD5_PASSWORD] = pap_auth_ns_mta_md5,
+       [FR_NT_PASSWORD]        = pap_auth_nt,
+       [FR_PASSWORD_WITH_HEADER] = pap_auth_dummy,
+       [FR_SHA1_PASSWORD]      = pap_auth_sha1,
 
 #ifdef HAVE_OPENSSL_EVP_H
-       [FR_SHA2_PASSWORD - 5000 ]      = pap_auth_sha_evp,
-       [FR_SSHA_PASSWORD - 5000 ]      = pap_auth_ssha,
-       [FR_SSHA1_PASSWORD - 5000 ]     = pap_auth_ssha,
-       [FR_SSHA2_224_PASSWORD - 5000 ] = pap_auth_ssha_evp,
-       [FR_SSHA2_256_PASSWORD - 5000 ] = pap_auth_ssha_evp,
-       [FR_SSHA2_384_PASSWORD - 5000 ] = pap_auth_ssha_evp,
-       [FR_SSHA2_512_PASSWORD - 5000 ] = pap_auth_ssha_evp,
-
-       [FR_PBKDF2_PASSWORD - 5000 ]    = pap_auth_pbkdf2,
+       [FR_PBKDF2_PASSWORD]    = pap_auth_pbkdf2,
+       [FR_SHA2_PASSWORD]      = pap_auth_dummy,
+       [FR_SHA2_224_PASSWORD]  = pap_auth_sha2_224,
+       [FR_SHA2_256_PASSWORD]  = pap_auth_sha2_256,
+       [FR_SHA2_384_PASSWORD]  = pap_auth_sha2_384,
+       [FR_SHA2_512_PASSWORD]  = pap_auth_sha2_512,
+       [FR_SSHA1_PASSWORD]     = pap_auth_ssha1,
+       [FR_SSHA2_224_PASSWORD] = pap_auth_ssha2_224,
+       [FR_SSHA2_256_PASSWORD] = pap_auth_ssha2_256,
+       [FR_SSHA2_384_PASSWORD] = pap_auth_ssha2_384,
+       [FR_SSHA2_512_PASSWORD] = pap_auth_ssha2_512,
 
 #  if OPENSSL_VERSION_NUMBER >= 0x10101000L
-       [FR_SHA3_PASSWORD - 5000 ]      = pap_auth_sha_evp,
-       [FR_SSHA3_224_PASSWORD - 5000 ] = pap_auth_ssha_evp,
-       [FR_SSHA3_256_PASSWORD - 5000 ] = pap_auth_ssha_evp,
-       [FR_SSHA3_384_PASSWORD - 5000 ] = pap_auth_ssha_evp,
-       [FR_SSHA3_512_PASSWORD - 5000 ] = pap_auth_ssha_evp,
-#endif
+       [FR_SHA3_PASSWORD]      = pap_auth_dummy,
+       [FR_SHA3_224_PASSWORD]  = pap_auth_sha3_224,
+       [FR_SHA3_256_PASSWORD]  = pap_auth_sha3_256,
+       [FR_SHA3_384_PASSWORD]  = pap_auth_sha3_384,
+       [FR_SHA3_512_PASSWORD]  = pap_auth_sha3_512,
+       [FR_SSHA3_224_PASSWORD] = pap_auth_ssha3_224,
+       [FR_SSHA3_256_PASSWORD] = pap_auth_ssha3_256,
+       [FR_SSHA3_384_PASSWORD] = pap_auth_ssha3_384,
+       [FR_SSHA3_512_PASSWORD] = pap_auth_ssha3_512,
+#  endif
 #endif /* HAVE_OPENSSL_EVP_H */
 };
-#define MAX_KNOWN_PASSWORD (sizeof(auth_func_table) / sizeof(auth_func_table[0]))
 
 /*
  *     Authenticate the user via one of any well-known password.
  */
 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void *thread, REQUEST *request)
 {
-       rlm_pap_t const *inst = instance;
-       VALUE_PAIR      *known_good, *password;
-       rlm_rcode_t     rcode = RLM_MODULE_INVALID;
-       pap_auth_func_t auth_func;
+       rlm_pap_t const         *inst = instance;
+       VALUE_PAIR const        *known_good;
+       VALUE_PAIR              *password;
+       rlm_rcode_t             rcode = RLM_MODULE_INVALID;
+       pap_auth_func_t         auth_func;
+       TALLOC_CTX              *tmp_ctx;
 
        password = fr_pair_find_by_da(request->packet->vps, attr_user_password, TAG_ANY);
        if (!password) {
@@ -986,37 +859,35 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void
        }
 
        if (RDEBUG_ENABLED3) {
-               RDEBUG3("Login attempt with password \"%pV\" (%zd)",
-                       fr_box_strvalue_len(password->vp_strvalue, password->vp_length),
-                       password->vp_length);
+               RDEBUG3("Login attempt with %pP (%zd)", password, password->vp_length);
        } else {
                RDEBUG2("Login attempt with password");
        }
 
        /*
-        *      Normalise passwords, if it hasn't already been done.
-        *
-        *      This function returns the "known good" password, and
-        *      prefers to return Cleartext-Password over everything
-        *      else.
+        *      Holds any ephemeral attributes
         */
-       known_good = password_normalise(request, inst->normify);
-       if (!known_good) {
-               REDEBUG("No \"known good\" password found for user.");
-               REDEBUG("Some other module must authenticate the user.");
-               return RLM_MODULE_NOOP;
-       }
+       MEM(tmp_ctx = talloc_named_const(request, 0, "password_tmp_ctx"));
 
-       if ((known_good->da->attr < 5000) || (known_good->da->attr >= (5000 + MAX_KNOWN_PASSWORD)) ||
-           !auth_func_table[known_good->da->attr - 5000]) {
-               /*
-                *      It's a known password, but we don't know what it is.
-                */
-               REDEBUG("No \"known good\" password was found for user");
+       /*
+        *      Retrieve the normalised version of
+        *      the known_good password, without
+        *      mangling the current password attributes
+        *      in the request.
+        */
+       known_good = password_find(tmp_ctx, request,
+                                  pap_allowed_passwords, talloc_array_length(pap_allowed_passwords),
+                                  inst->normify);
+       if (!known_good) {
+               REDEBUG("No \"known good\" password found for user");
+               talloc_free(tmp_ctx);
                return RLM_MODULE_FAIL;
        }
 
-       auth_func = auth_func_table[known_good->da->attr - 5000];
+       rad_assert(known_good->da->attr < NUM_ELEMENTS(auth_func_table));
+
+       auth_func = auth_func_table[known_good->da->attr];
+       rad_assert(auth_func);
 
        if (RDEBUG_ENABLED3) {
                RDEBUG3("Comparing with \"known good\" %pP (%zu)", known_good, known_good->vp_length);
@@ -1040,6 +911,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void
        default:
                break;
        }
+       talloc_free(tmp_ctx);
 
        return rcode;
 }
@@ -1066,6 +938,62 @@ static int mod_bootstrap(void *instance, CONF_SECTION *conf)
        return 0;
 }
 
+static int mod_load(void)
+{
+       size_t  i, j = 0;
+       size_t  allowed = 0;
+
+       /*
+        *      Load the dictionaries early
+        */
+       if (fr_dict_autoload(rlm_pap_dict) < 0) {
+               PERROR("%s", __FUNCTION__);
+               return -1;
+       }
+       if (fr_dict_attr_autoload(rlm_pap_dict_attr) < 0) {
+               PERROR("%s", __FUNCTION__);
+               fr_dict_autofree(rlm_pap_dict);
+               return -1;
+       }
+
+       /*
+        *      Figure out how many password types we allow
+        */
+       for (i = 0; i < NUM_ELEMENTS(auth_func_table); i++) {
+               if (auth_func_table[i] == NULL) continue;
+
+               allowed++;
+       }
+
+       /*
+        *      Get a list of the DAs that match are allowed
+        *      functions.
+        */
+       pap_allowed_passwords = talloc_array(NULL, fr_dict_attr_t const *, allowed);
+       for (i = 0; i < NUM_ELEMENTS(auth_func_table); i++) {
+               fr_dict_attr_t const *password_da;
+
+               if (auth_func_table[i] == NULL) continue;
+
+               password_da = fr_dict_attr_child_by_num(attr_password_root, i);
+               if (!fr_cond_assert(password_da)) {
+                       ERROR("Could not resolve password attribute %zu", i);
+                       talloc_free(pap_allowed_passwords);
+                       return -1;
+               }
+
+               pap_allowed_passwords[j++] = password_da;
+       }
+
+       return 0;
+}
+
+static void mod_unload(void)
+{
+       talloc_free(pap_allowed_passwords);
+       fr_dict_autofree(rlm_pap_dict);
+}
+
 /*
  *     The module name should be the only globally exported symbol.
  *     That is, everything else should be 'static'.
@@ -1080,6 +1008,8 @@ module_t rlm_pap = {
        .magic          = RLM_MODULE_INIT,
        .name           = "pap",
        .inst_size      = sizeof(rlm_pap_t),
+       .onload         = mod_load,
+       .unload         = mod_unload,
        .config         = module_config,
        .bootstrap      = mod_bootstrap,
        .methods = {
index ecf23b8fab30c5a6527ae55aeb9fb035bd9ea921..b0325b2b751351f0024ed79ae0353fa1c79ff931 100644 (file)
@@ -102,6 +102,68 @@ update {
        &control: !* ANY
 }
 
+#
+#  Base64 of SHA2-384 password (in SHA2-Password)
+#
+update control {
+       &control:SHA2-Password := "%{hex:%{sha2_384:%{request:User-Password}}}"
+}
+
+pap.authorize
+pap.authenticate {
+       reject = 1
+}
+if (reject) {
+       test_fail
+}
+
+update {
+       &control: !* ANY
+}
+
+update control {
+       &Auth-Type := Accept
+}
+
+#
+#  Base64 of SHA2-256 password (in SHA2-256-Password)
+#
+update control {
+       &control:SHA2-256-Password := "%{hex:%{sha2_256:%{request:User-Password}}}"
+}
+
+pap.authorize
+pap.authenticate {
+       reject = 1
+}
+if (reject) {
+       test_fail
+}
+
+update {
+       &control: !* ANY
+}
+
+#
+#  Base64 of SHA2-224 password (in SHA2-224-Password - No hex armour)
+#
+update control {
+       &control:SHA2-224-Password := "%{sha2_224:%{request:User-Password}}"
+}
+
+pap.authorize
+pap.authenticate {
+       reject = 1
+}
+if (reject) {
+       test_fail
+}
+
+update {
+       &control: !* ANY
+}
+
+
 update control {
        &Auth-Type := Accept
 }