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 { NULL, -1, 0, NULL, NULL }
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 pairmemcpy(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 pairmemcpy(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 = paircreate(request, attr, 0);
264 if (new->da->type == PW_TYPE_OCTETS) {
265 pairmemcpy(new, (uint8_t const *) q + 1, (len - hlen) + 1);
266 new->vp_length = (len - hlen); /* lie about the length */
268 pairstrcpy(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: %s = '%s' -> %s = '%s'", vp->da->name, old_value, new->da->name, new_value);
277 talloc_free(old_value);
278 talloc_free(new_value);
280 RDEBUG2("Converted: %s -> %s", vp->da->name, new->da->name);
287 * Doesn't have a header {...} prefix
289 * See if it's base64, if it is, decode it and check again!
291 decoded = fr_base64_decode(digest, sizeof(digest) - 1, vp->vp_strvalue, len);
292 if ((decoded > 0) && (digest[0] == '{') && (memchr(digest, '}', decoded) != NULL)) {
293 RDEBUG2("Normalizing %s from base64 encoding, %zu bytes -> %zu bytes",
294 vp->da->name, vp->vp_length, decoded);
296 * Password-With-Header is a string attribute.
297 * Even though we're handling binary data, the buffer
298 * must be \0 terminated.
300 digest[decoded] = '\0';
301 pairmemcpy(vp, digest, decoded + 1);
302 vp->vp_length = decoded; /* lie about the length */
307 if (RDEBUG_ENABLED3) {
308 RDEBUG3("No {...} in Password-With-Header = \"%s\", re-writing to "
309 "Cleartext-Password", vp->vp_strvalue);
311 RDEBUG("No {...} in Password-With-Header, re-writing to Cleartext-Password");
315 new = paircreate(request, PW_CLEARTEXT_PASSWORD, 0);
316 pairstrcpy(new, vp->vp_strvalue);
322 * Authorize the user for PAP authentication.
324 * This isn't strictly necessary, but it does make the
325 * server simpler to configure.
327 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
329 rlm_pap_t *inst = instance;
330 bool auth_type = false;
331 bool found_pw = false;
335 for (vp = fr_cursor_init(&cursor, &request->config);
337 vp = fr_cursor_next(&cursor)) {
340 switch (vp->da->attr) {
341 case PW_USER_PASSWORD: /* deprecated */
342 RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
343 RWDEBUG("!!! Ignoring control:User-Password. Update your !!!");
344 RWDEBUG("!!! configuration so that the \"known good\" clear text !!!");
345 RWDEBUG("!!! password is in Cleartext-Password and NOT in !!!");
346 RWDEBUG("!!! User-Password. !!!");
347 RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
350 case PW_PASSWORD_WITH_HEADER: /* preferred */
355 * Password already exists: use that instead of this one.
357 if (pairfind(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) {
358 RWDEBUG("Config already contains a \"known good\" password "
359 "(&control:Cleartext-Password). Ignoring &config:Password-With-Header");
363 new = normify_with_header(request, vp);
364 if (new) fr_cursor_insert(&cursor, new); /* inserts at the end of the list */
366 RDEBUG2("Removing &control:Password-With-Header");
367 vp = fr_cursor_remove(&cursor); /* advances the cursor for us */
372 vp = fr_cursor_current(&cursor);
377 case PW_CLEARTEXT_PASSWORD:
378 case PW_CRYPT_PASSWORD:
379 case PW_NS_MTA_MD5_PASSWORD:
381 break; /* don't touch these */
383 case PW_MD5_PASSWORD:
384 case PW_SMD5_PASSWORD:
388 normify(request, vp, 16); /* ensure it's in the right format */
393 #ifdef HAVE_OPENSSL_EVP_H
394 case PW_SHA2_PASSWORD:
396 normify(request, vp, 28); /* ensure it's in the right format */
401 case PW_SSHA2_224_PASSWORD:
403 normify(request, vp, 28); /* ensure it's in the right format */
408 case PW_SSHA2_256_PASSWORD:
410 normify(request, vp, 32); /* ensure it's in the right format */
415 case PW_SSHA2_384_PASSWORD:
417 normify(request, vp, 48); /* ensure it's in the right format */
422 case PW_SSHA2_512_PASSWORD:
424 normify(request, vp, 64); /* ensure it's in the right format */
430 case PW_SHA_PASSWORD:
431 case PW_SSHA_PASSWORD:
433 normify(request, vp, 20); /* ensure it's in the right format */
439 * If it's proxied somewhere, don't complain
440 * about not having passwords or Auth-Type.
442 case PW_PROXY_TO_REALM:
444 REALM *realm = realm_find(vp->vp_strvalue);
445 if (realm && realm->auth_pool) {
446 return RLM_MODULE_NOOP;
455 * Auth-Type := Accept
456 * Auth-Type := Reject
458 if ((vp->vp_integer == 254) ||
459 (vp->vp_integer == 4)) {
465 break; /* ignore it */
471 * Print helpful warnings if there was no password.
475 * Likely going to be proxied. Avoid printing
478 if (pairfind(request->config, PW_REALM, 0, TAG_ANY) ||
479 (pairfind(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY))) {
480 return RLM_MODULE_NOOP;
484 * The TLS types don't need passwords.
486 vp = pairfind(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY);
488 ((vp->vp_integer == 13) || /* EAP-TLS */
489 (vp->vp_integer == 21) || /* EAP-TTLS */
490 (vp->vp_integer == 25))) { /* PEAP */
491 return RLM_MODULE_NOOP;
494 RWDEBUG("No \"known good\" password found for the user. Not setting Auth-Type");
495 RWDEBUG("Authentication will fail unless a \"known good\" password is available");
496 return RLM_MODULE_NOOP;
500 * Don't touch existing Auth-Types.
503 if (auth_type != inst->auth_type) RWDEBUG2("Auth-Type already set. Not setting to PAP");
504 return RLM_MODULE_NOOP;
508 * Can't do PAP if there's no password.
510 if (!request->password ||
511 (request->password->da->attr != PW_USER_PASSWORD)) {
512 RDEBUG2("No User-Password attribute in the request. Cannot do PAP");
513 return RLM_MODULE_NOOP;
516 if (inst->auth_type) {
517 vp = radius_paircreate(request, &request->config,
519 vp->vp_integer = inst->auth_type;
522 return RLM_MODULE_UPDATED;
526 * PAP authentication functions
529 static rlm_rcode_t CC_HINT(nonnull) pap_auth_clear(UNUSED rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
531 if (RDEBUG_ENABLED3) {
532 RDEBUG3("Comparing with \"known good\" Cleartext-Password \"%s\" (%zd)", vp->vp_strvalue, vp->vp_length);
534 RDEBUG("Comparing with \"known good\" Cleartext-Password");
537 if ((vp->vp_length != request->password->vp_length) ||
538 (rad_digest_cmp(vp->vp_octets,
539 request->password->vp_octets,
540 vp->vp_length) != 0)) {
541 REDEBUG("Cleartext password does not match \"known good\" password");
542 return RLM_MODULE_REJECT;
544 return RLM_MODULE_OK;
547 static rlm_rcode_t CC_HINT(nonnull) pap_auth_crypt(UNUSED rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
549 if (RDEBUG_ENABLED3) {
550 RDEBUG3("Comparing with \"known good\" Crypt-Password \"%s\"", vp->vp_strvalue);
552 RDEBUG("Comparing with \"known-good\" Crypt-password");
555 if (fr_crypt_check(request->password->vp_strvalue,
556 vp->vp_strvalue) != 0) {
557 REDEBUG("Crypt digest does not match \"known good\" digest");
558 return RLM_MODULE_REJECT;
560 return RLM_MODULE_OK;
563 static rlm_rcode_t CC_HINT(nonnull) pap_auth_md5(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
565 FR_MD5_CTX md5_context;
568 RDEBUG("Comparing with \"known-good\" MD5-Password");
571 normify(request, vp, 16);
573 if (vp->vp_length != 16) {
574 REDEBUG("\"known-good\" MD5 password has incorrect length");
575 return RLM_MODULE_INVALID;
578 fr_md5_init(&md5_context);
579 fr_md5_update(&md5_context, request->password->vp_octets,
580 request->password->vp_length);
581 fr_md5_final(digest, &md5_context);
583 if (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0) {
584 REDEBUG("MD5 digest does not match \"known good\" digest");
585 return RLM_MODULE_REJECT;
588 return RLM_MODULE_OK;
592 static rlm_rcode_t CC_HINT(nonnull) pap_auth_smd5(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
594 FR_MD5_CTX md5_context;
597 RDEBUG("Comparing with \"known-good\" SMD5-Password");
600 normify(request, vp, 16);
602 if (vp->vp_length <= 16) {
603 REDEBUG("\"known-good\" SMD5-Password has incorrect length");
604 return RLM_MODULE_INVALID;
607 fr_md5_init(&md5_context);
608 fr_md5_update(&md5_context, request->password->vp_octets,
609 request->password->vp_length);
610 fr_md5_update(&md5_context, &vp->vp_octets[16], vp->vp_length - 16);
611 fr_md5_final(digest, &md5_context);
614 * Compare only the MD5 hash results, not the salt.
616 if (rad_digest_cmp(digest, vp->vp_octets, 16) != 0) {
617 REDEBUG("SMD5 digest does not match \"known good\" digest");
618 return RLM_MODULE_REJECT;
621 return RLM_MODULE_OK;
624 static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
626 fr_SHA1_CTX sha1_context;
629 RDEBUG("Comparing with \"known-good\" SHA-Password");
632 normify(request, vp, 20);
634 if (vp->vp_length != 20) {
635 REDEBUG("\"known-good\" SHA1-password has incorrect length");
636 return RLM_MODULE_INVALID;
639 fr_sha1_init(&sha1_context);
640 fr_sha1_update(&sha1_context, request->password->vp_octets,
641 request->password->vp_length);
642 fr_sha1_final(digest,&sha1_context);
644 if (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0) {
645 REDEBUG("SHA1 digest does not match \"known good\" digest");
646 return RLM_MODULE_REJECT;
649 return RLM_MODULE_OK;
652 static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
654 fr_SHA1_CTX sha1_context;
657 RDEBUG("Comparing with \"known-good\" SSHA-Password");
660 normify(request, vp, 20);
662 if (vp->vp_length <= 20) {
663 REDEBUG("\"known-good\" SSHA-Password has incorrect length");
664 return RLM_MODULE_INVALID;
667 fr_sha1_init(&sha1_context);
668 fr_sha1_update(&sha1_context, request->password->vp_octets, request->password->vp_length);
670 fr_sha1_update(&sha1_context, &vp->vp_octets[20], vp->vp_length - 20);
671 fr_sha1_final(digest, &sha1_context);
673 if (rad_digest_cmp(digest, vp->vp_octets, 20) != 0) {
674 REDEBUG("SSHA digest does not match \"known good\" digest");
675 return RLM_MODULE_REJECT;
678 return RLM_MODULE_OK;
681 #ifdef HAVE_OPENSSL_EVP_H
682 static rlm_rcode_t CC_HINT(nonnull) pap_auth_sha2(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
687 uint8_t digest[EVP_MAX_MD_SIZE];
688 unsigned int digest_len;
690 RDEBUG("Comparing with \"known-good\" SHA2-Password");
692 if (inst->normify) normify(request, vp, 28);
695 * All the SHA-2 algorithms produce digests of different lengths,
696 * so it's trivial to determine which EVP_MD to use.
698 switch (vp->vp_length) {
724 REDEBUG("\"known good\" digest length (%zu) does not match output length of any SHA-2 digests",
726 return RLM_MODULE_INVALID;
729 ctx = EVP_MD_CTX_create();
730 EVP_DigestInit_ex(ctx, md, NULL);
731 EVP_DigestUpdate(ctx, request->password->vp_octets, request->password->vp_length);
732 EVP_DigestFinal_ex(ctx, digest, &digest_len);
733 EVP_MD_CTX_destroy(ctx);
735 rad_assert((size_t) digest_len == vp->vp_length); /* This would be an OpenSSL bug... */
737 if (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0) {
738 REDEBUG("%s digest does not match \"known good\" digest", name);
739 return RLM_MODULE_REJECT;
742 return RLM_MODULE_OK;
745 static rlm_rcode_t CC_HINT(nonnull) pap_auth_ssha2(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
748 EVP_MD const *md = NULL;
749 char const *name = NULL;
750 uint8_t digest[EVP_MAX_MD_SIZE];
751 unsigned int digest_len, min_len = 0;
753 switch (vp->da->attr) {
754 case PW_SSHA2_224_PASSWORD:
760 case PW_SSHA2_256_PASSWORD:
766 case PW_SSHA2_384_PASSWORD:
772 case PW_SSHA2_512_PASSWORD:
782 RDEBUG("Comparing with \"known-good\" %s-Password", name);
785 * Unlike plain SHA2 we already know what length
786 * to expect, so can be more specific with the
787 * minimum digest length.
789 if (inst->normify) normify(request, vp, min_len + 1);
791 if (vp->vp_length <= min_len) {
792 REDEBUG("\"known-good\" %s-Password has incorrect length, got %zu bytes, need at least %u bytes",
793 name, vp->vp_length, min_len + 1);
794 return RLM_MODULE_INVALID;
797 ctx = EVP_MD_CTX_create();
798 EVP_DigestInit_ex(ctx, md, NULL);
799 EVP_DigestUpdate(ctx, request->password->vp_octets, request->password->vp_length);
800 EVP_DigestUpdate(ctx, &vp->vp_octets[min_len], vp->vp_length - min_len);
801 EVP_DigestFinal_ex(ctx, digest, &digest_len);
802 EVP_MD_CTX_destroy(ctx);
804 rad_assert((size_t) digest_len == min_len); /* This would be an OpenSSL bug... */
807 * Only compare digest_len bytes, the rest is salt.
809 if (rad_digest_cmp(digest, vp->vp_octets, (size_t)digest_len) != 0) {
810 REDEBUG("%s digest does not match \"known good\" digest", name);
811 return RLM_MODULE_REJECT;
814 return RLM_MODULE_OK;
818 static rlm_rcode_t CC_HINT(nonnull) pap_auth_nt(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
822 uint8_t ucs2_password[512];
824 RDEBUG("Comparing with \"known-good\" NT-Password");
826 rad_assert(request->password != NULL);
827 rad_assert(request->password->da->attr == PW_USER_PASSWORD);
830 normify(request, vp, 16);
833 if (vp->vp_length != 16) {
834 REDEBUG("\"known good\" NT-Password has incorrect length");
835 return RLM_MODULE_INVALID;
838 len = fr_utf8_to_ucs2(ucs2_password, sizeof(ucs2_password), request->password->vp_strvalue, request->password->vp_length);
840 REDEBUG("User-Password is not in UCS2 format");
841 return RLM_MODULE_INVALID;
844 fr_md4_calc(digest, (uint8_t *) ucs2_password, len);
846 if (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0) {
847 REDEBUG("NT digest does not match \"known good\" digest");
848 return RLM_MODULE_REJECT;
851 return RLM_MODULE_OK;
855 static rlm_rcode_t CC_HINT(nonnull) pap_auth_lm(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
858 char charbuf[32 + 1];
861 RDEBUG("Comparing with \"known-good\" LM-Password");
864 normify(request, vp, 16);
866 if (vp->vp_length != 16) {
867 REDEBUG("\"known good\" LM-Password has incorrect length");
868 return RLM_MODULE_INVALID;
871 len = radius_xlat(charbuf, sizeof(charbuf), request, "%{mschap:LM-Hash %{User-Password}}", NULL, NULL);
873 return RLM_MODULE_FAIL;
876 if ((fr_hex2bin(digest, sizeof(digest), charbuf, len) != vp->vp_length) ||
877 (rad_digest_cmp(digest, vp->vp_octets, vp->vp_length) != 0)) {
878 REDEBUG("LM digest does not match \"known good\" digest");
879 return RLM_MODULE_REJECT;
882 return RLM_MODULE_OK;
885 static rlm_rcode_t CC_HINT(nonnull) pap_auth_ns_mta_md5(UNUSED rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
887 FR_MD5_CTX md5_context;
889 uint8_t buff[MAX_STRING_LEN];
890 char buff2[MAX_STRING_LEN + 50];
892 RDEBUG("Using NT-MTA-MD5-Password");
894 if (vp->vp_length != 64) {
895 REDEBUG("\"known good\" NS-MTA-MD5-Password has incorrect length");
896 return RLM_MODULE_INVALID;
900 * Sanity check the value of NS-MTA-MD5-Password
902 if (fr_hex2bin(digest, sizeof(digest), vp->vp_strvalue, vp->vp_length) != 16) {
903 REDEBUG("\"known good\" NS-MTA-MD5-Password has invalid value");
904 return RLM_MODULE_INVALID;
908 * Ensure we don't have buffer overflows.
910 * This really: sizeof(buff) - 2 - 2*32 - strlen(passwd)
912 if (request->password->vp_length >= (sizeof(buff) - 2 - 2 * 32)) {
913 REDEBUG("\"known good\" NS-MTA-MD5-Password is too long");
914 return RLM_MODULE_INVALID;
918 * Set up the algorithm.
923 memcpy(p, &vp->vp_octets[32], 32);
926 strcpy(p, request->password->vp_strvalue);
929 memcpy(p, &vp->vp_octets[32], 32);
932 fr_md5_init(&md5_context);
933 fr_md5_update(&md5_context, (uint8_t *) buff2, p - buff2);
934 fr_md5_final(buff, &md5_context);
937 if (rad_digest_cmp(digest, buff, 16) != 0) {
938 REDEBUG("NS-MTA-MD5 digest does not match \"known good\" digest");
939 return RLM_MODULE_REJECT;
942 return RLM_MODULE_OK;
947 * Authenticate the user via one of any well-known password.
949 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
951 rlm_pap_t *inst = instance;
953 rlm_rcode_t rc = RLM_MODULE_INVALID;
955 rlm_rcode_t (*auth_func)(rlm_pap_t *, REQUEST *, VALUE_PAIR *) = NULL;
957 if (!request->password ||
958 (request->password->da->vendor != 0) ||
959 (request->password->da->attr != PW_USER_PASSWORD)) {
960 REDEBUG("You set 'Auth-Type = PAP' for a request that does not contain a User-Password attribute!");
961 return RLM_MODULE_INVALID;
965 * The user MUST supply a non-zero-length password.
967 if (request->password->vp_length == 0) {
968 REDEBUG("Password must not be empty");
969 return RLM_MODULE_INVALID;
972 if (RDEBUG_ENABLED3) {
973 RDEBUG3("Login attempt with password \"%s\" (%zd)", request->password->vp_strvalue, request->password->vp_length);
975 RDEBUG("Login attempt with password");
979 * Auto-detect passwords, by attribute in the
980 * config items, to find out which authentication
983 for (vp = fr_cursor_init(&cursor, &request->config);
985 vp = fr_cursor_next(&cursor)) {
986 if (!vp->da->vendor) switch (vp->da->attr) {
987 case PW_CLEARTEXT_PASSWORD:
988 auth_func = &pap_auth_clear;
991 case PW_CRYPT_PASSWORD:
992 auth_func = &pap_auth_crypt;
995 case PW_MD5_PASSWORD:
996 auth_func = &pap_auth_md5;
999 case PW_SMD5_PASSWORD:
1000 auth_func = &pap_auth_smd5;
1003 #ifdef HAVE_OPENSSL_EVP_H
1004 case PW_SHA2_PASSWORD:
1005 auth_func = &pap_auth_sha2;
1008 case PW_SSHA2_224_PASSWORD:
1009 case PW_SSHA2_256_PASSWORD:
1010 case PW_SSHA2_384_PASSWORD:
1011 case PW_SSHA2_512_PASSWORD:
1012 auth_func = &pap_auth_ssha2;
1016 case PW_SHA_PASSWORD:
1017 auth_func = &pap_auth_sha;
1020 case PW_SSHA_PASSWORD:
1021 auth_func = &pap_auth_ssha;
1024 case PW_NT_PASSWORD:
1025 auth_func = &pap_auth_nt;
1028 case PW_LM_PASSWORD:
1029 auth_func = &pap_auth_lm;
1032 case PW_NS_MTA_MD5_PASSWORD:
1033 auth_func = &pap_auth_ns_mta_md5;
1040 if (auth_func != NULL) break;
1044 * No attribute was found that looked like a password to match.
1047 RDEBUG("No password configured for the user. Cannot do authentication");
1048 return RLM_MODULE_FAIL;
1052 * Authenticate, and return.
1054 rc = auth_func(inst, request, vp);
1056 if (rc == RLM_MODULE_REJECT) {
1057 RDEBUG("Passwords don't match");
1060 if (rc == RLM_MODULE_OK) {
1061 RDEBUG("User authenticated successfully");
1069 * The module name should be the only globally exported symbol.
1070 * That is, everything else should be 'static'.
1072 * If the module needs to temporarily modify it's instantiation
1073 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
1074 * The server will then take care of ensuring that the module
1075 * is single-threaded.
1077 extern module_t rlm_pap;
1078 module_t rlm_pap = {
1079 .magic = RLM_MODULE_INIT,
1081 .type = RLM_TYPE_HUP_SAFE,
1082 .inst_size = sizeof(rlm_pap_t),
1083 .config = module_config,
1084 .instantiate = mod_instantiate,
1086 [MOD_AUTHENTICATE] = mod_authenticate,
1087 [MOD_AUTHORIZE] = mod_authorize