From: Arran Cudbard-Bell Date: Mon, 29 Jul 2019 12:38:29 +0000 (+0900) Subject: Move password normalisation into utility functions, and call those from rlm_chap X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e0ec439a82331c19e31163b852be53daae51b05;p=thirdparty%2Ffreeradius-server.git Move password normalisation into utility functions, and call those from rlm_chap This allows Password-With-Header to be used with rlm_chap, and in future, other modules, without calling rlm_pap first. --- diff --git a/src/lib/server/all.mk b/src/lib/server/all.mk index f6eec2d40e4..c36067ea07e 100644 --- a/src/lib/server/all.mk +++ b/src/lib/server/all.mk @@ -23,6 +23,7 @@ SOURCES := \ module.c \ paircmp.c \ pairmove.c \ + password.c \ pool.c \ rcode.c \ regex.c \ diff --git a/src/lib/server/base.h b/src/lib/server/base.h index 297ed3cfb90..f3c8a20f113 100644 --- a/src/lib/server/base.h +++ b/src/lib/server/base.h @@ -51,6 +51,7 @@ RCSIDH(base_h, "$Id$") #include #include #include +#include #include #include #include diff --git a/src/lib/server/password.c b/src/lib/server/password.c new file mode 100644 index 00000000000..11dd1f73b75 --- /dev/null +++ b/src/lib/server/password.c @@ -0,0 +1,298 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file lib/servers/password.c + * @brief Password normalisation functions + * + * @copyright 2019 The FreeRADIUS server project + * @copyright 2019 Arran Cudbard-Bell \ + */ +RCSID("$Id$") + +#include + +#include +#include +#include + +typedef enum { + NORMALISED_NOTHING = 0, + NORMALISED_B64, + NORMALISED_HEX +} normalise_t; + +static FR_NAME_NUMBER const normalise_table[] = { + { "nothing", NORMALISED_NOTHING }, + { "base64", NORMALISED_B64 }, + { "hex", NORMALISED_HEX }, + { NULL , -1 } +}; + +static ssize_t normify(normalise_t *action, uint8_t *buffer, size_t bufflen, + char const *known_good, size_t len, size_t min_len) +{ + if (min_len >= bufflen) return 0; /* paranoia */ + + /* + * Hex encoding. Length is even, and it's greater than + * twice the minimum length. + */ + if (!(len & 0x01) && len >= (2 * min_len)) { + size_t decoded; + + decoded = fr_hex2bin(buffer, bufflen, known_good, len); + if (decoded == (len >> 1)) { + if (action) *action = NORMALISED_HEX; + return decoded; + } + } + + /* + * Base 64 encoding. It's at least 4/3 the original size, + * and we want to avoid division... + */ + if ((len * 3) >= ((min_len * 4))) { + ssize_t decoded; + + decoded = fr_base64_decode(buffer, bufflen, known_good, len); + if (decoded < 0) return 0; + if (decoded >= (ssize_t) min_len) { + if (action) *action = NORMALISED_B64; + return decoded; + } + } + + /* + * Else unknown encoding, or already binary. Leave it. + */ + if (action) *action = NORMALISED_NOTHING; + return 0; +} + +/** Hex or base64 or bin auto-discovery + * + * Here we try and autodiscover what encoding was used for the password/hash, and + * convert it back to binary or plaintext. + * + * @note Earlier versions used a 0x prefix as a hard indicator that the string was + * hex encoded, and would fail if the 0x was present but the string didn't + * consist of hexits. The base64 char set is a superset of hex, and it was + * observed in the wild, that occasionally base64 encoded data really could + * start with 0x. That's why min_len (and decodability) are used as the + * only heuristics now. + * + * @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. + * - A new normalised password pair. + */ +VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const *known_good, size_t min_len) +{ + uint8_t buffer[256]; + ssize_t decoded; + VALUE_PAIR *out; + normalise_t normalised; + + if (min_len >= sizeof(buffer)) return NULL; /* paranoia */ + + switch (known_good->da->type) { + case FR_TYPE_OCTETS: + decoded = normify(&normalised, buffer, sizeof(buffer), + (char const *)known_good->vp_octets, known_good->vp_length, min_len); + break; + + case FR_TYPE_STRING: + decoded = normify(&normalised, buffer, sizeof(buffer), + known_good->vp_strvalue, known_good->vp_length, min_len); + break; + + default: + return NULL; + } + + if (normalised != NORMALISED_NOTHING) { + RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes", + known_good->da->name, fr_int2str(normalise_table, normalised, 0), + 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); + } + + /* + * Else unknown encoding, or already binary. Leave it. + */ + return NULL; +} + +/** 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 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 attributes 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. + * - 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) +{ + char const *p, *q, *end; + + uint8_t n1[256], n2[256]; + ssize_t decoded; + + char header[128]; + normalise_t normalised; + + int i; + + VALUE_PAIR *new; + + VP_VERIFY(known_good); + + /* + * Ensure this is only ever called with a + * string type attribute. + */ + rad_assert(known_good->da->type == FR_TYPE_STRING); + + p = known_good->vp_strvalue; + end = p + known_good->vp_length; + + /* + * Only allow one additional level of + * normification and header parsing. + */ + 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; + } + + p = q + 1; + + /* + * Try and base64 decode, and if we can + * use the decoded value. + * + * FIXME: Should pass in min length for + * password hash da represents. + */ + 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_int2str(normalise_table, normalised, 0), + (end - p), decoded); + p = (char const *)n1; + end = p + decoded; + } + + 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; + + case FR_TYPE_STRING: + fr_pair_value_bstrncpy(new, (uint8_t const *)p, end - p); + break; + + default: + if (!fr_cond_assert(0)) return NULL; + } + return new; + } + + /* + * 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_int2str(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; + continue; + } + } + + break; + } + + + if (RDEBUG_ENABLED3) { + RDEBUG3("No {...} in &%pP, re-writing to %s", known_good, def->name); + } else { + RDEBUG2("No {...} in &%s, re-writing to %s", known_good->da->name, def->name); + } + +unknown_header: + new = fr_pair_afrom_da(request, def); + fr_pair_value_bstrncpy(new, p, end - p); + + return new; +} diff --git a/src/lib/server/password.h b/src/lib/server/password.h new file mode 100644 index 00000000000..ea3736f3743 --- /dev/null +++ b/src/lib/server/password.h @@ -0,0 +1,41 @@ +#pragma once +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file lib/servers/password.h + * @brief Password normalisation functions + * + * @copyright 2019 The FreeRADIUS server project + * @copyright 2019 Arran Cudbard-Bell \ + */ +RCSIDH(password_h, "$Id$") + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef ssize_t(*password_header_lookup_t)(fr_dict_attr_t const **out, char const *header); + +VALUE_PAIR *password_normify(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR const *known_good, size_t min_len); + +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); +#ifdef __cplusplus +} +#endif diff --git a/src/modules/rlm_chap/rlm_chap.c b/src/modules/rlm_chap/rlm_chap.c index a410bfb499f..612f780c4a7 100644 --- a/src/modules/rlm_chap/rlm_chap.c +++ b/src/modules/rlm_chap/rlm_chap.c @@ -25,6 +25,7 @@ RCSID("$Id$") #include +#include #include typedef struct { @@ -43,7 +44,10 @@ fr_dict_autoload_t rlm_chap_dict[] = { }; static fr_dict_attr_t const *attr_auth_type; +static fr_dict_attr_t const *attr_proxy_to_realm; +static fr_dict_attr_t const *attr_realm; 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; @@ -52,7 +56,11 @@ static fr_dict_attr_t const *attr_user_password; 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_proxy_to_realm, .name = "Proxy-To-Realm", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, + { .out = &attr_realm, .name = "Realm", .type = FR_TYPE_STRING, .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,23 +68,82 @@ fr_dict_attr_autoload_t rlm_chap_dict_attr[] = { { NULL } }; +static const FR_NAME_NUMBER header_names[] = { + { "{clear}", FR_CLEARTEXT_PASSWORD }, + { "{cleartext}", FR_CLEARTEXT_PASSWORD }, + { NULL, 0 } +}; + +static ssize_t chap_password_header(fr_dict_attr_t const **out, char const *header) +{ + switch (fr_str2int(header_names, header, 0)) { + case FR_CLEARTEXT_PASSWORD: + *out = attr_cleartext_password; + return strlen(header); + + default: + *out = NULL; + return -1; + } +} + static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_chap_t *inst = instance; VALUE_PAIR *vp; + VALUE_PAIR *known_good; + /* + * This case means the warnings below won't be printed + * unless there's a CHAP-Password in the request. + */ if (!fr_pair_find_by_da(request->packet->vps, attr_chap_password, TAG_ANY)) return RLM_MODULE_NOOP; + known_good = fr_pair_find_by_da(request->control, attr_cleartext_password, TAG_ANY); + if (!known_good) { + VALUE_PAIR *new; + + known_good = fr_pair_find_by_da(request->control, attr_password_with_header, TAG_ANY); + if (!known_good) { + /* + * Likely going to be proxied. Avoid printing + * warning message. + */ + if (fr_pair_find_by_da(request->control, attr_realm, TAG_ANY) || + (fr_pair_find_by_da(request->control, attr_proxy_to_realm, TAG_ANY))) { + return RLM_MODULE_NOOP; + } + + RWDEBUG("No \"known good\" password found for the user. Not setting Auth-Type"); + RWDEBUG("Authentication will fail unless a \"known good\" password is available"); + + return RLM_MODULE_NOOP; + } + + /* + * Normalise Password-With-Header + */ + MEM(new = password_normify_with_header(request, request, known_good, + chap_password_header, attr_cleartext_password)); + if (RDEBUG_ENABLED3) { + RDEBUG3("Noramlized &control:%pP -> &control:%pP", known_good, new); + } else { + RDEBUG2("Normalized &control:%s -> &control:%s", known_good->da->name, new->da->name); + } + RDEBUG2("Removing &control:%s", known_good->da->name); + fr_pair_delete_by_da(&request->control, attr_password_with_header); + fr_pair_add(&request->control, new); + } + /* * Create the CHAP-Challenge if it wasn't already in the packet. * * This is so that the rest of the code does not need to * understand CHAP. */ - vp = fr_pair_find_by_da(request->packet->vps, attr_chap_challenge, TAG_ANY); if (!vp) { - RDEBUG2("Creating CHAP-Challenge from the request authenticator"); + RDEBUG2("Creating &%s from request authenticator", attr_chap_challenge->name); MEM(vp = fr_pair_afrom_da(request->packet, attr_chap_challenge)); fr_pair_value_memcpy(vp, request->packet->vector, sizeof(request->packet->vector), true); @@ -88,7 +155,6 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t return RLM_MODULE_OK; } - /* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user @@ -97,7 +163,7 @@ 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 *password, *chap; + VALUE_PAIR *known_good, *chap; uint8_t pass_str[FR_MAX_STRING_LEN]; if (!request->username) { @@ -122,8 +188,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUS return RLM_MODULE_INVALID; } - password = fr_pair_find_by_da(request->control, attr_cleartext_password, TAG_ANY); - if (password == NULL) { + known_good = fr_pair_find_by_da(request->control, attr_cleartext_password, TAG_ANY); + if (known_good == NULL) { if (fr_pair_find_by_da(request->control, attr_user_password, TAG_ANY) != NULL){ REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); REDEBUG("!!! Please update your configuration so that the \"known !!!"); @@ -138,15 +204,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, UNUS return RLM_MODULE_FAIL; } - fr_radius_encode_chap_password(pass_str, request->packet, chap->vp_octets[0], password); + fr_radius_encode_chap_password(pass_str, request->packet, chap->vp_octets[0], known_good); if (RDEBUG_ENABLED3) { uint8_t const *p; size_t length; VALUE_PAIR *vp; - RDEBUG3("Comparing with \"known good\" &control:Cleartext-Password value \"%s\"", - password->vp_strvalue); + RDEBUG3("Comparing with \"known good\" &control:%pP", known_good); vp = fr_pair_find_by_da(request->packet->vps, attr_chap_challenge, TAG_ANY); if (vp) { diff --git a/src/modules/rlm_pap/rlm_pap.c b/src/modules/rlm_pap/rlm_pap.c index 49063c57567..a56b305d83a 100644 --- a/src/modules/rlm_pap/rlm_pap.c +++ b/src/modules/rlm_pap/rlm_pap.c @@ -29,6 +29,7 @@ USES_APPLE_DEPRECATED_API #include #include #include +#include #include #include #include @@ -73,8 +74,8 @@ static fr_dict_attr_t const *attr_auth_type; static fr_dict_attr_t const *attr_proxy_to_realm; static fr_dict_attr_t const *attr_realm; -static fr_dict_attr_t const *attr_password_with_header; 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; @@ -107,8 +108,8 @@ fr_dict_attr_autoload_t rlm_pap_dict_attr[] = { { .out = &attr_proxy_to_realm, .name = "Proxy-To-Realm", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, { .out = &attr_realm, .name = "Realm", .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_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 }, @@ -176,7 +177,7 @@ static const FR_NAME_NUMBER header_names[] = { { "{x-nthash}", FR_NT_PASSWORD }, { "{ns-mta-md5}", FR_NS_MTA_MD5_PASSWORD }, { "{x- orcllmv}", FR_LM_PASSWORD }, - { "{X- orclntv}", FR_NT_PASSWORD }, + { "X- orclntv}", FR_NT_PASSWORD }, { NULL, 0 } }; @@ -187,7 +188,7 @@ static const FR_NAME_NUMBER pbkdf2_crypt_names[] = { { "HMACSHA2+256", FR_SSHA2_256_PASSWORD }, { "HMACSHA2+384", FR_SSHA2_384_PASSWORD }, { "HMACSHA2+512", FR_SSHA2_512_PASSWORD }, -#if OPENSSL_VERSION_NUMBER >= 0x10101000L +# if OPENSSL_VERSION_NUMBER >= 0x10101000L { "HMACSHA3+224", FR_SSHA3_224_PASSWORD }, { "HMACSHA3+256", FR_SSHA3_256_PASSWORD }, { "HMACSHA3+384", FR_SSHA3_384_PASSWORD }, @@ -205,276 +206,91 @@ static const FR_NAME_NUMBER pbkdf2_passlib_names[] = { }; #endif -/** Hex or base64 or bin auto-discovery - * - * Here we try and autodiscover what encoding was used for the password/hash, and - * convert it back to binary or plaintext. - * - * @note Earlier versions used a 0x prefix as a hard indicator that the string was - * hex encoded, and would fail if the 0x was present but the string didn't - * consist of hexits. The base64 char set is a superset of hex, and it was - * observed in the wild, that occasionally base64 encoded data really could - * start with 0x. That's why min_len (and decodability) are used as the - * only heuristics now. - * - * @param[in] request The current request. - * @param[in,out] known_good password to normify. - * @param[in] min_len we expect the decoded version to be. - */ -static void normify(REQUEST *request, VALUE_PAIR *known_good, size_t min_len) +static ssize_t pap_password_header(fr_dict_attr_t const **out, char const *header) { - uint8_t buffer[256]; - - if (min_len >= sizeof(buffer)) return; /* paranoia */ - - rad_assert((known_good->da->type == FR_TYPE_OCTETS) || (known_good->da->type == FR_TYPE_STRING)); - - /* - * Hex encoding. Length is even, and it's greater than - * twice the minimum length. - */ - if (!(known_good->vp_length & 0x01) && known_good->vp_length >= (2 * min_len)) { - bool tainted = known_good->data.tainted; - size_t decoded; - - - decoded = fr_hex2bin(buffer, sizeof(buffer), known_good->vp_strvalue, known_good->vp_length); - if (decoded == (known_good->vp_length >> 1)) { - RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes", - known_good->da->name, known_good->vp_length, decoded); - fr_pair_value_memcpy(known_good, buffer, decoded, tainted); - return; - } - } - - /* - * Base 64 encoding. It's at least 4/3 the original size, - * and we want to avoid division... - */ - if ((known_good->vp_length * 3) >= ((min_len * 4))) { - ssize_t decoded; - - decoded = fr_base64_decode(buffer, sizeof(buffer), known_good->vp_strvalue, known_good->vp_length); - if (decoded < 0) return; - if (decoded >= (ssize_t) min_len) { - bool tainted = known_good->data.tainted; - - RDEBUG2("Normalizing %s from base64 encoding, %zu bytes -> %zu bytes", - known_good->da->name, known_good->vp_length, decoded); - fr_pair_value_memcpy(known_good, buffer, decoded, tainted); - return; - } - } - - /* - * Else unknown encoding, or already binary. Leave it. - */ -} - -/** 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 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 attributes in. - * @param[in] request Current request. - * @param[in] known_good Password-With-Header attribute to convert. - * @return - * - New #VALUE_PAIR on success. - * - NULL on error. - */ -static VALUE_PAIR *normify_with_header(TALLOC_CTX *ctx, REQUEST *request, - VALUE_PAIR *known_good, UNUSED VALUE_PAIR const *password) -{ - char const *p, *q; - size_t len; - - uint8_t digest[256]; - ssize_t decoded; - - char buffer[256]; - - VALUE_PAIR *new; - - VP_VERIFY(known_good); - - /* - * Ensure this is only ever called with a - * string type attribute. - */ - rad_assert(known_good->da->type == FR_TYPE_STRING); - -redo: - p = known_good->vp_strvalue; - len = known_good->vp_length; - - /* - * Has a header {...} prefix - */ - q = strchr(p, '}'); - if (q) { - size_t hlen; - fr_dict_attr_t const *da; - - hlen = (q + 1) - p; - if (hlen >= sizeof(buffer)) { - REDEBUG("Password header too long. Got %zu bytes must be less than %zu bytes", - hlen, sizeof(buffer)); - return NULL; - } - - memcpy(buffer, p, hlen); - buffer[hlen] = '\0'; - - /* - * The data after the '}' may be binary, so we copy it via - * memcpy. BUT it might be a string (or used as one), so - * we ensure that there's a trailing zero, too. - */ - switch (fr_str2int(header_names, buffer, 0)) { - case FR_CLEARTEXT_PASSWORD: - da = attr_cleartext_password; - break; - - case FR_MD5_PASSWORD: - da = attr_md5_password; - break; - - case FR_SMD5_PASSWORD: - da = attr_smd5_password; - break; - - case FR_CRYPT_PASSWORD: - da = attr_crypt_password; - break; - - case FR_SHA2_PASSWORD: - da = attr_sha2_password; - break; - - case FR_SSHA2_224_PASSWORD: - da = attr_ssha2_224_password; - break; - - case FR_SSHA2_256_PASSWORD: - da = attr_ssha2_256_password; - break; - - case FR_SSHA2_384_PASSWORD: - da = attr_ssha2_384_password; - break; - - case FR_SSHA2_512_PASSWORD: - da = attr_ssha2_512_password; - break; + switch (fr_str2int(header_names, header, 0)) { + case FR_CLEARTEXT_PASSWORD: + *out = attr_cleartext_password; + break; - case FR_SSHA3_224_PASSWORD: - da = attr_ssha3_224_password; - break; + case FR_MD5_PASSWORD: + *out = attr_md5_password; + break; - case FR_SSHA3_256_PASSWORD: - da = attr_ssha3_256_password; - break; + case FR_SMD5_PASSWORD: + *out = attr_smd5_password; + break; - case FR_SSHA3_384_PASSWORD: - da = attr_ssha3_384_password; - break; + case FR_CRYPT_PASSWORD: + *out = attr_crypt_password; + break; - case FR_SSHA3_512_PASSWORD: - da = attr_ssha3_512_password; - break; + case FR_SHA2_PASSWORD: + *out = attr_sha2_password; + break; - case FR_PBKDF2_PASSWORD: - da = attr_pbkdf2_password; - break; + case FR_SSHA2_224_PASSWORD: + *out = attr_ssha2_224_password; + break; - case FR_SHA_PASSWORD: - da = attr_sha_password; - break; + case FR_SSHA2_256_PASSWORD: + *out = attr_ssha2_256_password; + break; - case FR_SSHA_PASSWORD: - da = attr_ssha_password; - break; + case FR_SSHA2_384_PASSWORD: + *out = attr_ssha2_384_password; + break; - case FR_NS_MTA_MD5_PASSWORD: - da = attr_ns_mta_md5_password; - break; + case FR_SSHA2_512_PASSWORD: + *out = attr_ssha2_512_password; + break; - case FR_LM_PASSWORD: - da = attr_lm_password; - break; + case FR_SSHA3_224_PASSWORD: + *out = attr_ssha3_224_password; + break; - case FR_NT_PASSWORD: - da = attr_nt_password; - break; + case FR_SSHA3_256_PASSWORD: + *out = attr_ssha3_256_password; + break; - default: - if (RDEBUG_ENABLED3) { - RDEBUG3("Unknown header {%s} in %pP, re-writing to " - "Cleartext-Password", buffer, known_good); - } else { - RDEBUG2("Unknown header {%s} in Password-With-Header, re-writing to " - "Cleartext-Password", buffer); - } - goto unknown_header; - } + case FR_SSHA3_384_PASSWORD: + *out = attr_ssha3_384_password; + break; - new = fr_pair_afrom_da(ctx, da); - switch (da->type) { - case FR_TYPE_OCTETS: - fr_pair_value_memcpy(new, (uint8_t const *)q + 1, len - hlen, true); - break; + case FR_SSHA3_512_PASSWORD: + *out = attr_ssha3_512_password; + break; - case FR_TYPE_STRING: - fr_pair_value_bstrncpy(new, (uint8_t const *)q + 1, len - hlen); - break; + case FR_PBKDF2_PASSWORD: + *out = attr_pbkdf2_password; + break; - default: - if (!fr_cond_assert(0)) return NULL; - } + case FR_SHA_PASSWORD: + *out = attr_sha_password; + break; - if (RDEBUG_ENABLED3) { - RDEBUG3("Converted: &control:%pP -> &control:%pP", known_good, new); - } else { - RDEBUG2("Converted: &control:%s -> &control:%s", known_good->da->name, new->da->name); - } + case FR_SSHA_PASSWORD: + *out = attr_ssha_password; + break; - return new; - } + case FR_NS_MTA_MD5_PASSWORD: + *out = attr_ns_mta_md5_password; + break; - /* - * Doesn't have a header {...} prefix - * - * See if it's base64, if it is, decode it and check again! - */ - decoded = fr_base64_decode(digest, sizeof(digest), known_good->vp_strvalue, len); - if ((decoded > 0) && (digest[0] == '{') && (memchr(digest, '}', decoded) != NULL)) { - RDEBUG2("Normalizing %s from base64 encoding, %zu bytes -> %zu bytes", - known_good->da->name, known_good->vp_length, decoded); - /* - * Password-With-Header is a string attribute. - * Even though we're handling binary data, the buffer - * must be \0 terminated. - */ - fr_pair_value_bstrncpy(known_good, digest, decoded); + case FR_LM_PASSWORD: + *out = attr_lm_password; + break; - goto redo; - } + case FR_NT_PASSWORD: + *out = attr_nt_password; + break; - if (RDEBUG_ENABLED3) { - RDEBUG3("No {...} in &%pP, re-writing to Cleartext-Password", known_good); - } else { - RDEBUG2("No {...} in &%s, re-writing to Cleartext-Password", known_good->da->name); + default: + *out = NULL; + return -1; } -unknown_header: - new = fr_pair_afrom_da(request, attr_cleartext_password); - fr_pair_value_strcpy(new, known_good->vp_strvalue); - - return new; + return strlen(header); } /* @@ -489,6 +305,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t bool found_pw = false; VALUE_PAIR *known_good, *password; fr_cursor_t cursor; + size_t normify_min_len = 0; password = fr_pair_find_by_da(request->packet->vps, attr_user_password, TAG_ANY); if (!password) { @@ -516,16 +333,22 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t */ if (fr_pair_find_by_da(request->control, attr_cleartext_password, TAG_ANY)) { RWDEBUG("Config already contains a \"known good\" password " - "(&control:Cleartext-Password). Ignoring &control:Password-With-Header"); + "(&control:%s). Ignoring &control:%s", + attr_cleartext_password->name, known_good->da->name); break; } - new = normify_with_header(request, request, known_good, password); - if (new) fr_cursor_append(&cursor, new); /* inserts at the end of the list */ - - RDEBUG2("Removing &control:Password-With-Header"); - known_good = fr_cursor_remove(&cursor); /* advances the cursor for us */ - talloc_free(known_good); + MEM(new = password_normify_with_header(request, request, known_good, + pap_password_header, + attr_cleartext_password)); + if (RDEBUG_ENABLED3) { + RDEBUG3("Normalized &control:%pP -> &control:%pP", known_good, new); + } else { + RDEBUG2("Normalized &control:%s -> &control:%s", known_good->da->name, new->da->name); + } + RDEBUG2("Removing &control:%s", known_good->da->name); + fr_cursor_free_item(&cursor); /* advances the cursor for us */ + fr_cursor_append(&cursor, new); /* inserts at the end of the list */ found_pw = true; @@ -539,41 +362,41 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t (known_good->da == attr_smd5_password) || (known_good->da == attr_nt_password) || (known_good->da == attr_lm_password)) { - if (inst->normify) normify(request, known_good, 16); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 16; found_pw = true; } #ifdef HAVE_OPENSSL_EVP_H else if (known_good->da == attr_sha2_password) { - if (inst->normify) normify(request, known_good, 28); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 28; found_pw = true; } else if (known_good->da == attr_ssha2_224_password) { - if (inst->normify) normify(request, known_good, 28); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 28; found_pw = true; } else if (known_good->da == attr_ssha2_256_password) { - if (inst->normify) normify(request, known_good, 32); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 32; found_pw = true; } else if (known_good->da == attr_ssha2_384_password) { - if (inst->normify) normify(request, known_good, 48); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 48; found_pw = true; } else if (known_good->da == attr_ssha2_512_password) { - if (inst->normify) normify(request, known_good, 64); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 64; found_pw = true; } # if OPENSSL_VERSION_NUMBER >= 0x10101000L else if (known_good->da == attr_sha3_password) { - if (inst->normify) normify(request, known_good, 28); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 28; found_pw = true; } else if (known_good->da == attr_ssha3_224_password) { - if (inst->normify) normify(request, known_good, 28); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 28; found_pw = true; } else if (known_good->da == attr_ssha3_256_password) { - if (inst->normify) normify(request, known_good, 32); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 32; found_pw = true; } else if (known_good->da == attr_ssha3_384_password) { - if (inst->normify) normify(request, known_good, 48); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 48; found_pw = true; } else if (known_good->da == attr_ssha3_512_password) { - if (inst->normify) normify(request, known_good, 64); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 64; found_pw = true; } # endif @@ -583,9 +406,22 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *t #endif else if ((known_good->da == attr_sha_password) || (known_good->da == attr_ssha_password)) { - if (inst->normify) normify(request, known_good, 20); /* ensure it's in the right format */ + if (inst->normify) normify_min_len = 20; found_pw = true; } + + if (normify_min_len > 0) { + VALUE_PAIR *new; + + new = password_normify(request, request, known_good, normify_min_len); + if (new) { + fr_cursor_free_item(&cursor); /* free the old pasword */ + fr_cursor_append(&cursor, new); /* inserts at the end of the list */ + + known_good = fr_cursor_current(&cursor); + if (known_good) goto next; + } + } } /* @@ -641,12 +477,11 @@ 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(rlm_pap_t const *inst, REQUEST *request, +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) { uint8_t digest[MD5_DIGEST_LENGTH]; - if (inst->normify) normify(request, known_good, MD5_DIGEST_LENGTH); if (known_good->vp_length != MD5_DIGEST_LENGTH) { REDEBUG("\"known-good\" MD5 password has incorrect length, expected 16 got %zu", known_good->vp_length); return RLM_MODULE_INVALID; @@ -666,13 +501,12 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_md5(rlm_pap_t const *inst, REQUEST } -static rlm_rcode_t CC_HINT(nonnull) pap_auth_smd5(rlm_pap_t const *inst, REQUEST *request, +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) { fr_md5_ctx_t *md5_ctx; uint8_t digest[MD5_DIGEST_LENGTH]; - if (inst->normify) normify(request, known_good, MD5_DIGEST_LENGTH); if (known_good->vp_length <= MD5_DIGEST_LENGTH) { REDEBUG("\"known-good\" SMD5-Password has incorrect length, expected 16 got %zu", known_good->vp_length); return RLM_MODULE_INVALID; @@ -698,14 +532,12 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_smd5(rlm_pap_t const *inst, REQUEST return RLM_MODULE_OK; } -static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha(rlm_pap_t const *inst, REQUEST *request, +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) { fr_sha1_ctx sha1_context; uint8_t digest[SHA1_DIGEST_LENGTH]; - if (inst->normify) normify(request, known_good, SHA1_DIGEST_LENGTH); - if (known_good->vp_length != SHA1_DIGEST_LENGTH) { REDEBUG("\"known-good\" SHA1-password has incorrect length, expected 20 got %zu", known_good->vp_length); return RLM_MODULE_INVALID; @@ -726,14 +558,12 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha(rlm_pap_t const *inst, REQUEST return RLM_MODULE_OK; } -static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha(rlm_pap_t const *inst, REQUEST *request, +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) { fr_sha1_ctx sha1_context; uint8_t digest[SHA1_DIGEST_LENGTH]; - if (inst->normify) normify(request, known_good, SHA1_DIGEST_LENGTH); - if (known_good->vp_length <= SHA1_DIGEST_LENGTH) { REDEBUG("\"known-good\" SSHA-Password has incorrect length, expected > 20 got %zu", known_good->vp_length); return RLM_MODULE_INVALID; @@ -759,7 +589,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha(rlm_pap_t const *inst, REQUEST } #ifdef HAVE_OPENSSL_EVP_H -static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha_evp(rlm_pap_t const *inst, REQUEST *request, +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) { EVP_MD_CTX *ctx; @@ -768,8 +598,6 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha_evp(rlm_pap_t const *inst, REQU uint8_t digest[EVP_MAX_MD_SIZE]; unsigned int digest_len; - if (inst->normify) normify(request, known_good, SHA224_DIGEST_LENGTH); - if (known_good->da == attr_sha2_password) { /* * All the SHA-2 algorithms produce digests of different lengths, @@ -868,7 +696,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha_evp(rlm_pap_t const *inst, REQU return RLM_MODULE_OK; } -static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha_evp(rlm_pap_t const *inst, REQUEST *request, +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) { EVP_MD_CTX *ctx; @@ -918,13 +746,6 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha_evp(rlm_pap_t const *inst, REQ return RLM_MODULE_INVALID; } - /* - * Unlike plain SHA2/3 we already know what length - * to expect, so can be more specific with the - * minimum digest length. - */ - if (inst->normify) normify(request, known_good, min_len + 1); - 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); @@ -1238,7 +1059,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(rlm_pap_t const *inst, REQUEST *request, +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) { ssize_t len; @@ -1250,8 +1071,6 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_nt(rlm_pap_t const *inst, REQUEST * rad_assert(password != NULL); rad_assert(password->da == attr_user_password); - if (inst->normify) normify(request, known_good, MD4_DIGEST_LENGTH); - if (known_good->vp_length != MD4_DIGEST_LENGTH) { REDEBUG("\"known good\" NT-Password has incorrect length, expected 16 got %zu", known_good->vp_length); return RLM_MODULE_INVALID; @@ -1276,7 +1095,7 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_nt(rlm_pap_t const *inst, REQUEST * return RLM_MODULE_OK; } -static rlm_rcode_t CC_HINT(nonnull) pap_auth_lm(rlm_pap_t const *inst, REQUEST *request, +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) { uint8_t digest[MD4_DIGEST_LENGTH]; @@ -1285,8 +1104,6 @@ static rlm_rcode_t CC_HINT(nonnull) pap_auth_lm(rlm_pap_t const *inst, REQUEST * RDEBUG2("Comparing with \"known-good\" LM-Password"); - if (inst->normify) normify(request, known_good, MD4_DIGEST_LENGTH); - if (known_good->vp_length != MD4_DIGEST_LENGTH) { REDEBUG("\"known good\" LM-Password has incorrect length, expected 16 got %zu", known_good->vp_length); return RLM_MODULE_INVALID;