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 char *username_string;
197 RDEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
198 response = pairfind(request->packet->vps,
200 VENDORPEC_MICROSOFT);
202 RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
207 * Responses are 50 octets.
209 if (response->length < 50) {
210 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
214 user_name = pairfind(request->packet->vps,
217 RDEBUG2("User-Name is required to calculateMS-CHAPv1 Challenge.");
222 * with_ntdomain_hack moved here, too.
224 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
225 if (inst->with_ntdomain_hack) {
228 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
229 username_string = user_name->vp_strvalue;
232 username_string = user_name->vp_strvalue;
236 * Get the MS-CHAPv1 challenge
237 * from the MS-CHAPv2 peer challenge,
238 * our challenge, and the user name.
240 mschap_challenge_hash(response->vp_octets + 2,
241 chap_challenge->vp_octets,
242 username_string, buffer);
246 RDEBUG2("Invalid MS-CHAP challenge length");
251 * Get the MS-CHAPv1 response, or the MS-CHAPv2
254 } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
255 response = pairfind(request->packet->vps,
256 PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
257 if (!response) response = pairfind(request->packet->vps,
259 VENDORPEC_MICROSOFT);
261 RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
266 * For MS-CHAPv1, the NT-Response exists only
267 * if the second octet says so.
269 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
270 ((response->vp_octets[1] & 0x01) == 0)) {
271 RDEBUG2("No NT-Response in MS-CHAP-Response");
276 * MS-CHAP-Response and MS-CHAP2-Response have
277 * the NT-Response at the same offset, and are
280 data = response->vp_octets + 26;
284 * LM-Response is deprecated, and exists only
285 * in MS-CHAPv1, and not often there.
287 } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
288 response = pairfind(request->packet->vps,
289 PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT);
291 RDEBUG2("No MS-CHAP-Response was found in the request.");
296 * For MS-CHAPv1, the NT-Response exists only
297 * if the second octet says so.
299 if ((response->vp_octets[1] & 0x01) != 0) {
300 RDEBUG2("No LM-Response in MS-CHAP-Response");
303 data = response->vp_octets + 2;
307 * Pull the NT-Domain out of the User-Name, if it exists.
309 } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
312 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
314 RDEBUG2("No User-Name was found in the request.");
319 * First check to see if this is a host/ style User-Name
320 * (a la Kerberos host principal)
322 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
324 * If we're getting a User-Name formatted in this way,
325 * it's likely due to PEAP. The Windows Domain will be
326 * the first domain component following the hostname,
327 * or the machine name itself if only a hostname is supplied
329 p = strchr(user_name->vp_strvalue, '.');
331 RDEBUG2("setting NT-Domain to same as machine name");
332 strlcpy(out, user_name->vp_strvalue + 5, outlen);
334 p++; /* skip the period */
337 * use the same hack as below
338 * only if another period was found
341 strlcpy(out, p, outlen);
345 p = strchr(user_name->vp_strvalue, '\\');
347 RDEBUG2("No NT-Domain was found in the User-Name.");
352 * Hack. This is simpler than the alternatives.
355 strlcpy(out, user_name->vp_strvalue, outlen);
362 * Pull the User-Name out of the User-Name...
364 } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
367 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0);
369 RDEBUG2("No User-Name was found in the request.");
374 * First check to see if this is a host/ style User-Name
375 * (a la Kerberos host principal)
377 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
379 * If we're getting a User-Name formatted in this way,
380 * it's likely due to PEAP. When authenticating this against
381 * a Domain, Windows will expect the User-Name to be in the
382 * format of hostname$, the SAM version of the name, so we
383 * have to convert it to that here. We do so by stripping
384 * off the first 5 characters (host/), and copying everything
385 * from that point to the first period into a string and appending
388 p = strchr(user_name->vp_strvalue, '.');
390 * use the same hack as above
391 * only if a period was found
394 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
397 p = strchr(user_name->vp_strvalue, '\\');
399 p++; /* skip the backslash */
401 p = user_name->vp_strvalue; /* use the whole User-Name */
403 strlcpy(out, p, outlen);
409 * Return the NT-Hash of the passed string
411 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
415 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
416 if ((p == '\0') || (outlen <= 32))
419 while (isspace(*p)) p++;
421 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
422 RDEBUG("xlat failed");
427 mschap_ntpwdhash(buffer,buf2);
429 fr_bin2hex(buffer, out, 16);
431 RDEBUG("NT-Hash of %s = %s", buf2, out);
435 * Return the LM-Hash of the passed string
437 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
441 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
442 if ((p == '\0') || (outlen <= 32))
445 while (isspace(*p)) p++;
447 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
448 RDEBUG("xlat failed");
453 smbdes_lmpwdhash(buf2, buffer);
454 fr_bin2hex(buffer, out, 16);
456 RDEBUG("LM-Hash of %s = %s", buf2, out);
459 RDEBUG2("Unknown expansion string \"%s\"",
464 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
467 * Didn't set anything: this is bad.
470 RDEBUG2("Failed to do anything intelligent");
475 * Check the output length.
477 if (outlen < ((data_len * 2) + 1)) {
478 data_len = (outlen - 1) / 2;
484 for (i = 0; i < data_len; i++) {
485 sprintf(out + (2 * i), "%02x", data[i]);
487 out[data_len * 2] = '\0';
493 static const CONF_PARSER module_config[] = {
495 * Cache the password by default.
497 { "use_mppe", PW_TYPE_BOOLEAN,
498 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
499 { "require_encryption", PW_TYPE_BOOLEAN,
500 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
501 { "require_strong", PW_TYPE_BOOLEAN,
502 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
503 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
504 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
505 { "passwd", PW_TYPE_STRING_PTR,
506 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
507 { "ntlm_auth", PW_TYPE_STRING_PTR,
508 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
510 { "use_open_directory", PW_TYPE_BOOLEAN,
511 offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
514 { NULL, -1, 0, NULL, NULL } /* end the list */
518 * deinstantiate module, free all memory allocated during
519 * mschap_instantiate()
521 static int mschap_detach(void *instance){
522 #define inst ((rlm_mschap_t *)instance)
523 if (inst->xlat_name) {
524 xlat_unregister(inst->xlat_name, mschap_xlat);
525 free(inst->xlat_name);
533 * Create instance for our module. Allocate space for
534 * instance structure and read configuration parameters
536 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
540 inst = *instance = rad_malloc(sizeof(*inst));
544 memset(inst, 0, sizeof(*inst));
546 if (cf_section_parse(conf, inst, module_config) < 0) {
552 * This module used to support SMB Password files, but it
553 * made it too complicated. If the user tries to
554 * configure an SMB Password file, then die, with an
557 if (inst->passwd_file) {
558 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
564 * Create the dynamic translation.
566 inst->xlat_name = cf_section_name2(conf);
567 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
568 inst->xlat_name = strdup(inst->xlat_name);
569 xlat_register(inst->xlat_name, mschap_xlat, inst);
572 * For backwards compatibility
574 if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
575 inst->auth_type = "MS-CHAP";
577 inst->auth_type = inst->xlat_name;
584 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
585 * attribute to reply packet
587 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
588 const char* name, const char* value, int len)
590 VALUE_PAIR *reply_attr;
591 reply_attr = pairmake(name, "", T_OP_EQ);
593 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
597 reply_attr->vp_octets[0] = ident;
598 memcpy(reply_attr->vp_octets + 1, value, len);
599 reply_attr->length = len + 1;
600 pairadd(vp, reply_attr);
604 * Add MPPE attributes to the reply.
606 static void mppe_add_reply(REQUEST *request,
607 const char* name, const uint8_t * value, int len)
610 vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
612 RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
616 memcpy(vp->vp_octets, value, len);
622 * Do the MS-CHAP stuff.
624 * This function is here so that all of the MS-CHAP related
625 * authentication is in one place, and we can perhaps later replace
626 * it with code to call winbindd, or something similar.
628 static int do_mschap(rlm_mschap_t *inst,
629 REQUEST *request, VALUE_PAIR *password,
630 uint8_t *challenge, uint8_t *response,
631 uint8_t *nthashhash, int do_ntlm_auth)
633 uint8_t calculated[24];
636 * Do normal authentication.
640 * No password: can't do authentication.
643 RDEBUG2("FAILED: No NT/LM-Password. Cannot perform authentication.");
647 smbdes_mschap(password->vp_strvalue, challenge, calculated);
648 if (memcmp(response, calculated, 24) != 0) {
653 * If the password exists, and is an NT-Password,
654 * then calculate the hash of the NT hash. Doing this
655 * here minimizes work for later.
657 if (password && (password->attribute == PW_NT_PASSWORD)) {
658 fr_md4_calc(nthashhash, password->vp_octets, 16);
660 memset(nthashhash, 0, 16);
662 } else { /* run ntlm_auth */
666 memset(nthashhash, 0, 16);
669 * Run the program, and expect that we get 16
671 result = radius_exec_program(inst->ntlm_auth, request,
673 buffer, sizeof(buffer),
676 RDEBUG2("External script failed.");
681 * Parse the answer as an nthashhash.
683 * ntlm_auth currently returns:
684 * NT_KEY: 000102030405060708090a0b0c0d0e0f
686 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
687 RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
692 * Check the length. It should be at least 32,
693 * with an LF at the end.
695 if (strlen(buffer + 8) < 32) {
696 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
701 * Update the NT hash hash, from the NT key.
703 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
704 RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
714 * Data for the hashes.
716 static const uint8_t SHSpad1[40] =
717 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
718 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
719 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
720 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
722 static const uint8_t SHSpad2[40] =
723 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
724 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
725 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
726 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
728 static const uint8_t magic1[27] =
729 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
730 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
731 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
733 static const uint8_t magic2[84] =
734 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
735 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
736 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
737 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
738 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
739 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
740 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
741 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
742 0x6b, 0x65, 0x79, 0x2e };
744 static const uint8_t magic3[84] =
745 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
746 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
747 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
748 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
749 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
750 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
751 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
752 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
753 0x6b, 0x65, 0x79, 0x2e };
756 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
762 fr_SHA1Init(&Context);
763 fr_SHA1Update(&Context,nt_hashhash,16);
764 fr_SHA1Update(&Context,nt_response,24);
765 fr_SHA1Update(&Context,magic1,27);
766 fr_SHA1Final(digest,&Context);
768 memcpy(masterkey,digest,16);
772 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
773 int keylen,int issend)
787 fr_SHA1Init(&Context);
788 fr_SHA1Update(&Context,masterkey,16);
789 fr_SHA1Update(&Context,SHSpad1,40);
790 fr_SHA1Update(&Context,s,84);
791 fr_SHA1Update(&Context,SHSpad2,40);
792 fr_SHA1Final(digest,&Context);
794 memcpy(sesskey,digest,keylen);
798 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
799 uint8_t *sendkey,uint8_t *recvkey)
801 uint8_t masterkey[16];
803 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
805 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
806 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
810 * Generate MPPE keys.
812 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
813 uint8_t *sendkey,uint8_t *recvkey)
818 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
821 * dictionary.microsoft defines these attributes as
822 * 'encrypt=2'. The functions in src/lib/radius.c will
823 * take care of encrypting/decrypting them as appropriate,
824 * so that we don't have to.
826 memcpy (sendkey, enckey1, 16);
827 memcpy (recvkey, enckey2, 16);
832 * mschap_authorize() - authorize user if we can authenticate
833 * it later. Add Auth-Type attribute if present in module
834 * configuration (usually Auth-Type must be "MS-CHAP")
836 static int mschap_authorize(void * instance, REQUEST *request)
838 #define inst ((rlm_mschap_t *)instance)
839 VALUE_PAIR *challenge = NULL, *response = NULL;
841 challenge = pairfind(request->packet->vps,
843 VENDORPEC_MICROSOFT);
845 return RLM_MODULE_NOOP;
848 response = pairfind(request->packet->vps,
850 VENDORPEC_MICROSOFT);
852 response = pairfind(request->packet->vps,
854 VENDORPEC_MICROSOFT);
857 * Nothing we recognize. Don't do anything.
860 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
861 return RLM_MODULE_NOOP;
864 if (pairfind(request->config_items, PW_AUTH_TYPE, 0)) {
865 RDEBUG2("Found existing Auth-Type. Not changing it.");
866 return RLM_MODULE_NOOP;
869 RDEBUG2("Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
872 * Set Auth-Type to MS-CHAP. The authentication code
873 * will take care of turning clear-text passwords into
876 if (!radius_pairmake(request, &request->config_items,
877 "Auth-Type", inst->auth_type, T_OP_EQ)) {
878 return RLM_MODULE_FAIL;
881 return RLM_MODULE_OK;
886 * mschap_authenticate() - authenticate user based on given
887 * attributes and configuration.
888 * We will try to find out password in configuration
889 * or in configured passwd file.
890 * If one is found we will check paraneters given by NAS.
892 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
894 * PAP: PW_USER_PASSWORD or
895 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
896 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
897 * In case of password mismatch or locked account we MAY return
898 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
899 * If MS-CHAP2 succeeds we MUST return
902 static int mschap_authenticate(void * instance, REQUEST *request)
904 #define inst ((rlm_mschap_t *)instance)
905 VALUE_PAIR *challenge = NULL;
906 VALUE_PAIR *response = NULL;
907 VALUE_PAIR *password = NULL;
908 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
909 VALUE_PAIR *username;
910 uint8_t nthashhash[16];
912 char *username_string;
917 * If we have ntlm_auth configured, use it unless told
920 do_ntlm_auth = (inst->ntlm_auth != NULL);
923 * If we have an ntlm_auth configuration, then we may
924 * want to suppress it.
927 VALUE_PAIR *vp = pairfind(request->config_items,
928 PW_MS_CHAP_USE_NTLM_AUTH, 0);
929 if (vp) do_ntlm_auth = vp->vp_integer;
933 * Find the SMB-Account-Ctrl attribute, or the
934 * SMB-Account-Ctrl-Text attribute.
936 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0);
938 password = pairfind(request->config_items,
939 PW_SMB_ACCOUNT_CTRL_TEXT, 0);
941 smb_ctrl = radius_pairmake(request,
942 &request->config_items,
943 "SMB-Account-CTRL", "0",
946 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
952 * We're configured to do MS-CHAP authentication.
953 * and account control information exists. Enforce it.
957 * Password is not required.
959 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
960 RDEBUG2("SMB-Account-Ctrl says no password is required.");
961 return RLM_MODULE_OK;
966 * Decide how to get the passwords.
968 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0);
971 * We need an LM-Password.
973 lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0);
978 if ((lm_password->length == 16) ||
979 ((lm_password->length == 32) &&
980 (fr_hex2bin(lm_password->vp_strvalue,
981 lm_password->vp_octets, 16) == 16))) {
982 RDEBUG2("Found LM-Password");
983 lm_password->length = 16;
986 radlog_request(L_ERR, 0, request, "Invalid LM-Password");
990 } else if (!password) {
991 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create LM-Password.");
993 } else { /* there is a configured Cleartext-Password */
994 lm_password = radius_pairmake(request, &request->config_items,
995 "LM-Password", "", T_OP_EQ);
997 radlog_request(L_ERR, 0, request, "No memory");
999 smbdes_lmpwdhash(password->vp_strvalue,
1000 lm_password->vp_octets);
1001 lm_password->length = 16;
1006 * We need an NT-Password.
1008 nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0);
1010 if ((nt_password->length == 16) ||
1011 ((nt_password->length == 32) &&
1012 (fr_hex2bin(nt_password->vp_strvalue,
1013 nt_password->vp_octets, 16) == 16))) {
1014 RDEBUG2("Found NT-Password");
1015 nt_password->length = 16;
1018 radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1021 } else if (!password) {
1022 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured. Cannot create NT-Password.");
1024 } else { /* there is a configured Cleartext-Password */
1025 nt_password = radius_pairmake(request, &request->config_items,
1026 "NT-Password", "", T_OP_EQ);
1028 radlog_request(L_ERR, 0, request, "No memory");
1029 return RLM_MODULE_FAIL;
1031 mschap_ntpwdhash(nt_password->vp_octets,
1032 password->vp_strvalue);
1033 nt_password->length = 16;
1037 challenge = pairfind(request->packet->vps,
1038 PW_MSCHAP_CHALLENGE,
1039 VENDORPEC_MICROSOFT);
1041 RDEBUG2("No MS-CHAP-Challenge in the request");
1042 return RLM_MODULE_REJECT;
1046 * We also require an MS-CHAP-Response.
1048 response = pairfind(request->packet->vps,
1050 VENDORPEC_MICROSOFT);
1053 * MS-CHAP-Response, means MS-CHAPv1
1059 * MS-CHAPv1 challenges are 8 octets.
1061 if (challenge->length < 8) {
1062 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1063 return RLM_MODULE_INVALID;
1067 * Responses are 50 octets.
1069 if (response->length < 50) {
1070 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1071 return RLM_MODULE_INVALID;
1075 * We are doing MS-CHAP. Calculate the MS-CHAP
1078 if (response->vp_octets[1] & 0x01) {
1079 RDEBUG2("Told to do MS-CHAPv1 with NT-Password");
1080 password = nt_password;
1083 RDEBUG2("Told to do MS-CHAPv1 with LM-Password");
1084 password = lm_password;
1089 * Do the MS-CHAP authentication.
1091 if (do_mschap(inst, request, password, challenge->vp_octets,
1092 response->vp_octets + offset, nthashhash,
1093 do_ntlm_auth) < 0) {
1094 RDEBUG2("MS-CHAP-Response is incorrect.");
1095 mschap_add_reply(request, &request->reply->vps,
1096 *response->vp_octets,
1097 "MS-CHAP-Error", "E=691 R=1", 9);
1098 return RLM_MODULE_REJECT;
1103 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT)) != NULL) {
1104 uint8_t mschapv1_challenge[16];
1107 * MS-CHAPv2 challenges are 16 octets.
1109 if (challenge->length < 16) {
1110 radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1111 return RLM_MODULE_INVALID;
1115 * Responses are 50 octets.
1117 if (response->length < 50) {
1118 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1119 return RLM_MODULE_INVALID;
1123 * We also require a User-Name
1125 username = pairfind(request->packet->vps, PW_USER_NAME, 0);
1127 radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1128 return RLM_MODULE_INVALID;
1133 * with_ntdomain_hack moved here
1135 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1136 if (inst->with_ntdomain_hack) {
1139 RDEBUG2(" NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1140 username_string = username->vp_strvalue;
1143 username_string = username->vp_strvalue;
1148 * No "known good" NT-Password attribute. Try to do
1149 * OpenDirectory authentication.
1151 * If OD determines the user is an AD user it will return noop, which
1152 * indicates the auth process should continue directly to AD.
1153 * Otherwise OD will determine auth success/fail.
1155 if (!nt_password && inst->open_directory) {
1156 RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1157 int odStatus = od_mschap_auth(request, challenge, username);
1158 if (odStatus != RLM_MODULE_NOOP) {
1164 * The old "mschapv2" function has been moved to
1167 * MS-CHAPv2 takes some additional data to create an
1168 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1170 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1171 challenge->vp_octets, /* our challenge */
1172 username_string, /* user name */
1173 mschapv1_challenge); /* resulting challenge */
1175 RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password",
1178 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1179 response->vp_octets + 26, nthashhash,
1180 do_ntlm_auth) < 0) {
1181 RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1182 mschap_add_reply(request, &request->reply->vps,
1183 *response->vp_octets,
1184 "MS-CHAP-Error", "E=691 R=1", 9);
1185 return RLM_MODULE_REJECT;
1189 * Get the NT-hash-hash, if necessary
1194 mschap_auth_response(username_string, /* without the domain */
1195 nthashhash, /* nt-hash-hash */
1196 response->vp_octets + 26, /* peer response */
1197 response->vp_octets + 2, /* peer challenge */
1198 challenge->vp_octets, /* our challenge */
1199 msch2resp); /* calculated MPPE key */
1200 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1201 "MS-CHAP2-Success", msch2resp, 42);
1204 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1205 radlog_request(L_AUTH, 0, request, "No MS-CHAP response found");
1206 return RLM_MODULE_INVALID;
1210 * We have a CHAP response, but the account may be
1211 * disabled. Reject the user with the same error code
1212 * we use when their password is invalid.
1216 * Account is disabled.
1218 * They're found, but they don't exist, so we
1219 * return 'not found'.
1221 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1222 ((smb_ctrl->vp_integer & ACB_NORMAL) == 0)) {
1223 RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1224 mschap_add_reply(request, &request->reply->vps,
1225 *response->vp_octets,
1226 "MS-CHAP-Error", "E=691 R=1", 9);
1227 return RLM_MODULE_NOTFOUND;
1231 * User is locked out.
1233 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1234 RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1235 mschap_add_reply(request, &request->reply->vps,
1236 *response->vp_octets,
1237 "MS-CHAP-Error", "E=647 R=0", 9);
1238 return RLM_MODULE_USERLOCK;
1242 /* now create MPPE attributes */
1243 if (inst->use_mppe) {
1244 uint8_t mppe_sendkey[34];
1245 uint8_t mppe_recvkey[34];
1248 RDEBUG2("adding MS-CHAPv1 MPPE keys");
1249 memset(mppe_sendkey, 0, 32);
1251 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1255 * According to RFC 2548 we
1256 * should send NT hash. But in
1257 * practice it doesn't work.
1258 * Instead, we should send nthashhash
1260 * This is an error on RFC 2548.
1263 * do_mschap cares to zero nthashhash if NT hash
1266 memcpy(mppe_sendkey + 8,
1268 mppe_add_reply(request,
1269 "MS-CHAP-MPPE-Keys",
1271 } else if (chap == 2) {
1272 RDEBUG2("adding MS-CHAPv2 MPPE keys");
1273 mppe_chap2_gen_keys128(nthashhash,
1274 response->vp_octets + 26,
1275 mppe_sendkey, mppe_recvkey);
1277 mppe_add_reply(request,
1280 mppe_add_reply(request,
1285 radius_pairmake(request, &request->reply->vps,
1286 "MS-MPPE-Encryption-Policy",
1287 (inst->require_encryption)? "0x00000002":"0x00000001",
1289 radius_pairmake(request, &request->reply->vps,
1290 "MS-MPPE-Encryption-Types",
1291 (inst->require_strong)? "0x00000004":"0x00000006",
1293 } /* else we weren't asked to use MPPE */
1295 return RLM_MODULE_OK;
1299 module_t rlm_mschap = {
1302 RLM_TYPE_THREAD_SAFE, /* type */
1303 mschap_instantiate, /* instantiation */
1304 mschap_detach, /* detach */
1306 mschap_authenticate, /* authenticate */
1307 mschap_authorize, /* authorize */
1308 NULL, /* pre-accounting */
1309 NULL, /* accounting */
1310 NULL, /* checksimul */
1311 NULL, /* pre-proxy */
1312 NULL, /* post-proxy */
1313 NULL /* post-auth */