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 2000,2001 The FreeRADIUS server project
25 * mschap.c MS-CHAP module
27 * This implements MS-CHAP, as described in RFC 2548
29 * http://www.freeradius.org/rfc/rfc2548.txt
34 * If you have any questions on NTLM (Samba) passwords
35 * support, LM authentication and MS-CHAP v2 support
38 * Vladimir Dubrovin vlad@sandy.ru
40 * ZARAZA 3APA3A@security.nnov.ru
43 /* MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
58 #include "rad_assert.h"
62 static const char rcsid[] = "$Id$";
65 /* Allowable account control bits */
66 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
67 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
68 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
69 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
70 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
71 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
72 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
73 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
74 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
75 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
76 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
78 static int pdb_decode_acct_ctrl(const char *p)
84 * Check if the account type bits have been encoded after the
85 * NT password (in the form [NDHTUWSLXI]).
88 if (*p != '[') return 0;
90 for (p++; *p && !finished; p++) {
92 case 'N': /* 'N'o password. */
93 acct_ctrl |= ACB_PWNOTREQ;
96 case 'D': /* 'D'isabled. */
97 acct_ctrl |= ACB_DISABLED ;
100 case 'H': /* 'H'omedir required. */
101 acct_ctrl |= ACB_HOMDIRREQ;
104 case 'T': /* 'T'emp account. */
105 acct_ctrl |= ACB_TEMPDUP;
108 case 'U': /* 'U'ser account (normal). */
109 acct_ctrl |= ACB_NORMAL;
112 case 'M': /* 'M'NS logon user account. What is this? */
113 acct_ctrl |= ACB_MNS;
116 case 'W': /* 'W'orkstation account. */
117 acct_ctrl |= ACB_WSTRUST;
120 case 'S': /* 'S'erver account. */
121 acct_ctrl |= ACB_SVRTRUST;
124 case 'L': /* 'L'ocked account. */
125 acct_ctrl |= ACB_AUTOLOCK;
128 case 'X': /* No 'X'piry on password */
129 acct_ctrl |= ACB_PWNOEXP;
132 case 'I': /* 'I'nterdomain trust account. */
133 acct_ctrl |= ACB_DOMTRUST;
136 case ' ': /* ignore spaces */
154 * ntpwdhash converts Unicode password to 16-byte NT hash
157 static void ntpwdhash (char *szHash, const char *szPassword)
159 char szUnicodePass[513];
164 * NT passwords are unicode. Convert plain text password
165 * to unicode by inserting a zero every other byte
167 nPasswordLen = strlen(szPassword);
168 for (i = 0; i < nPasswordLen; i++) {
169 szUnicodePass[i << 1] = szPassword[i];
170 szUnicodePass[(i << 1) + 1] = 0;
173 /* Encrypt Unicode password to a 16-byte MD4 hash */
174 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
179 * challenge_hash() is used by mschap2() and auth_response()
180 * implements RFC2759 ChallengeHash()
181 * generates 64 bit challenge
183 static void challenge_hash( const char *peer_challenge,
184 const char *auth_challenge,
185 const char *user_name, char *challenge )
191 SHA1Update(&Context, peer_challenge, 16);
192 SHA1Update(&Context, auth_challenge, 16);
193 SHA1Update(&Context, user_name, strlen(user_name));
194 SHA1Final(hash, &Context);
195 memcpy(challenge, hash, 8);
199 * auth_response() generates MS-CHAP v2 SUCCESS response
200 * according to RFC 2759 GenerateAuthenticatorResponse()
201 * returns 42-octet response string
203 static void auth_response(const char *username,
204 const char *nt_hash_hash,
206 char *peer_challenge, char *auth_challenge,
210 const char magic1[39] =
211 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
212 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
213 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
214 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
216 const char magic2[41] =
217 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
218 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
219 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
220 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
227 SHA1Update(&Context, nt_hash_hash, 16);
228 SHA1Update(&Context, ntresponse, 24);
229 SHA1Update(&Context, magic1, 39);
230 SHA1Final(digest, &Context);
231 challenge_hash(peer_challenge, auth_challenge, username, challenge);
233 SHA1Update(&Context, digest, 20);
234 SHA1Update(&Context, challenge, 8);
235 SHA1Update(&Context, magic2, 41);
236 SHA1Final(digest, &Context);
239 * Encode the value of 'Digest' as "S=" followed by
240 * 40 ASCII hexadecimal digits and return it in
241 * AuthenticatorResponse.
243 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
247 lrad_bin2hex(digest, response + 2, 20);
251 typedef struct rlm_mschap_t {
253 int require_encryption;
255 int with_ntdomain_hack; /* this should be in another module */
258 char *auth_type; /* I don't think this is needed... */
264 * Does dynamic translation of strings.
266 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
269 static int mschap_xlat(void *instance, REQUEST *request,
270 char *fmt, char *out, size_t outlen,
271 RADIUS_ESCAPE_STRING func)
274 uint8_t *data = NULL;
276 VALUE_PAIR *user_name;
277 VALUE_PAIR *chap_challenge, *response;
278 rlm_mschap_t *inst = instance;
280 chap_challenge = response = NULL;
282 func = func; /* -Wunused */
285 * Challenge means MS-CHAPv1 challenge, or
286 * hash of MS-CHAPv2 challenge, and peer challenge.
288 if (strcasecmp(fmt, "Challenge") == 0) {
289 chap_challenge = pairfind(request->packet->vps,
290 PW_MSCHAP_CHALLENGE);
291 if (!chap_challenge) {
292 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
297 * MS-CHAP-Challenges are 8 octets,
300 if (chap_challenge->length == 8) {
301 DEBUG2(" mschap1: %02x", chap_challenge->vp_strvalue[0]);
302 data = chap_challenge->vp_strvalue;
306 * MS-CHAP-Challenges are 16 octets,
309 } else if (chap_challenge->length == 16) {
310 char *username_string;
312 DEBUG2(" mschap2: %02x", chap_challenge->vp_strvalue[0]);
313 response = pairfind(request->packet->vps,
314 PW_MSCHAP2_RESPONSE);
316 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
321 * Responses are 50 octets.
323 if (response->length < 50) {
324 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
328 user_name = pairfind(request->packet->vps,
331 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
336 * with_ntdomain_hack moved here, too.
338 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
339 if (inst->with_ntdomain_hack) {
342 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
343 username_string = user_name->vp_strvalue;
346 username_string = user_name->vp_strvalue;
350 * Get the MS-CHAPv1 challenge
351 * from the MS-CHAPv2 peer challenge,
352 * our challenge, and the user name.
354 challenge_hash(response->vp_strvalue + 2,
355 chap_challenge->vp_strvalue,
356 username_string, buffer);
360 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
365 * Get the MS-CHAPv1 response, or the MS-CHAPv2
368 } else if (strcasecmp(fmt, "NT-Response") == 0) {
369 response = pairfind(request->packet->vps,
371 if (!response) response = pairfind(request->packet->vps,
372 PW_MSCHAP2_RESPONSE);
374 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
379 * For MS-CHAPv1, the NT-Response exists only
380 * if the second octet says so.
382 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
383 ((response->vp_strvalue[1] & 0x01) == 0)) {
384 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
389 * MS-CHAP-Response and MS-CHAP2-Response have
390 * the NT-Response at the same offset, and are
393 data = response->vp_strvalue + 26;
397 * LM-Response is deprecated, and exists only
398 * in MS-CHAPv1, and not often there.
400 } else if (strcasecmp(fmt, "LM-Response") == 0) {
401 response = pairfind(request->packet->vps,
404 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
409 * For MS-CHAPv1, the NT-Response exists only
410 * if the second octet says so.
412 if ((response->vp_strvalue[1] & 0x01) != 0) {
413 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
416 data = response->vp_strvalue + 2;
420 * Pull the NT-Domain out of the User-Name, if it exists.
422 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
425 user_name = pairfind(request->packet->vps, PW_USER_NAME);
427 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
431 p = strchr(user_name->vp_strvalue, '\\');
433 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
438 * Hack. This is simpler than the alternatives.
441 strNcpy(out, user_name->vp_strvalue, outlen);
447 * Pull the User-Name out of the User-Name...
449 } else if (strcasecmp(fmt, "User-Name") == 0) {
452 user_name = pairfind(request->packet->vps, PW_USER_NAME);
454 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
458 p = strchr(user_name->vp_strvalue, '\\');
460 p++; /* skip the backslash */
462 p = user_name->vp_strvalue; /* use the whole User-Name */
465 strNcpy(out, p, outlen);
469 * Return the NT-Hash of the passed string
471 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
474 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
475 if ((p == '\0') || (outlen <= 32))
477 DEBUG("rlm_mschap: NT-Hash: %s",p);
480 lrad_bin2hex(buffer, out, 16);
482 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
486 * Return the LM-Hash of the passed string
488 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
491 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
492 if ((p == '\0') || (outlen <= 32))
495 DEBUG("rlm_mschap: LM-Hash: %s",p);
496 smbdes_lmpwdhash(p,buffer);
497 lrad_bin2hex(buffer, out, 16);
499 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
502 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
507 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
510 * Didn't set anything: this is bad.
513 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
518 * Check the output length.
520 if (outlen < ((data_len * 2) + 1)) {
521 data_len = (outlen - 1) / 2;
527 for (i = 0; i < data_len; i++) {
528 sprintf(out + (2 * i), "%02x", data[i]);
530 out[data_len * 2] = '\0';
536 static const CONF_PARSER module_config[] = {
538 * Cache the password by default.
540 { "use_mppe", PW_TYPE_BOOLEAN,
541 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
542 { "require_encryption", PW_TYPE_BOOLEAN,
543 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
544 { "require_strong", PW_TYPE_BOOLEAN,
545 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
546 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
547 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
548 { "passwd", PW_TYPE_STRING_PTR,
549 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
550 { "authtype", PW_TYPE_STRING_PTR,
551 offsetof(rlm_mschap_t, auth_type), NULL, NULL },
552 { "ntlm_auth", PW_TYPE_STRING_PTR,
553 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
555 { NULL, -1, 0, NULL, NULL } /* end the list */
559 * deinstantiate module, free all memory allocated during
560 * mschap_instantiate()
562 static int mschap_detach(void *instance){
563 #define inst ((rlm_mschap_t *)instance)
564 if (inst->passwd_file) free(inst->passwd_file);
565 if (inst->auth_type) free(inst->auth_type);
566 if (inst->ntlm_auth) free(inst->ntlm_auth);
567 if (inst->xlat_name) {
568 xlat_unregister(inst->xlat_name, mschap_xlat);
569 free(inst->xlat_name);
577 * Create instance for our module. Allocate space for
578 * instance structure and read configuration parameters
580 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
582 const char *xlat_name;
585 inst = *instance = rad_malloc(sizeof(*inst));
589 memset(inst, 0, sizeof(*inst));
591 if (cf_section_parse(conf, inst, module_config) < 0) {
597 * This module used to support SMB Password files, but it
598 * made it too complicated. If the user tries to
599 * configure an SMB Password file, then die, with an
602 if (inst->passwd_file) {
603 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
609 * Create the dynamic translation.
611 if (cf_section_name1(conf))
612 xlat_register(cf_section_name1(conf),mschap_xlat, inst);
614 if ((xlat_name = cf_section_name2(conf)) != NULL)
615 xlat_register(xlat_name, mschap_xlat, inst);
616 if (xlat_name == NULL)
617 xlat_name = cf_section_name1(conf);
619 inst->xlat_name = strdup(xlat_name);
625 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
626 * attribute to reply packet
628 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
629 const char* name, const char* value, int len)
631 VALUE_PAIR *reply_attr;
632 reply_attr = pairmake(name, "", T_OP_EQ);
634 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
638 reply_attr->vp_strvalue[0] = ident;
639 memcpy(reply_attr->vp_strvalue + 1, value, len);
640 reply_attr->length = len + 1;
641 pairadd(vp, reply_attr);
645 * Add MPPE attributes to the reply.
647 static void mppe_add_reply(VALUE_PAIR **vp,
648 const char* name, const char* value, int len)
650 VALUE_PAIR *reply_attr;
651 reply_attr = pairmake(name, "", T_OP_EQ);
653 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
657 memcpy(reply_attr->vp_strvalue, value, len);
658 reply_attr->length = len;
659 pairadd(vp, reply_attr);
664 * Do the MS-CHAP stuff.
666 * This function is here so that all of the MS-CHAP related
667 * authentication is in one place, and we can perhaps later replace
668 * it with code to call winbindd, or something similar.
670 static int do_mschap(rlm_mschap_t *inst,
671 REQUEST *request, VALUE_PAIR *password,
672 uint8_t *challenge, uint8_t *response,
675 int do_ntlm_auth = 0;
676 uint8_t calculated[24];
677 VALUE_PAIR *vp = NULL;
680 * If we have ntlm_auth configured, use it unless told
683 if (inst->ntlm_auth) do_ntlm_auth = 1;
686 * If we have an ntlm_auth configuration, then we may
689 vp = pairfind(request->config_items,
690 PW_MS_CHAP_USE_NTLM_AUTH);
691 if (vp) do_ntlm_auth = vp->lvalue;
694 * No ntlm_auth configured, attribute to tell us to
695 * use it exists, and we're told to use it. We don't
698 if (!inst->ntlm_auth && do_ntlm_auth) {
699 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
704 * Do normal authentication.
708 * No password: can't do authentication.
711 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
715 smbdes_mschap(password->vp_strvalue, challenge, calculated);
716 if (memcmp(response, calculated, 24) != 0) {
721 * If the password exists, and is an NT-Password,
722 * then calculate the hash of the NT hash. Doing this
723 * here minimizes work for later.
725 if (password && (password->attribute == PW_NT_PASSWORD)) {
726 md4_calc(nthashhash, password->vp_strvalue, 16);
728 memset(nthashhash, 0, 16);
730 } else { /* run ntlm_auth */
734 memset(nthashhash, 0, 16);
737 * Run the program, and expect that we get 16
739 result = radius_exec_program(inst->ntlm_auth, request,
741 buffer, sizeof(buffer),
744 DEBUG2(" rlm_mschap: External script failed.");
749 * Parse the answer as an nthashhash.
751 * ntlm_auth currently returns:
752 * NT_KEY: 000102030405060708090a0b0c0d0e0f
754 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
755 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
760 * Check the length. It should be at least 32,
761 * with an LF at the end.
763 if (strlen(buffer + 8) < 32) {
764 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
769 * Update the NT hash hash, from the NT key.
771 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
772 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
782 * Data for the hashes.
784 static const uint8_t SHSpad1[40] =
785 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
786 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
787 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
788 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
790 static const uint8_t SHSpad2[40] =
791 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
792 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
793 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
794 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
796 static const uint8_t magic1[27] =
797 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
798 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
799 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
801 static const uint8_t magic2[84] =
802 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
803 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
804 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
805 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
806 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
807 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
808 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
809 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
810 0x6b, 0x65, 0x79, 0x2e };
812 static const uint8_t magic3[84] =
813 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
814 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
815 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
816 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
817 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
818 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
819 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
820 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
821 0x6b, 0x65, 0x79, 0x2e };
824 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
831 SHA1Update(&Context,nt_hashhash,16);
832 SHA1Update(&Context,nt_response,24);
833 SHA1Update(&Context,magic1,27);
834 SHA1Final(digest,&Context);
836 memcpy(masterkey,digest,16);
840 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
841 int keylen,int issend)
856 SHA1Update(&Context,masterkey,16);
857 SHA1Update(&Context,SHSpad1,40);
858 SHA1Update(&Context,s,84);
859 SHA1Update(&Context,SHSpad2,40);
860 SHA1Final(digest,&Context);
862 memcpy(sesskey,digest,keylen);
866 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
867 uint8_t *sendkey,uint8_t *recvkey)
869 uint8_t masterkey[16];
871 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
873 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
874 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
878 * Generate MPPE keys.
880 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
881 uint8_t *sendkey,uint8_t *recvkey)
886 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
889 * dictionary.microsoft defines these attributes as
890 * 'encrypt=2'. The functions in src/lib/radius.c will
891 * take care of encrypting/decrypting them as appropriate,
892 * so that we don't have to.
894 memcpy (sendkey, enckey1, 16);
895 memcpy (recvkey, enckey2, 16);
900 * mschap_authorize() - authorize user if we can authenticate
901 * it later. Add Auth-Type attribute if present in module
902 * configuration (usually Auth-Type must be "MS-CHAP")
904 static int mschap_authorize(void * instance, REQUEST *request)
906 #define inst ((rlm_mschap_t *)instance)
907 VALUE_PAIR *challenge = NULL, *response = NULL;
909 const char *authtype_name = "MS-CHAP";
911 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
913 return RLM_MODULE_NOOP;
916 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
918 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
921 * Nothing we recognize. Don't do anything.
924 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
925 return RLM_MODULE_NOOP;
929 * Choose MS-CHAP, or whatever else they told us to use.
931 if (inst->auth_type) {
932 authtype_name = inst->auth_type;
935 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", authtype_name);
938 * Set Auth-Type to MS-CHAP. The authentication code
939 * will take care of turning clear-text passwords into
942 pairdelete(&request->config_items, PW_AUTHTYPE);
943 vp = pairmake("Auth-Type", authtype_name, T_OP_EQ);
944 rad_assert(vp != NULL);
945 pairadd(&request->config_items, vp);
947 return RLM_MODULE_OK;
952 * mschap_authenticate() - authenticate user based on given
953 * attributes and configuration.
954 * We will try to find out password in configuration
955 * or in configured passwd file.
956 * If one is found we will check paraneters given by NAS.
958 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
960 * PAP: PW_PASSWORD or
961 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
962 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
963 * In case of password mismatch or locked account we MAY return
964 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
965 * If MS-CHAP2 succeeds we MUST return
968 static int mschap_authenticate(void * instance, REQUEST *request)
970 #define inst ((rlm_mschap_t *)instance)
971 VALUE_PAIR *challenge = NULL, *response = NULL;
972 VALUE_PAIR *password = NULL;
973 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
974 VALUE_PAIR *username;
975 VALUE_PAIR *reply_attr;
976 uint8_t nthashhash[16];
977 uint8_t msch2resp[42];
978 char *username_string;
982 * Find the SMB-Account-Ctrl attribute, or the
983 * SMB-Account-Ctrl-Text attribute.
985 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
987 password = pairfind(request->config_items,
988 PW_SMB_ACCOUNT_CTRL_TEXT);
990 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
991 pairadd(&request->config_items, smb_ctrl);
992 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->vp_strvalue);
997 * We're configured to do MS-CHAP authentication.
998 * and account control information exists. Enforce it.
1002 * Password is not required.
1004 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1005 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1006 return RLM_MODULE_OK;
1011 * Decide how to get the passwords.
1013 password = pairfind(request->config_items, PW_PASSWORD);
1016 * We need an LM-Password.
1018 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1023 if ((lm_password->length == 16) ||
1024 ((lm_password->length == 32) &&
1025 (lrad_hex2bin(lm_password->vp_strvalue,
1026 lm_password->vp_strvalue, 16) == 16))) {
1027 DEBUG2(" rlm_mschap: Found LM-Password");
1028 lm_password->length = 16;
1031 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1035 } else if (!password) {
1036 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create LM-Password.");
1038 } else { /* there is a configured User-Password */
1039 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1041 radlog(L_ERR, "No memory");
1043 smbdes_lmpwdhash(password->vp_strvalue,
1044 lm_password->vp_strvalue);
1045 lm_password->length = 16;
1046 pairadd(&request->config_items, lm_password);
1051 * We need an NT-Password.
1053 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1055 if ((nt_password->length == 16) ||
1056 ((nt_password->length == 32) &&
1057 (lrad_hex2bin(nt_password->vp_strvalue,
1058 nt_password->vp_strvalue, 16) == 16))) {
1059 DEBUG2(" rlm_mschap: Found NT-Password");
1060 nt_password->length = 16;
1063 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1066 } else if (!password) {
1067 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create NT-Password.");
1069 } else { /* there is a configured User-Password */
1070 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1072 radlog(L_ERR, "No memory");
1073 return RLM_MODULE_FAIL;
1075 ntpwdhash(nt_password->vp_strvalue, password->vp_strvalue);
1076 nt_password->length = 16;
1077 pairadd(&request->config_items, nt_password);
1081 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1083 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1084 return RLM_MODULE_REJECT;
1088 * We also require an MS-CHAP-Response.
1090 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1093 * MS-CHAP-Response, means MS-CHAPv1
1099 * MS-CHAPv1 challenges are 8 octets.
1101 if (challenge->length < 8) {
1102 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1103 return RLM_MODULE_INVALID;
1107 * Responses are 50 octets.
1109 if (response->length < 50) {
1110 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1111 return RLM_MODULE_INVALID;
1115 * We are doing MS-CHAP. Calculate the MS-CHAP
1118 if (response->vp_strvalue[1] & 0x01) {
1119 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1120 password = nt_password;
1123 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1124 password = lm_password;
1129 * Do the MS-CHAP authentication.
1131 if (do_mschap(inst, request, password, challenge->vp_strvalue,
1132 response->vp_strvalue + offset, nthashhash) < 0) {
1133 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1134 add_reply(&request->reply->vps, *response->vp_strvalue,
1135 "MS-CHAP-Error", "E=691 R=1", 9);
1136 return RLM_MODULE_REJECT;
1141 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1142 uint8_t mschapv1_challenge[16];
1145 * MS-CHAPv2 challenges are 16 octets.
1147 if (challenge->length < 16) {
1148 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1149 return RLM_MODULE_INVALID;
1153 * Responses are 50 octets.
1155 if (response->length < 50) {
1156 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1157 return RLM_MODULE_INVALID;
1161 * We also require a User-Name
1163 username = pairfind(request->packet->vps, PW_USER_NAME);
1165 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1166 return RLM_MODULE_INVALID;
1171 * with_ntdomain_hack moved here
1173 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1174 if (inst->with_ntdomain_hack) {
1177 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1178 username_string = username->vp_strvalue;
1181 username_string = username->vp_strvalue;
1185 * The old "mschapv2" function has been moved to
1188 * MS-CHAPv2 takes some additional data to create an
1189 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1191 challenge_hash(response->vp_strvalue + 2, /* peer challenge */
1192 challenge->vp_strvalue, /* our challenge */
1193 username_string, /* user name */
1194 mschapv1_challenge); /* resulting challenge */
1196 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1199 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1200 response->vp_strvalue + 26, nthashhash) < 0) {
1201 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1202 add_reply(&request->reply->vps, *response->vp_strvalue,
1203 "MS-CHAP-Error", "E=691 R=1", 9);
1204 return RLM_MODULE_REJECT;
1208 * Get the NT-hash-hash, if necessary
1213 auth_response(username_string, /* without the domain */
1214 nthashhash, /* nt-hash-hash */
1215 response->vp_strvalue + 26, /* peer response */
1216 response->vp_strvalue + 2, /* peer challenge */
1217 challenge->vp_strvalue, /* our challenge */
1218 msch2resp); /* calculated MPPE key */
1219 add_reply( &request->reply->vps, *response->vp_strvalue,
1220 "MS-CHAP2-Success", msch2resp, 42);
1223 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1224 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1225 return RLM_MODULE_INVALID;
1229 * We have a CHAP response, but the account may be
1230 * disabled. Reject the user with the same error code
1231 * we use when their password is invalid.
1235 * Account is disabled.
1237 * They're found, but they don't exist, so we
1238 * return 'not found'.
1240 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1241 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1242 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1243 add_reply( &request->reply->vps, *response->vp_strvalue,
1244 "MS-CHAP-Error", "E=691 R=1", 9);
1245 return RLM_MODULE_NOTFOUND;
1249 * User is locked out.
1251 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1252 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1253 add_reply( &request->reply->vps, *response->vp_strvalue,
1254 "MS-CHAP-Error", "E=647 R=0", 9);
1255 return RLM_MODULE_USERLOCK;
1259 /* now create MPPE attributes */
1260 if (inst->use_mppe) {
1261 uint8_t mppe_sendkey[34];
1262 uint8_t mppe_recvkey[34];
1265 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1266 memset(mppe_sendkey, 0, 32);
1268 memcpy(mppe_sendkey, lm_password->vp_strvalue, 8);
1272 * According to RFC 2548 we
1273 * should send NT hash. But in
1274 * practice it doesn't work.
1275 * Instead, we should send nthashhash
1277 * This is an error on RFC 2548.
1280 * do_mschap cares to zero nthashhash if NT hash
1283 memcpy(mppe_sendkey + 8,
1285 mppe_add_reply(&request->reply->vps,
1286 "MS-CHAP-MPPE-Keys",
1288 } else if (chap == 2) {
1289 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1290 mppe_chap2_gen_keys128(nthashhash,
1291 response->vp_strvalue + 26,
1292 mppe_sendkey, mppe_recvkey);
1294 mppe_add_reply(&request->reply->vps,
1297 mppe_add_reply(&request->reply->vps,
1302 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1303 (inst->require_encryption)? "0x00000002":"0x00000001",
1305 rad_assert(reply_attr != NULL);
1306 pairadd(&request->reply->vps, reply_attr);
1307 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1308 (inst->require_strong)? "0x00000004":"0x00000006",
1310 rad_assert(reply_attr != NULL);
1311 pairadd(&request->reply->vps, reply_attr);
1313 } /* else we weren't asked to use MPPE */
1315 return RLM_MODULE_OK;
1319 module_t rlm_mschap = {
1322 RLM_TYPE_THREAD_SAFE, /* type */
1323 mschap_instantiate, /* instantiation */
1324 mschap_detach, /* detach */
1326 mschap_authenticate, /* authenticate */
1327 mschap_authorize, /* authorize */
1328 NULL, /* pre-accounting */
1329 NULL, /* accounting */
1330 NULL, /* checksimul */
1331 NULL, /* pre-proxy */
1332 NULL, /* post-proxy */
1333 NULL /* post-auth */