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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2000,2001,2006 The FreeRADIUS server project
23 /* MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
25 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
30 #include <freeradius-devel/rad_assert.h>
31 #include <freeradius-devel/md5.h>
32 #include <freeradius-devel/sha1.h>
40 extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
43 /* Allowable account control bits */
44 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
45 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
46 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
47 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
48 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
49 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
50 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
51 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
52 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
53 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
54 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
56 static int pdb_decode_acct_ctrl(const char *p)
62 * Check if the account type bits have been encoded after the
63 * NT password (in the form [NDHTUWSLXI]).
66 if (*p != '[') return 0;
68 for (p++; *p && !finished; p++) {
70 case 'N': /* 'N'o password. */
71 acct_ctrl |= ACB_PWNOTREQ;
74 case 'D': /* 'D'isabled. */
75 acct_ctrl |= ACB_DISABLED ;
78 case 'H': /* 'H'omedir required. */
79 acct_ctrl |= ACB_HOMDIRREQ;
82 case 'T': /* 'T'emp account. */
83 acct_ctrl |= ACB_TEMPDUP;
86 case 'U': /* 'U'ser account (normal). */
87 acct_ctrl |= ACB_NORMAL;
90 case 'M': /* 'M'NS logon user account. What is this? */
94 case 'W': /* 'W'orkstation account. */
95 acct_ctrl |= ACB_WSTRUST;
98 case 'S': /* 'S'erver account. */
99 acct_ctrl |= ACB_SVRTRUST;
102 case 'L': /* 'L'ocked account. */
103 acct_ctrl |= ACB_AUTOLOCK;
106 case 'X': /* No 'X'piry on password */
107 acct_ctrl |= ACB_PWNOEXP;
110 case 'I': /* 'I'nterdomain trust account. */
111 acct_ctrl |= ACB_DOMTRUST;
114 case ' ': /* ignore spaces */
131 typedef struct rlm_mschap_t {
133 int require_encryption;
135 int with_ntdomain_hack; /* this should be in another module */
137 const char *xlat_name;
139 const char *auth_type;
147 * Does dynamic translation of strings.
149 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
152 static size_t mschap_xlat(void *instance, REQUEST *request,
153 char *fmt, char *out, size_t outlen,
154 RADIUS_ESCAPE_STRING func)
157 uint8_t *data = NULL;
159 VALUE_PAIR *user_name;
160 VALUE_PAIR *chap_challenge, *response;
161 rlm_mschap_t *inst = instance;
165 func = func; /* -Wunused */
168 * Challenge means MS-CHAPv1 challenge, or
169 * hash of MS-CHAPv2 challenge, and peer challenge.
171 if (strncasecmp(fmt, "Challenge", 9) == 0) {
172 chap_challenge = pairfind(request->packet->vps,
174 VENDORPEC_MICROSOFT);
175 if (!chap_challenge) {
176 RDEBUG2("No MS-CHAP-Challenge in the request.");
181 * MS-CHAP-Challenges are 8 octets,
184 if (chap_challenge->length == 8) {
185 RDEBUG2(" mschap1: %02x",
186 chap_challenge->vp_octets[0]);
187 data = chap_challenge->vp_octets;
191 * MS-CHAP-Challenges are 16 octets,
194 } else if (chap_challenge->length == 16) {
195 VALUE_PAIR *name_attr, *response_name;
196 char *username_string;
198 RDEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
199 response = pairfind(request->packet->vps,
201 VENDORPEC_MICROSOFT);
203 RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
208 * FIXME: Much of this is copied from
209 * below. We should put it into a
214 * Responses are 50 octets.
216 if (response->length < 50) {
217 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
221 user_name = pairfind(request->packet->vps,
224 RDEBUG2("User-Name is required to calculate MS-CHAPv1 Challenge.");
229 * Check for MS-CHAP-User-Name and if found, use it
230 * to construct the MSCHAPv1 challenge. This is
231 * set by rlm_eap_mschap to the MS-CHAP Response
234 * We prefer this to the User-Name in the
237 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0);
239 name_attr = response_name;
241 name_attr = user_name;
245 * with_ntdomain_hack moved here, too.
247 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
248 if (inst->with_ntdomain_hack) {
251 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
252 username_string = name_attr->vp_strvalue;
255 username_string = name_attr->vp_strvalue;
259 ((user_name->length != response_name->length) ||
260 (strncasecmp(user_name->vp_strvalue, response_name->vp_strvalue, user_name->length) != 0))) {
261 RDEBUG("WARNING: User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", user_name->vp_strvalue, response_name->vp_strvalue);
265 * Get the MS-CHAPv1 challenge
266 * from the MS-CHAPv2 peer challenge,
267 * our challenge, and the user name.
269 RDEBUG2("Creating challenge hash with username: %s",
271 mschap_challenge_hash(response->vp_octets + 2,
272 chap_challenge->vp_octets,
273 username_string, buffer);
277 RDEBUG2("Invalid MS-CHAP challenge length");
282 * Get the MS-CHAPv1 response, or the MS-CHAPv2
285 } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
286 response = pairfind(request->packet->vps,
287 PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
288 if (!response) response = pairfind(request->packet->vps,
290 VENDORPEC_MICROSOFT);
292 RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
297 * For MS-CHAPv1, the NT-Response exists only
298 * if the second octet says so.
300 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
301 ((response->vp_octets[1] & 0x01) == 0)) {
302 RDEBUG2("No NT-Response in MS-CHAP-Response");
307 * MS-CHAP-Response and MS-CHAP2-Response have
308 * the NT-Response at the same offset, and are
311 data = response->vp_octets + 26;
315 * LM-Response is deprecated, and exists only
316 * in MS-CHAPv1, and not often there.
318 } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
319 response = pairfind(request->packet->vps,
320 PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
322 RDEBUG2("No MS-CHAP-Response was found in the request.");
327 * For MS-CHAPv1, the NT-Response exists only
328 * if the second octet says so.
330 if ((response->vp_octets[1] & 0x01) != 0) {
331 RDEBUG2("No LM-Response in MS-CHAP-Response");
334 data = response->vp_octets + 2;
338 * Pull the NT-Domain out of the User-Name, if it exists.
340 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
343 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
345 RDEBUG2("No User-Name was found in the request.");
350 * First check to see if this is a host/ style User-Name
351 * (a la Kerberos host principal)
353 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
355 * If we're getting a User-Name formatted in this way,
356 * it's likely due to PEAP. The Windows Domain will be
357 * the first domain component following the hostname,
358 * or the machine name itself if only a hostname is supplied
360 p = strchr(user_name->vp_strvalue, '.');
362 RDEBUG2("setting NT-Domain to same as machine name");
363 strlcpy(out, user_name->vp_strvalue + 5, outlen);
365 p++; /* skip the period */
368 * use the same hack as below
369 * only if another period was found
372 strlcpy(out, p, outlen);
376 p = strchr(user_name->vp_strvalue, '\\');
378 RDEBUG2("No NT-Domain was found in the User-Name.");
383 * Hack. This is simpler than the alternatives.
386 strlcpy(out, user_name->vp_strvalue, outlen);
393 * Pull the User-Name out of the User-Name...
395 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
398 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
400 RDEBUG2("No User-Name was found in the request.");
405 * First check to see if this is a host/ style User-Name
406 * (a la Kerberos host principal)
408 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
410 * If we're getting a User-Name formatted in this way,
411 * it's likely due to PEAP. When authenticating this against
412 * a Domain, Windows will expect the User-Name to be in the
413 * format of hostname$, the SAM version of the name, so we
414 * have to convert it to that here. We do so by stripping
415 * off the first 5 characters (host/), and copying everything
416 * from that point to the first period into a string and appending
419 p = strchr(user_name->vp_strvalue, '.');
421 * use the same hack as above
422 * only if a period was found
425 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
428 p = strchr(user_name->vp_strvalue, '\\');
430 p++; /* skip the backslash */
432 p = user_name->vp_strvalue; /* use the whole User-Name */
434 strlcpy(out, p, outlen);
440 * Return the NT-Hash of the passed string
442 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
446 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
447 if ((p == '\0') || (outlen <= 32))
450 while (isspace(*p)) p++;
452 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
453 RDEBUG("xlat failed");
458 mschap_ntpwdhash(buffer,buf2);
460 fr_bin2hex(buffer, out, 16);
462 RDEBUG("NT-Hash of %s = %s", buf2, out);
466 * Return the LM-Hash of the passed string
468 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
472 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
473 if ((p == '\0') || (outlen <= 32))
476 while (isspace(*p)) p++;
478 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
479 RDEBUG("xlat failed");
484 smbdes_lmpwdhash(buf2, buffer);
485 fr_bin2hex(buffer, out, 16);
487 RDEBUG("LM-Hash of %s = %s", buf2, out);
490 RDEBUG2("Unknown expansion string \"%s\"",
495 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
498 * Didn't set anything: this is bad.
501 RDEBUG2("Failed to do anything intelligent");
506 * Check the output length.
508 if (outlen < ((data_len * 2) + 1)) {
509 data_len = (outlen - 1) / 2;
515 for (i = 0; i < data_len; i++) {
516 sprintf(out + (2 * i), "%02x", data[i]);
518 out[data_len * 2] = '\0';
524 static const CONF_PARSER module_config[] = {
526 * Cache the password by default.
528 { "use_mppe", PW_TYPE_BOOLEAN,
529 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
530 { "require_encryption", PW_TYPE_BOOLEAN,
531 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
532 { "require_strong", PW_TYPE_BOOLEAN,
533 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
534 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
535 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
536 { "passwd", PW_TYPE_STRING_PTR,
537 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
538 { "ntlm_auth", PW_TYPE_STRING_PTR,
539 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
541 { "use_open_directory", PW_TYPE_BOOLEAN,
542 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
545 { NULL, -1, 0, NULL, NULL } /* end the list */
549 * deinstantiate module, free all memory allocated during
550 * mschap_instantiate()
552 static int mschap_detach(void *instance){
553 #define inst ((rlm_mschap_t *)instance)
554 if (inst->xlat_name) {
555 xlat_unregister(inst->xlat_name, mschap_xlat);
556 free(inst->xlat_name);
564 * Create instance for our module. Allocate space for
565 * instance structure and read configuration parameters
567 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
571 inst = *instance = rad_malloc(sizeof(*inst));
575 memset(inst, 0, sizeof(*inst));
577 if (cf_section_parse(conf, inst, module_config) < 0) {
583 * This module used to support SMB Password files, but it
584 * made it too complicated. If the user tries to
585 * configure an SMB Password file, then die, with an
588 if (inst->passwd_file) {
589 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
595 * Create the dynamic translation.
597 inst->xlat_name = cf_section_name2(conf);
598 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
599 inst->xlat_name = strdup(inst->xlat_name);
600 xlat_register(inst->xlat_name, mschap_xlat, inst);
603 * For backwards compatibility
605 if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
606 inst->auth_type = "MS-CHAP";
608 inst->auth_type = inst->xlat_name;
615 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
616 * attribute to reply packet
618 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
619 const char* name, const char* value, int len)
621 VALUE_PAIR *reply_attr;
622 reply_attr = pairmake(name, "", T_OP_EQ);
624 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
628 reply_attr->vp_octets[0] = ident;
629 memcpy(reply_attr->vp_octets + 1, value, len);
630 reply_attr->length = len + 1;
631 pairadd(vp, reply_attr);
635 * Add MPPE attributes to the reply.
637 static void mppe_add_reply(REQUEST *request,
638 const char* name, const uint8_t * value, int len)
641 vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
643 RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
647 memcpy(vp->vp_octets, value, len);
653 * Do the MS-CHAP stuff.
655 * This function is here so that all of the MS-CHAP related
656 * authentication is in one place, and we can perhaps later replace
657 * it with code to call winbindd, or something similar.
659 static int do_mschap(rlm_mschap_t *inst,
660 REQUEST *request, VALUE_PAIR *password,
661 uint8_t *challenge, uint8_t *response,
662 uint8_t *nthashhash, int do_ntlm_auth)
664 uint8_t calculated[24];
666 rad_assert(request != NULL);
669 * Do normal authentication.
673 * No password: can't do authentication.
676 RDEBUG2("FAILED: No NT/LM-Password. Cannot perform authentication.");
680 smbdes_mschap(password->vp_octets, challenge, calculated);
681 if (memcmp(response, calculated, 24) != 0) {
686 * If the password exists, and is an NT-Password,
687 * then calculate the hash of the NT hash. Doing this
688 * here minimizes work for later.
690 if (password && (password->attribute == PW_NT_PASSWORD)) {
691 fr_md4_calc(nthashhash, password->vp_octets, 16);
693 memset(nthashhash, 0, 16);
695 } else { /* run ntlm_auth */
699 memset(nthashhash, 0, 16);
702 * Run the program, and expect that we get 16
704 result = radius_exec_program(inst->ntlm_auth, request,
706 buffer, sizeof(buffer),
710 VALUE_PAIR *vp = NULL;
712 RDEBUG2("External script failed.");
714 vp = pairmake("Module-Failure-Message", "", T_OP_EQ);
716 radlog_request(L_ERR, 0, request, "No memory to allocate Module-Failure-Message");
717 return RLM_MODULE_FAIL;
720 p = strchr(buffer, '\n');
722 snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
723 "%s: External script says %s",
724 inst->xlat_name, buffer);
725 vp->length = strlen(vp->vp_strvalue);
726 pairadd(&request->packet->vps, vp);
731 * Parse the answer as an nthashhash.
733 * ntlm_auth currently returns:
734 * NT_KEY: 000102030405060708090a0b0c0d0e0f
736 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
737 RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
742 * Check the length. It should be at least 32,
743 * with an LF at the end.
745 if (strlen(buffer + 8) < 32) {
746 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
751 * Update the NT hash hash, from the NT key.
753 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
754 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
764 * Data for the hashes.
766 static const uint8_t SHSpad1[40] =
767 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
768 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
769 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
770 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
772 static const uint8_t SHSpad2[40] =
773 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
774 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
775 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
776 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
778 static const uint8_t magic1[27] =
779 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
780 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
781 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
783 static const uint8_t magic2[84] =
784 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
785 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
786 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
787 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
788 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
789 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
790 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
791 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
792 0x6b, 0x65, 0x79, 0x2e };
794 static const uint8_t magic3[84] =
795 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
796 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
797 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
798 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
799 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
800 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
801 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
802 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
803 0x6b, 0x65, 0x79, 0x2e };
806 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
812 fr_SHA1Init(&Context);
813 fr_SHA1Update(&Context,nt_hashhash,16);
814 fr_SHA1Update(&Context,nt_response,24);
815 fr_SHA1Update(&Context,magic1,27);
816 fr_SHA1Final(digest,&Context);
818 memcpy(masterkey,digest,16);
822 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
823 int keylen,int issend)
837 fr_SHA1Init(&Context);
838 fr_SHA1Update(&Context,masterkey,16);
839 fr_SHA1Update(&Context,SHSpad1,40);
840 fr_SHA1Update(&Context,s,84);
841 fr_SHA1Update(&Context,SHSpad2,40);
842 fr_SHA1Final(digest,&Context);
844 memcpy(sesskey,digest,keylen);
848 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
849 uint8_t *sendkey,uint8_t *recvkey)
851 uint8_t masterkey[16];
853 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
855 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
856 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
860 * Generate MPPE keys.
862 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
863 uint8_t *sendkey,uint8_t *recvkey)
868 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
871 * dictionary.microsoft defines these attributes as
872 * 'encrypt=2'. The functions in src/lib/radius.c will
873 * take care of encrypting/decrypting them as appropriate,
874 * so that we don't have to.
876 memcpy (sendkey, enckey1, 16);
877 memcpy (recvkey, enckey2, 16);
882 * mschap_authorize() - authorize user if we can authenticate
883 * it later. Add Auth-Type attribute if present in module
884 * configuration (usually Auth-Type must be "MS-CHAP")
886 static int mschap_authorize(void * instance, REQUEST *request)
888 #define inst ((rlm_mschap_t *)instance)
889 VALUE_PAIR *challenge = NULL, *response = NULL;
891 challenge = pairfind(request->packet->vps,
893 VENDORPEC_MICROSOFT);
895 return RLM_MODULE_NOOP;
898 response = pairfind(request->packet->vps,
900 VENDORPEC_MICROSOFT);
902 response = pairfind(request->packet->vps,
904 VENDORPEC_MICROSOFT);
907 * Nothing we recognize. Don't do anything.
910 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
911 return RLM_MODULE_NOOP;
914 if (pairfind(request->config_items, PW_AUTH_TYPE, 0)) {
915 RDEBUG2("WARNING: Auth-Type already set. Not setting to MS-CHAP");
916 return RLM_MODULE_NOOP;
919 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
922 * Set Auth-Type to MS-CHAP. The authentication code
923 * will take care of turning clear-text passwords into
926 if (!radius_pairmake(request, &request->config_items,
927 "Auth-Type", inst->auth_type, T_OP_EQ)) {
928 return RLM_MODULE_FAIL;
931 return RLM_MODULE_OK;
936 * mschap_authenticate() - authenticate user based on given
937 * attributes and configuration.
938 * We will try to find out password in configuration
939 * or in configured passwd file.
940 * If one is found we will check paraneters given by NAS.
942 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
944 * PAP: PW_USER_PASSWORD or
945 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
946 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
947 * In case of password mismatch or locked account we MAY return
948 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
949 * If MS-CHAP2 succeeds we MUST return
952 static int mschap_authenticate(void * instance, REQUEST *request)
954 #define inst ((rlm_mschap_t *)instance)
955 VALUE_PAIR *challenge = NULL;
956 VALUE_PAIR *response = NULL;
957 VALUE_PAIR *password = NULL;
958 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
959 VALUE_PAIR *username;
960 uint8_t nthashhash[16];
962 char *username_string;
967 * If we have ntlm_auth configured, use it unless told
970 do_ntlm_auth = (inst->ntlm_auth != NULL);
973 * If we have an ntlm_auth configuration, then we may
974 * want to suppress it.
977 VALUE_PAIR *vp = pairfind(request->config_items,
978 PW_MS_CHAP_USE_NTLM_AUTH, 0);
979 if (vp) do_ntlm_auth = vp->vp_integer;
983 * Find the SMB-Account-Ctrl attribute, or the
984 * SMB-Account-Ctrl-Text attribute.
986 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0);
988 password = pairfind(request->config_items,
989 PW_SMB_ACCOUNT_CTRL_TEXT, 0);
991 smb_ctrl = radius_pairmake(request,
992 &request->config_items,
993 "SMB-Account-CTRL", "0",
996 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1002 * We're configured to do MS-CHAP authentication.
1003 * and account control information exists. Enforce it.
1007 * Password is not required.
1009 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1010 RDEBUG2("SMB-Account-Ctrl says no password is required.");
1011 return RLM_MODULE_OK;
1016 * Decide how to get the passwords.
1018 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0);
1021 * We need an LM-Password.
1023 lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0);
1028 if ((lm_password->length == 16) ||
1029 ((lm_password->length == 32) &&
1030 (fr_hex2bin(lm_password->vp_strvalue,
1031 lm_password->vp_octets, 16) == 16))) {
1032 RDEBUG2("Found LM-Password");
1033 lm_password->length = 16;
1036 radlog_request(L_ERR, 0, request, "Invalid LM-Password");
1040 } else if (!password) {
1041 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create LM-Password.");
1043 } else { /* there is a configured Cleartext-Password */
1044 lm_password = radius_pairmake(request, &request->config_items,
1045 "LM-Password", "", T_OP_EQ);
1047 radlog_request(L_ERR, 0, request, "No memory");
1049 smbdes_lmpwdhash(password->vp_strvalue,
1050 lm_password->vp_octets);
1051 lm_password->length = 16;
1056 * We need an NT-Password.
1058 nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0);
1060 if ((nt_password->length == 16) ||
1061 ((nt_password->length == 32) &&
1062 (fr_hex2bin(nt_password->vp_strvalue,
1063 nt_password->vp_octets, 16) == 16))) {
1064 RDEBUG2("Found NT-Password");
1065 nt_password->length = 16;
1068 radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1071 } else if (!password) {
1072 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create NT-Password.");
1074 } else { /* there is a configured Cleartext-Password */
1075 nt_password = radius_pairmake(request, &request->config_items,
1076 "NT-Password", "", T_OP_EQ);
1078 radlog_request(L_ERR, 0, request, "No memory");
1079 return RLM_MODULE_FAIL;
1081 mschap_ntpwdhash(nt_password->vp_octets,
1082 password->vp_strvalue);
1083 nt_password->length = 16;
1087 challenge = pairfind(request->packet->vps,
1088 PW_MSCHAP_CHALLENGE,
1089 VENDORPEC_MICROSOFT);
1091 RDEBUG("ERROR: You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1092 return RLM_MODULE_REJECT;
1096 * We also require an MS-CHAP-Response.
1098 response = pairfind(request->packet->vps,
1100 VENDORPEC_MICROSOFT);
1103 * MS-CHAP-Response, means MS-CHAPv1
1109 * MS-CHAPv1 challenges are 8 octets.
1111 if (challenge->length < 8) {
1112 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1113 return RLM_MODULE_INVALID;
1117 * Responses are 50 octets.
1119 if (response->length < 50) {
1120 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1121 return RLM_MODULE_INVALID;
1125 * We are doing MS-CHAP. Calculate the MS-CHAP
1128 if (response->vp_octets[1] & 0x01) {
1129 RDEBUG2("Told to do MS-CHAPv1 with NT-Password");
1130 password = nt_password;
1133 RDEBUG2("Told to do MS-CHAPv1 with LM-Password");
1134 password = lm_password;
1139 * Do the MS-CHAP authentication.
1141 if (do_mschap(inst, request, password, challenge->vp_octets,
1142 response->vp_octets + offset, nthashhash,
1143 do_ntlm_auth) < 0) {
1144 RDEBUG2("MS-CHAP-Response is incorrect.");
1145 mschap_add_reply(request, &request->reply->vps,
1146 *response->vp_octets,
1147 "MS-CHAP-Error", "E=691 R=1", 9);
1148 return RLM_MODULE_REJECT;
1153 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT)) != NULL) {
1154 uint8_t mschapv1_challenge[16];
1155 VALUE_PAIR *name_attr, *response_name;
1158 * MS-CHAPv2 challenges are 16 octets.
1160 if (challenge->length < 16) {
1161 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1162 return RLM_MODULE_INVALID;
1166 * Responses are 50 octets.
1168 if (response->length < 50) {
1169 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1170 return RLM_MODULE_INVALID;
1174 * We also require a User-Name
1176 username = pairfind(request->packet->vps, PW_USER_NAME, 0);
1178 radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1179 return RLM_MODULE_INVALID;
1183 * Check for MS-CHAP-User-Name and if found, use it
1184 * to construct the MSCHAPv1 challenge. This is
1185 * set by rlm_eap_mschap to the MS-CHAP Response
1186 * packet Name field.
1188 * We prefer this to the User-Name in the
1191 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0);
1192 if (response_name) {
1193 name_attr = response_name;
1195 name_attr = username;
1199 * with_ntdomain_hack moved here, too.
1201 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1202 if (inst->with_ntdomain_hack) {
1205 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1206 username_string = name_attr->vp_strvalue;
1209 username_string = name_attr->vp_strvalue;
1212 if (response_name &&
1213 ((username->length != response_name->length) ||
1214 (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->length) != 0))) {
1215 RDEBUG("ERROR: User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", username->vp_strvalue, response_name->vp_strvalue);
1216 return RLM_MODULE_REJECT;
1221 * No "known good" NT-Password attribute. Try to do
1222 * OpenDirectory authentication.
1224 * If OD determines the user is an AD user it will return noop, which
1225 * indicates the auth process should continue directly to AD.
1226 * Otherwise OD will determine auth success/fail.
1228 if (!nt_password && inst->open_directory) {
1229 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1230 int odStatus = od_mschap_auth(request, challenge, username);
1231 if (odStatus != RLM_MODULE_NOOP) {
1237 * The old "mschapv2" function has been moved to
1240 * MS-CHAPv2 takes some additional data to create an
1241 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1243 RDEBUG2("Creating challenge hash with username: %s",
1245 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1246 challenge->vp_octets, /* our challenge */
1247 username_string, /* user name */
1248 mschapv1_challenge); /* resulting challenge */
1250 RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password",
1253 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1254 response->vp_octets + 26, nthashhash,
1255 do_ntlm_auth) < 0) {
1256 RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1257 mschap_add_reply(request, &request->reply->vps,
1258 *response->vp_octets,
1259 "MS-CHAP-Error", "E=691 R=1", 9);
1260 return RLM_MODULE_REJECT;
1263 mschap_auth_response(username_string, /* without the domain */
1264 nthashhash, /* nt-hash-hash */
1265 response->vp_octets + 26, /* peer response */
1266 response->vp_octets + 2, /* peer challenge */
1267 challenge->vp_octets, /* our challenge */
1268 msch2resp); /* calculated MPPE key */
1269 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1270 "MS-CHAP2-Success", msch2resp, 42);
1273 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1274 RDEBUG("ERROR: You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1275 return RLM_MODULE_INVALID;
1279 * We have a CHAP response, but the account may be
1280 * disabled. Reject the user with the same error code
1281 * we use when their password is invalid.
1285 * Account is disabled.
1287 * They're found, but they don't exist, so we
1288 * return 'not found'.
1290 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1291 ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1292 RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1293 mschap_add_reply(request, &request->reply->vps,
1294 *response->vp_octets,
1295 "MS-CHAP-Error", "E=691 R=1", 9);
1296 return RLM_MODULE_NOTFOUND;
1300 * User is locked out.
1302 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1303 RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1304 mschap_add_reply(request, &request->reply->vps,
1305 *response->vp_octets,
1306 "MS-CHAP-Error", "E=647 R=0", 9);
1307 return RLM_MODULE_USERLOCK;
1311 /* now create MPPE attributes */
1312 if (inst->use_mppe) {
1313 uint8_t mppe_sendkey[34];
1314 uint8_t mppe_recvkey[34];
1317 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1318 memset(mppe_sendkey, 0, 32);
1320 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1324 * According to RFC 2548 we
1325 * should send NT hash. But in
1326 * practice it doesn't work.
1327 * Instead, we should send nthashhash
1329 * This is an error on RFC 2548.
1332 * do_mschap cares to zero nthashhash if NT hash
1335 memcpy(mppe_sendkey + 8,
1337 mppe_add_reply(request,
1338 "MS-CHAP-MPPE-Keys",
1340 } else if (chap == 2) {
1341 RDEBUG2("adding MS-CHAPv2 MPPE keys");
1342 mppe_chap2_gen_keys128(nthashhash,
1343 response->vp_octets + 26,
1344 mppe_sendkey, mppe_recvkey);
1346 mppe_add_reply(request,
1349 mppe_add_reply(request,
1354 radius_pairmake(request, &request->reply->vps,
1355 "MS-MPPE-Encryption-Policy",
1356 (inst->require_encryption)? "0x00000002":"0x00000001",
1358 radius_pairmake(request, &request->reply->vps,
1359 "MS-MPPE-Encryption-Types",
1360 (inst->require_strong)? "0x00000004":"0x00000006",
1362 } /* else we weren't asked to use MPPE */
1364 return RLM_MODULE_OK;
1368 module_t rlm_mschap = {
1371 RLM_TYPE_THREAD_SAFE, /* type */
1372 mschap_instantiate, /* instantiation */
1373 mschap_detach, /* detach */
1375 mschap_authenticate, /* authenticate */
1376 mschap_authorize, /* authorize */
1377 NULL, /* pre-accounting */
1378 NULL, /* accounting */
1379 NULL, /* checksimul */
1380 NULL, /* pre-proxy */
1381 NULL, /* post-proxy */
1382 NULL /* post-auth */