2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief Hashes plaintext passwords to compare against a prehashed reference.
22 * @copyright 2001-2012 The FreeRADIUS server project.
23 * @copyright 2012 Matthew Newton <matthew@newtoncomputing.co.uk>
24 * @copyright 2001 Kostas Kalevras <kkalev@noc.ntua.gr>
27 USES_APPLE_DEPRECATED_API
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/modules.h>
31 #include <freeradius-devel/base64.h>
32 #include <freeradius-devel/rad_assert.h>
36 #include "../../include/md5.h"
37 #include "../../include/sha1.h"
39 #ifdef HAVE_OPENSSL_EVP_H
40 # include <openssl/evp.h>
44 * Define a structure for our module configuration.
46 * These variables do not need to be in a structure, but it's
47 * a lot cleaner to do so, and a pointer to the structure can
48 * be used as the instance handle.
50 typedef struct rlm_pap_t {
51 char const *name; /* CONF_SECTION->name, not strdup'd */
57 * A mapping of configuration file names to internal variables.
59 * Note that the string is dynamically allocated, so it MUST
60 * be freed. When the configuration file parse re-reads the string,
61 * it free's the old one, and strdup's the new one, placing the pointer
62 * to the strdup'd string into 'config.string'. This gets around
65 static const CONF_PARSER module_config[] = {
66 { "normalise", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_pap_t, normify), "yes" },
67 CONF_PARSER_TERMINATOR
72 * For auto-header discovery.
74 * @note Header comparison is case insensitive.
76 static const FR_NAME_NUMBER header_names[] = {
77 { "{clear}", PW_CLEARTEXT_PASSWORD },
78 { "{cleartext}", PW_CLEARTEXT_PASSWORD },
79 { "{md5}", PW_MD5_PASSWORD },
80 { "{base64_md5}", PW_MD5_PASSWORD },
81 { "{smd5}", PW_SMD5_PASSWORD },
82 { "{crypt}", PW_CRYPT_PASSWORD },
83 #ifdef HAVE_OPENSSL_EVP_H
85 * It'd make more sense for the headers to be
86 * ssha2-* with SHA3 coming soon but we're at
87 * the mercy of directory implementors.
89 { "{sha2}", PW_SHA2_PASSWORD },
90 { "{sha224}", PW_SHA2_PASSWORD },
91 { "{sha256}", PW_SHA2_PASSWORD },
92 { "{sha384}", PW_SHA2_PASSWORD },
93 { "{sha512}", PW_SHA2_PASSWORD },
94 { "{ssha224}", PW_SSHA2_224_PASSWORD },
95 { "{ssha256}", PW_SSHA2_256_PASSWORD },
96 { "{ssha384}", PW_SSHA2_384_PASSWORD },
97 { "{ssha512}", PW_SSHA2_512_PASSWORD },
99 { "{sha}", PW_SHA_PASSWORD },
100 { "{ssha}", PW_SSHA_PASSWORD },
101 { "{md4}", PW_NT_PASSWORD },
102 { "{nt}", PW_NT_PASSWORD },
103 { "{nthash}", PW_NT_PASSWORD },
104 { "{x-nthash}", PW_NT_PASSWORD },
105 { "{ns-mta-md5}", PW_NS_MTA_MD5_PASSWORD },
106 { "{x- orcllmv}", PW_LM_PASSWORD },
107 { "{X- orclntv}", PW_NT_PASSWORD },
111 static int mod_instantiate(CONF_SECTION *conf, void *instance)
113 rlm_pap_t *inst = instance;
116 inst->name = cf_section_name2(conf);
118 inst->name = cf_section_name1(conf);
121 dval = dict_valbyname(PW_AUTH_TYPE, 0, inst->name);
123 inst->auth_type = dval->value;
131 /** Hex or base64 or bin auto-discovery
133 * Here we try and autodiscover what encoding was used for the password/hash, and
134 * convert it back to binary or plaintext.
136 * @note Earlier versions used a 0x prefix as a hard indicator that the string was
137 * hex encoded, and would fail if the 0x was present but the string didn't
138 * consist of hexits. The base64 char set is a superset of hex, and it was
139 * observed in the wild, that occasionally base64 encoded data really could
140 * start with 0x. That's why min_len (and decodability) are used as the
141 * only heuristics now.
143 * @param[in] request Current request.
144 * @param[in,out] vp to normify.
145 * @param[in] min_len we expect the decoded version to be.
147 static void normify(REQUEST *request, VALUE_PAIR *vp, size_t min_len)
151 if (min_len >= sizeof(buffer)) return; /* paranoia */
153 rad_assert((vp->da->type == PW_TYPE_OCTETS) || (vp->da->type == PW_TYPE_STRING));
156 * Hex encoding. Length is even, and it's greater than
157 * twice the minimum length.
159 if (!(vp->vp_length & 0x01) && vp->vp_length >= (2 * min_len)) {
162 decoded = fr_hex2bin(buffer, sizeof(buffer), vp->vp_strvalue, vp->vp_length);
163 if (decoded == (vp->vp_length >> 1)) {
164 RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes",
165 vp->da->name, vp->vp_length, decoded);
166 fr_pair_value_memcpy(vp, buffer, decoded);
172 * Base 64 encoding. It's at least 4/3 the original size,
173 * and we want to avoid division...
175 if ((vp->vp_length * 3) >= ((min_len * 4))) {
177 decoded = fr_base64_decode(buffer, sizeof(buffer), vp->vp_strvalue, vp->vp_length);
178 if (decoded < 0) return;
179 if (decoded >= (ssize_t) min_len) {
180 RDEBUG2("Normalizing %s from base64 encoding, %zu bytes -> %zu bytes",
181 vp->da->name, vp->vp_length, decoded);
182 fr_pair_value_memcpy(vp, buffer, decoded);
188 * Else unknown encoding, or already binary. Leave it.
192 /** Convert a Password-With-Header attribute to the correct type
194 * Attribute may be base64 encoded, in which case it will be decoded
195 * first, then evaluated.
197 * @note The buffer for octets types\ attributes is extended by one byte
198 * and '\0' terminated, to allow it to be used as a char buff.
200 * @param request Current request.
201 * @param vp Password-With-Header attribute to convert.
202 * @return a new VALUE_PAIR on success, NULL on error.
204 static VALUE_PAIR *normify_with_header(REQUEST *request, VALUE_PAIR *vp)
210 uint8_t digest[257]; /* +1 for \0 */
220 * Ensure this is only ever called with a
221 * string type attribute.
223 rad_assert(vp->da->type == PW_TYPE_STRING);
230 * Has a header {...} prefix
237 if (hlen >= sizeof(buffer)) {
238 REDEBUG("Password header too long. Got %zu bytes must be less than %zu bytes",
239 hlen, sizeof(buffer));
243 memcpy(buffer, p, hlen);
246 attr = fr_str2int(header_names, buffer, 0);
248 if (RDEBUG_ENABLED3) {
249 RDEBUG3("Unknown header {%s} in Password-With-Header = \"%s\", re-writing to "
250 "Cleartext-Password", buffer, vp->vp_strvalue);
252 RDEBUG("Unknown header {%s} in Password-With-Header, re-writing to "
253 "Cleartext-Password", buffer);
259 * The data after the '}' may be binary, so we copy it via
260 * memcpy. BUT it might be a string (or used as one), so
261 * we ensure that there's a trailing zero, too.
263 new = fr_pair_afrom_num(request, attr, 0);
264 if (new->da->type == PW_TYPE_OCTETS) {
265 fr_pair_value_memcpy(new, (uint8_t const *) q + 1, (len - hlen) + 1);
266 new->vp_length = (len - hlen); /* lie about the length */
268 fr_pair_value_strcpy(new, q + 1);
271 if (RDEBUG_ENABLED3) {
272 char *old_value, *new_value;
274 old_value = vp_aprints_value(request, vp, '\'');
275 new_value = vp_aprints_value(request, new, '\'');
276 RDEBUG3("Converted: &control:%s = '%s' -> &control:%s = '%s'",
277 vp->da->name, old_value, new->da->name, new_value);
278 talloc_free(old_value);
279 talloc_free(new_value);
281 RDEBUG2("Converted: &control:%s -> &control:%s", vp->da->name, new->da->name);
288 * Doesn't have a header {...} prefix
290 * See if it's base64, if it is, decode it and check again!
292 decoded = fr_base64_decode(digest, sizeof(digest) - 1, vp->vp_strvalue, len);
293 if ((decoded > 0) && (digest[0] == '{') && (memchr(digest, '}', decoded) != NULL)) {
294 RDEBUG2("Normalizing %s from base64 encoding, %zu bytes -> %zu bytes",
295 vp->da->name, vp->vp_length, decoded);
297 * Password-With-Header is a string attribute.
298 * Even though we're handling binary data, the buffer
299 * must be \0 terminated.
301 digest[decoded] = '\0';
302 fr_pair_value_memcpy(vp, digest, decoded + 1);
303 vp->vp_length = decoded; /* lie about the length */
308 if (RDEBUG_ENABLED3) {
309 RDEBUG3("No {...} in Password-With-Header = \"%s\", re-writing to "
310 "Cleartext-Password", vp->vp_strvalue);
312 RDEBUG("No {...} in Password-With-Header, re-writing to Cleartext-Password");
316 new = fr_pair_afrom_num(request, PW_CLEARTEXT_PASSWORD, 0);
317 fr_pair_value_strcpy(new, vp->vp_strvalue);
323 * Authorize the user for PAP authentication.
325 * This isn't strictly necessary, but it does make the
326 * server simpler to configure.
328 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
330 rlm_pap_t *inst = instance;
331 bool auth_type = false;
332 bool found_pw = false;
336 for (vp = fr_cursor_init(&cursor, &request->config);
338 vp = fr_cursor_next(&cursor)) {
341 switch (vp->da->attr) {
342 case PW_USER_PASSWORD: /* deprecated */
343 RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
344 RWDEBUG("!!! Ignoring control:User-Password. Update your !!!");
345 RWDEBUG("!!! configuration so that the \"known good\" clear text !!!");
346 RWDEBUG("!!! password is in Cleartext-Password and NOT in !!!");
347 RWDEBUG("!!! User-Password. !!!");
348 RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
351 case PW_PASSWORD_WITH_HEADER: /* preferred */
356 * Password already exists: use that instead of this one.
358 if (fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) {
359 RWDEBUG("Config already contains a \"known good\" password "
360 "(&control:Cleartext-Password). Ignoring &config:Password-With-Header");
364 new = normify_with_header(request, vp);
365 if (new) fr_cursor_insert(&cursor, new); /* inserts at the end of the list */
367 RDEBUG2("Removing &control:Password-With-Header");
368 vp = fr_cursor_remove(&cursor); /* advances the cursor for us */
373 vp = fr_cursor_current(&cursor);
378 case PW_CLEARTEXT_PASSWORD:
379 case PW_CRYPT_PASSWORD:
380 case PW_NS_MTA_MD5_PASSWORD:
382 break; /* don't touch these */
384 case PW_MD5_PASSWORD:
385 case PW_SMD5_PASSWORD:
389 normify(request, vp, 16); /* ensure it's in the right format */
394 #ifdef HAVE_OPENSSL_EVP_H
395 case PW_SHA2_PASSWORD:
397 normify(request, vp, 28); /* ensure it's in the right format */
402 case PW_SSHA2_224_PASSWORD:
404 normify(request, vp, 28); /* ensure it's in the right format */
409 case PW_SSHA2_256_PASSWORD:
411 normify(request, vp, 32); /* ensure it's in the right format */
416 case PW_SSHA2_384_PASSWORD:
418 normify(request, vp, 48); /* ensure it's in the right format */
423 case PW_SSHA2_512_PASSWORD:
425 normify(request, vp, 64); /* ensure it's in the right format */
431 case PW_SHA_PASSWORD:
432 case PW_SSHA_PASSWORD:
434 normify(request, vp, 20); /* ensure it's in the right format */
440 * If it's proxied somewhere, don't complain
441 * about not having passwords or Auth-Type.
443 case PW_PROXY_TO_REALM:
445 REALM *realm = realm_find(vp->vp_strvalue);
446 if (realm && realm->auth_pool) {
447 return RLM_MODULE_NOOP;
456 * Auth-Type := Accept
457 * Auth-Type := Reject
459 if ((vp->vp_integer == 254) ||
460 (vp->vp_integer == 4)) {
466 break; /* ignore it */
472 * Print helpful warnings if there was no password.
476 * Likely going to be proxied. Avoid printing
479 if (fr_pair_find_by_num(request->config, PW_REALM, 0, TAG_ANY) ||
480 (fr_pair_find_by_num(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY))) {
481 return RLM_MODULE_NOOP;
485 * The TLS types don't need passwords.
487 vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY);
489 ((vp->vp_integer == 13) || /* EAP-TLS */
490 (vp->vp_integer == 21) || /* EAP-TTLS */
491 (vp->vp_integer == 25))) { /* PEAP */
492 return RLM_MODULE_NOOP;
495 RWDEBUG("No \"known good\" password found for the user. Not setting Auth-Type");
496 RWDEBUG("Authentication will fail unless a \"known good\" password is available");
497 return RLM_MODULE_NOOP;
501 * Don't touch existing Auth-Types.
504 if (auth_type != inst->auth_type) RWDEBUG2("Auth-Type already set. Not setting to PAP");
505 return RLM_MODULE_NOOP;
509 * Can't do PAP if there's no password.
511 if (!request->password ||
512 (request->password->da->attr != PW_USER_PASSWORD)) {
513 RDEBUG2("No User-Password attribute in the request. Cannot do PAP");
514 return RLM_MODULE_NOOP;
517 if (inst->auth_type) {
518 vp = radius_pair_create(request, &request->config,
520 vp->vp_integer = inst->auth_type;
523 return RLM_MODULE_UPDATED;
527 * PAP authentication functions
530 static rlm_rcode_t CC_HINT(nonnull) pap_auth_clear(UNUSED rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
532 if (RDEBUG_ENABLED3) {
533 RDEBUG3("Comparing with \"known good\" Cleartext-Password \"%s\" (%zd)", vp->vp_strvalue, vp->vp_length);
535 RDEBUG("Comparing with \"known good\" Cleartext-Password");
538 if ((vp->vp_length != request->password->vp_length) ||
539 (rad_digest_cmp(vp->vp_octets,
540 request->password->vp_octets,
541 vp->vp_length) != 0)) {
542 REDEBUG("Cleartext password \"%s\" does not match \"known good\" password",
543 request->password->vp_strvalue);
544 return RLM_MODULE_REJECT;
546 return RLM_MODULE_OK;
549 static rlm_rcode_t CC_HINT(nonnull) pap_auth_crypt(UNUSED rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
551 if (RDEBUG_ENABLED3) {
552 RDEBUG3("Comparing with \"known good\" Crypt-Password \"%s\"", vp->vp_strvalue);
554 RDEBUG("Comparing with \"known-good\" Crypt-password");
557 if (fr_crypt_check(request->password->vp_strvalue,
558 vp->vp_strvalue) != 0) {
559 REDEBUG("Crypt digest does not match \"known good\" digest");
560 return RLM_MODULE_REJECT;
562 return RLM_MODULE_OK;
565 static rlm_rcode_t CC_HINT(nonnull) pap_auth_md5(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
567 FR_MD5_CTX md5_context;
570 RDEBUG("Comparing with \"known-good\" MD5-Password");
573 normify(request, vp, 16);
575 if (vp->vp_length != 16) {
576 REDEBUG("\"known-good\" MD5 password has incorrect length");
577 return RLM_MODULE_INVALID;
580 fr_md5_init(&md5_context);
581 fr_md5_update(&md5_context, request->password->vp_octets,
582 request->password->vp_length);
583 fr_md5_final(digest, &md5_context);
585 if (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0) {
586 REDEBUG("MD5 digest does not match \"known good\" digest");
587 return RLM_MODULE_REJECT;
590 return RLM_MODULE_OK;
594 static rlm_rcode_t CC_HINT(nonnull) pap_auth_smd5(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
596 FR_MD5_CTX md5_context;
599 RDEBUG("Comparing with \"known-good\" SMD5-Password");
602 normify(request, vp, 16);
604 if (vp->vp_length <= 16) {
605 REDEBUG("\"known-good\" SMD5-Password has incorrect length");
606 return RLM_MODULE_INVALID;
609 fr_md5_init(&md5_context);
610 fr_md5_update(&md5_context, request->password->vp_octets,
611 request->password->vp_length);
612 fr_md5_update(&md5_context, &vp->vp_octets[16], vp->vp_length - 16);
613 fr_md5_final(digest, &md5_context);
616 * Compare only the MD5 hash results, not the salt.
618 if (rad_digest_cmp(digest, vp->vp_octets, 16) != 0) {
619 REDEBUG("SMD5 digest does not match \"known good\" digest");
620 return RLM_MODULE_REJECT;
623 return RLM_MODULE_OK;
626 static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
628 fr_sha1_ctx sha1_context;
631 RDEBUG("Comparing with \"known-good\" SHA-Password");
634 normify(request, vp, 20);
636 if (vp->vp_length != 20) {
637 REDEBUG("\"known-good\" SHA1-password has incorrect length");
638 return RLM_MODULE_INVALID;
641 fr_sha1_init(&sha1_context);
642 fr_sha1_update(&sha1_context, request->password->vp_octets,
643 request->password->vp_length);
644 fr_sha1_final(digest,&sha1_context);
646 if (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0) {
647 REDEBUG("SHA1 digest does not match \"known good\" digest");
648 return RLM_MODULE_REJECT;
651 return RLM_MODULE_OK;
654 static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
656 fr_sha1_ctx sha1_context;
659 RDEBUG("Comparing with \"known-good\" SSHA-Password");
662 normify(request, vp, 20);
664 if (vp->vp_length <= 20) {
665 REDEBUG("\"known-good\" SSHA-Password has incorrect length");
666 return RLM_MODULE_INVALID;
669 fr_sha1_init(&sha1_context);
670 fr_sha1_update(&sha1_context, request->password->vp_octets, request->password->vp_length);
672 fr_sha1_update(&sha1_context, &vp->vp_octets[20], vp->vp_length - 20);
673 fr_sha1_final(digest, &sha1_context);
675 if (rad_digest_cmp(digest, vp->vp_octets, 20) != 0) {
676 REDEBUG("SSHA digest does not match \"known good\" digest");
677 return RLM_MODULE_REJECT;
680 return RLM_MODULE_OK;
683 #ifdef HAVE_OPENSSL_EVP_H
684 static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha2(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
689 uint8_t digest[EVP_MAX_MD_SIZE];
690 unsigned int digest_len;
692 RDEBUG("Comparing with \"known-good\" SHA2-Password");
694 if (inst->normify) normify(request, vp, 28);
697 * All the SHA-2 algorithms produce digests of different lengths,
698 * so it's trivial to determine which EVP_MD to use.
700 switch (vp->vp_length) {
726 REDEBUG("\"known good\" digest length (%zu) does not match output length of any SHA-2 digests",
728 return RLM_MODULE_INVALID;
731 ctx = EVP_MD_CTX_create();
732 EVP_DigestInit_ex(ctx, md, NULL);
733 EVP_DigestUpdate(ctx, request->password->vp_octets, request->password->vp_length);
734 EVP_DigestFinal_ex(ctx, digest, &digest_len);
735 EVP_MD_CTX_destroy(ctx);
737 rad_assert((size_t) digest_len == vp->vp_length); /* This would be an OpenSSL bug... */
739 if (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0) {
740 REDEBUG("%s digest does not match \"known good\" digest", name);
741 return RLM_MODULE_REJECT;
744 return RLM_MODULE_OK;
747 static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha2(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
750 EVP_MD const *md = NULL;
751 char const *name = NULL;
752 uint8_t digest[EVP_MAX_MD_SIZE];
753 unsigned int digest_len, min_len = 0;
755 switch (vp->da->attr) {
756 case PW_SSHA2_224_PASSWORD:
762 case PW_SSHA2_256_PASSWORD:
768 case PW_SSHA2_384_PASSWORD:
774 case PW_SSHA2_512_PASSWORD:
784 RDEBUG("Comparing with \"known-good\" %s-Password", name);
787 * Unlike plain SHA2 we already know what length
788 * to expect, so can be more specific with the
789 * minimum digest length.
791 if (inst->normify) normify(request, vp, min_len + 1);
793 if (vp->vp_length <= min_len) {
794 REDEBUG("\"known-good\" %s-Password has incorrect length, got %zu bytes, need at least %u bytes",
795 name, vp->vp_length, min_len + 1);
796 return RLM_MODULE_INVALID;
799 ctx = EVP_MD_CTX_create();
800 EVP_DigestInit_ex(ctx, md, NULL);
801 EVP_DigestUpdate(ctx, request->password->vp_octets, request->password->vp_length);
802 EVP_DigestUpdate(ctx, &vp->vp_octets[min_len], vp->vp_length - min_len);
803 EVP_DigestFinal_ex(ctx, digest, &digest_len);
804 EVP_MD_CTX_destroy(ctx);
806 rad_assert((size_t) digest_len == min_len); /* This would be an OpenSSL bug... */
809 * Only compare digest_len bytes, the rest is salt.
811 if (rad_digest_cmp(digest, vp->vp_octets, (size_t)digest_len) != 0) {
812 REDEBUG("%s digest does not match \"known good\" digest", name);
813 return RLM_MODULE_REJECT;
816 return RLM_MODULE_OK;
820 static rlm_rcode_t CC_HINT(nonnull) pap_auth_nt(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
824 uint8_t ucs2_password[512];
826 RDEBUG("Comparing with \"known-good\" NT-Password");
828 rad_assert(request->password != NULL);
829 rad_assert(request->password->da->attr == PW_USER_PASSWORD);
832 normify(request, vp, 16);
835 if (vp->vp_length != 16) {
836 REDEBUG("\"known good\" NT-Password has incorrect length");
837 return RLM_MODULE_INVALID;
840 len = fr_utf8_to_ucs2(ucs2_password, sizeof(ucs2_password), request->password->vp_strvalue, request->password->vp_length);
842 REDEBUG("User-Password is not in UCS2 format");
843 return RLM_MODULE_INVALID;
846 fr_md4_calc(digest, (uint8_t *) ucs2_password, len);
848 if (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0) {
849 REDEBUG("NT digest does not match \"known good\" digest");
850 return RLM_MODULE_REJECT;
853 return RLM_MODULE_OK;
857 static rlm_rcode_t CC_HINT(nonnull) pap_auth_lm(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
860 char charbuf[32 + 1];
863 RDEBUG("Comparing with \"known-good\" LM-Password");
866 normify(request, vp, 16);
868 if (vp->vp_length != 16) {
869 REDEBUG("\"known good\" LM-Password has incorrect length");
870 return RLM_MODULE_INVALID;
873 len = radius_xlat(charbuf, sizeof(charbuf), request, "%{mschap:LM-Hash %{User-Password}}", NULL, NULL);
875 return RLM_MODULE_FAIL;
878 if ((fr_hex2bin(digest, sizeof(digest), charbuf, len) != vp->vp_length) ||
879 (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0)) {
880 REDEBUG("LM digest does not match \"known good\" digest");
881 return RLM_MODULE_REJECT;
884 return RLM_MODULE_OK;
887 static rlm_rcode_t CC_HINT(nonnull) pap_auth_ns_mta_md5(UNUSED rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
889 FR_MD5_CTX md5_context;
891 uint8_t buff[MAX_STRING_LEN];
892 char buff2[MAX_STRING_LEN + 50];
894 RDEBUG("Using NT-MTA-MD5-Password");
896 if (vp->vp_length != 64) {
897 REDEBUG("\"known good\" NS-MTA-MD5-Password has incorrect length");
898 return RLM_MODULE_INVALID;
902 * Sanity check the value of NS-MTA-MD5-Password
904 if (fr_hex2bin(digest, sizeof(digest), vp->vp_strvalue, vp->vp_length) != 16) {
905 REDEBUG("\"known good\" NS-MTA-MD5-Password has invalid value");
906 return RLM_MODULE_INVALID;
910 * Ensure we don't have buffer overflows.
912 * This really: sizeof(buff) - 2 - 2*32 - strlen(passwd)
914 if (request->password->vp_length >= (sizeof(buff) - 2 - 2 * 32)) {
915 REDEBUG("\"known good\" NS-MTA-MD5-Password is too long");
916 return RLM_MODULE_INVALID;
920 * Set up the algorithm.
925 memcpy(p, &vp->vp_octets[32], 32);
928 strcpy(p, request->password->vp_strvalue);
931 memcpy(p, &vp->vp_octets[32], 32);
934 fr_md5_init(&md5_context);
935 fr_md5_update(&md5_context, (uint8_t *) buff2, p - buff2);
936 fr_md5_final(buff, &md5_context);
939 if (rad_digest_cmp(digest, buff, 16) != 0) {
940 REDEBUG("NS-MTA-MD5 digest does not match \"known good\" digest");
941 return RLM_MODULE_REJECT;
944 return RLM_MODULE_OK;
949 * Authenticate the user via one of any well-known password.
951 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
953 rlm_pap_t *inst = instance;
955 rlm_rcode_t rc = RLM_MODULE_INVALID;
957 rlm_rcode_t (*auth_func)(rlm_pap_t *, REQUEST *, VALUE_PAIR *) = NULL;
959 if (!request->password ||
960 (request->password->da->vendor != 0) ||
961 (request->password->da->attr != PW_USER_PASSWORD)) {
962 REDEBUG("You set 'Auth-Type = PAP' for a request that does not contain a User-Password attribute!");
963 return RLM_MODULE_INVALID;
967 * The user MUST supply a non-zero-length password.
969 if (request->password->vp_length == 0) {
970 REDEBUG("Password must not be empty");
971 return RLM_MODULE_INVALID;
974 if (RDEBUG_ENABLED3) {
975 RDEBUG3("Login attempt with password \"%s\" (%zd)", request->password->vp_strvalue, request->password->vp_length);
977 RDEBUG("Login attempt with password");
981 * Auto-detect passwords, by attribute in the
982 * config items, to find out which authentication
985 for (vp = fr_cursor_init(&cursor, &request->config);
987 vp = fr_cursor_next(&cursor)) {
988 if (!vp->da->vendor) switch (vp->da->attr) {
989 case PW_CLEARTEXT_PASSWORD:
990 auth_func = &pap_auth_clear;
993 case PW_CRYPT_PASSWORD:
994 auth_func = &pap_auth_crypt;
997 case PW_MD5_PASSWORD:
998 auth_func = &pap_auth_md5;
1001 case PW_SMD5_PASSWORD:
1002 auth_func = &pap_auth_smd5;
1005 #ifdef HAVE_OPENSSL_EVP_H
1006 case PW_SHA2_PASSWORD:
1007 auth_func = &pap_auth_sha2;
1010 case PW_SSHA2_224_PASSWORD:
1011 case PW_SSHA2_256_PASSWORD:
1012 case PW_SSHA2_384_PASSWORD:
1013 case PW_SSHA2_512_PASSWORD:
1014 auth_func = &pap_auth_ssha2;
1018 case PW_SHA_PASSWORD:
1019 auth_func = &pap_auth_sha;
1022 case PW_SSHA_PASSWORD:
1023 auth_func = &pap_auth_ssha;
1026 case PW_NT_PASSWORD:
1027 auth_func = &pap_auth_nt;
1030 case PW_LM_PASSWORD:
1031 auth_func = &pap_auth_lm;
1034 case PW_NS_MTA_MD5_PASSWORD:
1035 auth_func = &pap_auth_ns_mta_md5;
1042 if (auth_func != NULL) break;
1046 * No attribute was found that looked like a password to match.
1049 RDEBUG("No password configured for the user. Cannot do authentication");
1050 return RLM_MODULE_FAIL;
1054 * Authenticate, and return.
1056 rc = auth_func(inst, request, vp);
1058 if (rc == RLM_MODULE_REJECT) {
1059 RDEBUG("Passwords don't match");
1062 if (rc == RLM_MODULE_OK) {
1063 RDEBUG("User authenticated successfully");
1071 * The module name should be the only globally exported symbol.
1072 * That is, everything else should be 'static'.
1074 * If the module needs to temporarily modify it's instantiation
1075 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
1076 * The server will then take care of ensuring that the module
1077 * is single-threaded.
1079 extern module_t rlm_pap;
1080 module_t rlm_pap = {
1081 .magic = RLM_MODULE_INIT,
1083 .type = RLM_TYPE_HUP_SAFE,
1084 .inst_size = sizeof(rlm_pap_t),
1085 .config = module_config,
1086 .instantiate = mod_instantiate,
1088 [MOD_AUTHENTICATE] = mod_authenticate,
1089 [MOD_AUTHORIZE] = mod_authorize