6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2001 The FreeRADIUS server project
21 * Copyright 2001 Kostas Kalevras <kkalev@noc.ntua.gr>
25 #include "libradius.h"
36 #include "../../include/md5.h"
37 #include "../../include/sha1.h"
39 #define PAP_ENC_INVALID -1
40 #define PAP_ENC_CLEAR 0
41 #define PAP_ENC_CRYPT 1
43 #define PAP_ENC_SHA1 3
46 #define PAP_ENC_SMD5 6
47 #define PAP_ENC_SSHA 7
48 #define PAP_ENC_NS_MTA_MD5 8
49 #define PAP_ENC_AUTO 9
53 static const char rcsid[] = "$Id$";
56 * Define a structure for our module configuration.
58 * These variables do not need to be in a structure, but it's
59 * a lot cleaner to do so, and a pointer to the structure can
60 * be used as the instance handle.
62 typedef struct rlm_pap_t {
63 char *scheme; /* password encryption scheme */
69 * A mapping of configuration file names to internal variables.
71 * Note that the string is dynamically allocated, so it MUST
72 * be freed. When the configuration file parse re-reads the string,
73 * it free's the old one, and strdup's the new one, placing the pointer
74 * to the strdup'd string into 'config.string'. This gets around
77 static const CONF_PARSER module_config[] = {
78 { "encryption_scheme", PW_TYPE_STRING_PTR, offsetof(rlm_pap_t,scheme), NULL, "auto" },
79 { NULL, -1, 0, NULL, NULL }
82 static const LRAD_NAME_NUMBER schemes[] = {
83 { "clear", PAP_ENC_CLEAR },
84 { "crypt", PAP_ENC_CRYPT },
85 { "md5", PAP_ENC_MD5 },
86 { "sha1", PAP_ENC_SHA1 },
89 { "smd5", PAP_ENC_SMD5 },
90 { "ssha", PAP_ENC_SSHA },
91 { "auto", PAP_ENC_AUTO },
92 { NULL, PAP_ENC_INVALID }
96 static int pap_detach(void *instance)
98 rlm_pap_t *inst = (rlm_pap_t *) instance;
100 if (inst->scheme) free((char *)inst->scheme);
107 static int pap_instantiate(CONF_SECTION *conf, void **instance)
112 * Set up a storage area for instance data
114 inst = rad_malloc(sizeof(*inst));
118 memset(inst, 0, sizeof(*inst));
121 * If the configuration parameters can't be parsed, then
124 if (cf_section_parse(conf, inst, module_config) < 0) {
128 if (inst->scheme == NULL || strlen(inst->scheme) == 0){
129 radlog(L_ERR, "rlm_pap: No scheme defined");
134 inst->sch = lrad_str2int(schemes, inst->scheme, PAP_ENC_INVALID);
135 if (inst->sch == PAP_ENC_INVALID) {
136 radlog(L_ERR, "rlm_pap: Unknown scheme \"%s\"", inst->scheme);
148 * Decode one base64 chunk
150 static int decode_it(const char *src, uint8_t *dst)
155 for(i = 0; i < 4; i++) {
156 if (src[i] >= 'A' && src[i] <= 'Z')
157 x = (x << 6) + (unsigned int)(src[i] - 'A' + 0);
158 else if (src[i] >= 'a' && src[i] <= 'z')
159 x = (x << 6) + (unsigned int)(src[i] - 'a' + 26);
160 else if(src[i] >= '0' && src[i] <= '9')
161 x = (x << 6) + (unsigned int)(src[i] - '0' + 52);
162 else if(src[i] == '+')
164 else if (src[i] == '/')
166 else if (src[i] == '=')
171 dst[2] = (unsigned char)(x & 255); x >>= 8;
172 dst[1] = (unsigned char)(x & 255); x >>= 8;
173 dst[0] = (unsigned char)(x & 255); x >>= 8;
182 static int base64_decode (const char *src, uint8_t *dst)
189 while (src[length] && src[length] != '=') length++;
191 if (src[length] != '=') return 0; /* no trailing '=' */
193 while (src[length + equals] == '=') equals++;
195 num = (length + equals) / 4;
197 for (i = 0; i < num - 1; i++) {
198 if (!decode_it(src, dst)) return 0;
203 decode_it(src, last);
204 for (i = 0; i < (3 - equals); i++) {
208 return (num * 3) - equals;
213 * Hex or base64 or bin auto-discovery.
215 static void normify(VALUE_PAIR *vp, int min_length)
220 if (min_length >= sizeof(buffer)) return; /* paranoia */
225 if (vp->length >= (2 * min_length)) {
226 decoded = lrad_hex2bin(vp->strvalue, buffer, vp->length >> 1);
227 if (decoded == (vp->length >> 1)) {
228 DEBUG2("rlm_pap: Normalizing %s from hex encoding", vp->name);
229 memcpy(vp->strvalue, buffer, decoded);
230 vp->length = decoded;
236 * Base 64 encoding. It's at least 4/3 the original size,
237 * and we want to avoid division...
239 if ((vp->length * 3) >= ((min_length * 4))) {
240 decoded = base64_decode(vp->strvalue, buffer);
241 if (decoded >= min_length) {
242 DEBUG2("rlm_pap: Normalizing %s from base64 encoding", vp->name);
243 memcpy(vp->strvalue, buffer, decoded);
244 vp->length = decoded;
250 * Else unknown encoding, or already binary. Leave it.
256 * Authorize the user for PAP authentication.
258 * This isn't strictly necessary, but it does make the
259 * server simpler to configure.
261 static int pap_authorize(void *instance, REQUEST *request)
263 int auth_type = FALSE;
264 int found_pw = FALSE;
267 instance = instance; /* -Wunused */
269 for (vp = request->config_items; vp != NULL; vp = vp->next) {
270 switch (vp->attribute) {
271 case PW_USER_PASSWORD:
272 case PW_CRYPT_PASSWORD:
273 case PW_NS_MTA_MD5_PASSWORD:
275 break; /* don't touch these */
277 case PW_MD5_PASSWORD:
278 case PW_SMD5_PASSWORD:
281 normify(vp, 16); /* ensure it's in the right format */
285 case PW_SHA_PASSWORD:
286 case PW_SSHA_PASSWORD:
287 normify(vp, 20); /* ensure it's in the right format */
296 break; /* ignore it */
302 * Print helpful warnings if there was no password.
305 DEBUG("rlm_pap: WARNING! No \"known good\" password found for the user. Authentication will probably fail");
306 return RLM_MODULE_NOOP;
310 * Don't touch existing Auth-Types.
313 DEBUG2("rlm_pap: Found existing Auth-Type, not changing it.");
314 return RLM_MODULE_NOOP;
318 * Can't do PAP if there's no password.
320 if (!request->password ||
321 (request->password->attribute != PW_USER_PASSWORD)) {
323 * Don't print out debugging messages if we know
326 if (request->packet->code == PW_ACCESS_CHALLENGE) {
327 return RLM_MODULE_NOOP;
330 DEBUG2("rlm_pap: No clear-text password in the request. Not performing PAP.");
331 return RLM_MODULE_NOOP;
335 vp = pairmake("Auth-Type", "PAP", T_OP_SET);
336 if (!vp) return RLM_MODULE_FAIL;
338 pairadd(&request->config_items, vp);
340 return RLM_MODULE_UPDATED;
345 * Authenticate the user via one of any well-known password.
347 static int pap_authenticate(void *instance, REQUEST *request)
349 rlm_pap_t *inst = instance;
351 VALUE_PAIR *module_fmsg_vp;
352 char module_fmsg[MAX_STRING_LEN];
354 SHA1_CTX sha1_context;
356 char buff[MAX_STRING_LEN];
357 char buff2[MAX_STRING_LEN + 50];
358 int scheme = PAP_ENC_INVALID;
360 /* quiet the compiler */
364 if (!request->password){
365 radlog(L_AUTH, "rlm_pap: Attribute \"Password\" is required for authentication.");
366 return RLM_MODULE_INVALID;
370 * Clear-text passwords are the only ones we support.
372 if (request->password->attribute != PW_USER_PASSWORD) {
373 radlog(L_AUTH, "rlm_pap: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
374 return RLM_MODULE_INVALID;
378 * The user MUST supply a non-zero-length password.
380 if (request->password->length == 0) {
381 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: empty password supplied");
382 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
383 pairadd(&request->packet->vps, module_fmsg_vp);
384 return RLM_MODULE_INVALID;
387 DEBUG("rlm_pap: login attempt with password %s",
388 request->password->strvalue);
391 * First, auto-detect passwords, by attribute in the
394 if (inst->sch == PAP_ENC_AUTO) {
395 for (vp = request->config_items; vp != NULL; vp = vp->next) {
396 switch (vp->attribute) {
397 case PW_USER_PASSWORD:
400 case PW_CRYPT_PASSWORD:
403 case PW_MD5_PASSWORD:
406 case PW_SHA_PASSWORD:
415 case PW_SMD5_PASSWORD:
418 case PW_SSHA_PASSWORD:
421 case PW_NS_MTA_MD5_PASSWORD:
425 break; /* ignore it */
431 DEBUG("rlm_pap: No password configured for the user. Cannot do authentication");
432 return RLM_MODULE_FAIL;
437 if (inst->sch == PAP_ENC_CRYPT) {
438 vp = pairfind(request->config_items, PW_CRYPT_PASSWORD);
442 * Old-style: all passwords are in User-Password.
445 vp = pairfind(request->config_items, PW_USER_PASSWORD);
451 * Now that we've decided what to do, go do it.
456 DEBUG("rlm_pap: Using clear text password.");
457 if (strcmp((char *) vp->strvalue,
458 (char *) request->password->strvalue) != 0){
459 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CLEAR TEXT password check failed");
463 DEBUG("rlm_pap: User authenticated succesfully");
464 return RLM_MODULE_OK;
469 DEBUG("rlm_pap: Using CRYPT encryption.");
470 if (lrad_crypt_check((char *) request->password->strvalue,
471 (char *) vp->strvalue) != 0) {
472 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CRYPT password check failed");
478 case PW_MD5_PASSWORD:
480 DEBUG("rlm_pap: Using MD5 encryption.");
483 if (vp->length != 16) {
484 DEBUG("rlm_pap: Configured MD5 password has incorrect length");
485 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured MD5 password has incorrect length");
489 MD5Init(&md5_context);
490 MD5Update(&md5_context, request->password->strvalue,
491 request->password->length);
492 MD5Final(digest, &md5_context);
493 if (memcmp(digest, vp->strvalue, vp->length) != 0) {
494 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: MD5 password check failed");
500 case PW_SMD5_PASSWORD:
502 DEBUG("rlm_pap: Using SMD5 encryption.");
505 if (vp->length <= 16) {
506 DEBUG("rlm_pap: Configured SMD5 password has incorrect length");
507 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SMD5 password has incorrect length");
511 MD5Init(&md5_context);
512 MD5Update(&md5_context, request->password->strvalue,
513 request->password->length);
514 MD5Update(&md5_context, &vp->strvalue[16], vp->length - 16);
515 MD5Final(digest, &md5_context);
518 * Compare only the MD5 hash results, not the salt.
520 if (memcmp(digest, vp->strvalue, 16) != 0) {
521 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SMD5 password check failed");
527 case PW_SHA_PASSWORD:
529 DEBUG("rlm_pap: Using SHA1 encryption.");
532 if (vp->length != 20) {
533 DEBUG("rlm_pap: Configured SHA1 password has incorrect length");
534 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA1 password has incorrect length");
538 SHA1Init(&sha1_context);
539 SHA1Update(&sha1_context, request->password->strvalue,
540 request->password->length);
541 SHA1Final(digest,&sha1_context);
542 if (memcmp(digest, vp->strvalue, vp->length) != 0) {
543 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SHA1 password check failed");
549 case PW_SSHA_PASSWORD:
551 DEBUG("rlm_pap: Using SSHA encryption.");
554 if (vp->length <= 20) {
555 DEBUG("rlm_pap: Configured SSHA password has incorrect length");
556 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA password has incorrect length");
561 SHA1Init(&sha1_context);
562 SHA1Update(&sha1_context, request->password->strvalue,
563 request->password->length);
564 SHA1Update(&sha1_context, &vp->strvalue[20], vp->length - 20);
565 SHA1Final(digest,&sha1_context);
566 if (memcmp(digest, vp->strvalue, 20) != 0) {
567 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SSHA password check failed");
575 DEBUG("rlm_pap: Using NT encryption.");
578 if (vp->length != 16) {
579 DEBUG("rlm_pap: Configured NT-Password has incorrect length");
580 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NT-Password has incorrect length");
584 sprintf(buff2,"%%{mschap:NT-Hash %s}",
585 request->password->strvalue);
586 if (!radius_xlat(digest,sizeof(digest),buff2,request,NULL)){
587 DEBUG("rlm_pap: mschap xlat failed");
588 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: mschap xlat failed");
591 if ((lrad_hex2bin(digest, digest, 16) != vp->length) ||
592 (memcmp(digest, vp->strvalue, vp->length) != 0)) {
593 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: NT password check failed");
601 DEBUG("rlm_pap: Using LM encryption.");
604 if (vp->length != 16) {
605 DEBUG("rlm_pap: Configured LM-Password has incorrect length");
606 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured LM-Password has incorrect length");
609 sprintf(buff2,"%%{mschap:LM-Hash %s}",request->password->strvalue);
610 if (!radius_xlat(digest,sizeof(digest),buff2,request,NULL)){
611 DEBUG("rlm_pap: mschap xlat failed");
612 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: mschap xlat failed");
615 if ((lrad_hex2bin(digest, digest, 16) != vp->length) ||
616 (memcmp(digest, vp->strvalue, vp->length) != 0)) {
617 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: LM password check failed");
619 DEBUG("rlm_pap: Passwords don't match");
620 module_fmsg_vp = pairmake("Module-Failure-Message",
621 module_fmsg, T_OP_EQ);
622 pairadd(&request->packet->vps, module_fmsg_vp);
623 return RLM_MODULE_REJECT;
628 case PAP_ENC_NS_MTA_MD5:
630 DEBUG("rlm_pap: Using NT-MTA-MD5 password");
632 if (vp->length != 64) {
633 DEBUG("rlm_pap: Configured NS-MTA-MD5-Password has incorrect length");
634 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NS-MTA-MD5-Password has incorrect length");
639 * Sanity check the value of NS-MTA-MD5-Password
641 if (lrad_hex2bin(vp->strvalue, buff, 32) != 16) {
642 DEBUG("rlm_pap: Configured NS-MTA-MD5-Password has invalid value");
643 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NS-MTA-MD5-Password has invalid value");
648 * Ensure we don't have buffer overflows.
650 * This really: sizeof(buff) - 2 - 2*32 - strlen(passwd)
652 if (strlen(request->password->strvalue) >= (sizeof(buff2) - 2 - 2 * 32)) {
653 DEBUG("rlm_pap: Configured password is too long");
654 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: password is too long");
659 * Set up the algorithm.
664 memcpy(p, &vp->strvalue[32], 32);
667 strcpy(p, request->password->strvalue);
670 memcpy(p, &vp->strvalue[32], 32);
673 MD5Init(&md5_context);
674 MD5Update(&md5_context, buff2, p - buff2);
675 MD5Final(digest, &md5_context);
677 if (memcmp(digest, buff, 16) != 0) {
678 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: NS-MTA-MD5 password check failed");
687 DEBUG("rlm_pap: No password configured for the user. Cannot do authentication");
688 return RLM_MODULE_FAIL;
693 * The module name should be the only globally exported symbol.
694 * That is, everything else should be 'static'.
696 * If the module needs to temporarily modify it's instantiation
697 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
698 * The server will then take care of ensuring that the module
699 * is single-threaded.
704 NULL, /* initialization */
705 pap_instantiate, /* instantiation */
707 pap_authenticate, /* authentication */
708 pap_authorize, /* authorization */
709 NULL, /* preaccounting */
710 NULL, /* accounting */
711 NULL, /* checksimul */
712 NULL, /* pre-proxy */
713 NULL, /* post-proxy */
716 pap_detach, /* detach */