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> */
46 #include "libradius.h"
59 #include "rad_assert.h"
63 static const char rcsid[] = "$Id$";
66 /* Allowable account control bits */
67 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
68 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
69 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
70 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
71 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
72 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
73 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
74 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
75 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
76 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
77 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
79 static int pdb_decode_acct_ctrl(const char *p)
85 * Check if the account type bits have been encoded after the
86 * NT password (in the form [NDHTUWSLXI]).
89 if (*p != '[') return 0;
91 for (p++; *p && !finished; p++) {
93 case 'N': /* 'N'o password. */
94 acct_ctrl |= ACB_PWNOTREQ;
97 case 'D': /* 'D'isabled. */
98 acct_ctrl |= ACB_DISABLED ;
101 case 'H': /* 'H'omedir required. */
102 acct_ctrl |= ACB_HOMDIRREQ;
105 case 'T': /* 'T'emp account. */
106 acct_ctrl |= ACB_TEMPDUP;
109 case 'U': /* 'U'ser account (normal). */
110 acct_ctrl |= ACB_NORMAL;
113 case 'M': /* 'M'NS logon user account. What is this? */
114 acct_ctrl |= ACB_MNS;
117 case 'W': /* 'W'orkstation account. */
118 acct_ctrl |= ACB_WSTRUST;
121 case 'S': /* 'S'erver account. */
122 acct_ctrl |= ACB_SVRTRUST;
125 case 'L': /* 'L'ocked account. */
126 acct_ctrl |= ACB_AUTOLOCK;
129 case 'X': /* No 'X'piry on password */
130 acct_ctrl |= ACB_PWNOEXP;
133 case 'I': /* 'I'nterdomain trust account. */
134 acct_ctrl |= ACB_DOMTRUST;
137 case ' ': /* ignore spaces */
155 * ntpwdhash converts Unicode password to 16-byte NT hash
158 static void ntpwdhash (char *szHash, const char *szPassword)
160 char szUnicodePass[513];
165 * NT passwords are unicode. Convert plain text password
166 * to unicode by inserting a zero every other byte
168 nPasswordLen = strlen(szPassword);
169 for (i = 0; i < nPasswordLen; i++) {
170 szUnicodePass[i << 1] = szPassword[i];
171 szUnicodePass[(i << 1) + 1] = 0;
174 /* Encrypt Unicode password to a 16-byte MD4 hash */
175 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
180 * challenge_hash() is used by mschap2() and auth_response()
181 * implements RFC2759 ChallengeHash()
182 * generates 64 bit challenge
184 static void challenge_hash( const char *peer_challenge,
185 const char *auth_challenge,
186 const char *user_name, char *challenge )
192 SHA1Update(&Context, peer_challenge, 16);
193 SHA1Update(&Context, auth_challenge, 16);
194 SHA1Update(&Context, user_name, strlen(user_name));
195 SHA1Final(hash, &Context);
196 memcpy(challenge, hash, 8);
200 * auth_response() generates MS-CHAP v2 SUCCESS response
201 * according to RFC 2759 GenerateAuthenticatorResponse()
202 * returns 42-octet response string
204 static void auth_response(const char *username,
205 const char *nt_hash_hash,
207 char *peer_challenge, char *auth_challenge,
211 const char magic1[39] =
212 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
213 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
214 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
215 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
217 const char magic2[41] =
218 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
219 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
220 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
221 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
228 SHA1Update(&Context, nt_hash_hash, 16);
229 SHA1Update(&Context, ntresponse, 24);
230 SHA1Update(&Context, magic1, 39);
231 SHA1Final(digest, &Context);
232 challenge_hash(peer_challenge, auth_challenge, username, challenge);
234 SHA1Update(&Context, digest, 20);
235 SHA1Update(&Context, challenge, 8);
236 SHA1Update(&Context, magic2, 41);
237 SHA1Final(digest, &Context);
240 * Encode the value of 'Digest' as "S=" followed by
241 * 40 ASCII hexadecimal digits and return it in
242 * AuthenticatorResponse.
244 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
248 lrad_bin2hex(digest, response + 2, 20);
252 typedef struct rlm_mschap_t {
254 int require_encryption;
256 int with_ntdomain_hack; /* this should be in another module */
259 char *auth_type; /* I don't think this is needed... */
265 * Does dynamic translation of strings.
267 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
270 static int mschap_xlat(void *instance, REQUEST *request,
271 char *fmt, char *out, size_t outlen,
272 RADIUS_ESCAPE_STRING func)
275 uint8_t *data = NULL;
277 VALUE_PAIR *user_name;
278 VALUE_PAIR *chap_challenge, *response;
279 rlm_mschap_t *inst = instance;
281 chap_challenge = response = NULL;
283 func = func; /* -Wunused */
286 * Challenge means MS-CHAPv1 challenge, or
287 * hash of MS-CHAPv2 challenge, and peer challenge.
289 if (strcasecmp(fmt, "Challenge") == 0) {
290 chap_challenge = pairfind(request->packet->vps,
291 PW_MSCHAP_CHALLENGE);
292 if (!chap_challenge) {
293 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
298 * MS-CHAP-Challenges are 8 octets,
301 if (chap_challenge->length == 8) {
302 DEBUG2(" mschap1: %02x", chap_challenge->strvalue[0]);
303 data = chap_challenge->strvalue;
307 * MS-CHAP-Challenges are 16 octets,
310 } else if (chap_challenge->length == 16) {
311 char *username_string;
313 DEBUG2(" mschap2: %02x", chap_challenge->strvalue[0]);
314 response = pairfind(request->packet->vps,
315 PW_MSCHAP2_RESPONSE);
317 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
322 * Responses are 50 octets.
324 if (response->length < 50) {
325 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
329 user_name = pairfind(request->packet->vps,
332 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
337 * with_ntdomain_hack moved here, too.
339 if ((username_string = strchr(user_name->strvalue, '\\')) != NULL) {
340 if (inst->with_ntdomain_hack) {
343 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
344 username_string = user_name->strvalue;
347 username_string = user_name->strvalue;
351 * Get the MS-CHAPv1 challenge
352 * from the MS-CHAPv2 peer challenge,
353 * our challenge, and the user name.
355 challenge_hash(response->strvalue + 2,
356 chap_challenge->strvalue,
357 username_string, buffer);
361 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
366 * Get the MS-CHAPv1 response, or the MS-CHAPv2
369 } else if (strcasecmp(fmt, "NT-Response") == 0) {
370 response = pairfind(request->packet->vps,
372 if (!response) response = pairfind(request->packet->vps,
373 PW_MSCHAP2_RESPONSE);
375 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
380 * For MS-CHAPv1, the NT-Response exists only
381 * if the second octet says so.
383 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
384 ((response->strvalue[1] & 0x01) == 0)) {
385 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
390 * MS-CHAP-Response and MS-CHAP2-Response have
391 * the NT-Response at the same offset, and are
394 data = response->strvalue + 26;
398 * LM-Response is deprecated, and exists only
399 * in MS-CHAPv1, and not often there.
401 } else if (strcasecmp(fmt, "LM-Response") == 0) {
402 response = pairfind(request->packet->vps,
405 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
410 * For MS-CHAPv1, the NT-Response exists only
411 * if the second octet says so.
413 if ((response->strvalue[1] & 0x01) != 0) {
414 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
417 data = response->strvalue + 2;
421 * Pull the NT-Domain out of the User-Name, if it exists.
423 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
426 user_name = pairfind(request->packet->vps, PW_USER_NAME);
428 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
432 p = strchr(user_name->strvalue, '\\');
434 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
439 * Hack. This is simpler than the alternatives.
442 strNcpy(out, user_name->strvalue, outlen);
448 * Pull the User-Name out of the User-Name...
450 } else if (strcasecmp(fmt, "User-Name") == 0) {
453 user_name = pairfind(request->packet->vps, PW_USER_NAME);
455 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
459 p = strchr(user_name->strvalue, '\\');
461 p++; /* skip the backslash */
463 p = user_name->strvalue; /* use the whole User-Name */
466 strNcpy(out, p, outlen);
470 * Return the NT-Hash of the passed string
472 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
475 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
476 if ((p == '\0') || (outlen <= 32))
478 DEBUG("rlm_mschap: NT-Hash: %s",p);
481 lrad_bin2hex(buffer, out, 16);
483 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
487 * Return the LM-Hash of the passed string
489 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
492 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
493 if ((p == '\0') || (outlen <= 32))
496 DEBUG("rlm_mschap: LM-Hash: %s",p);
497 smbdes_lmpwdhash(p,buffer);
498 lrad_bin2hex(buffer, out, 16);
500 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
503 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
508 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
511 * Didn't set anything: this is bad.
514 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
519 * Check the output length.
521 if (outlen < ((data_len * 2) + 1)) {
522 data_len = (outlen - 1) / 2;
528 for (i = 0; i < data_len; i++) {
529 sprintf(out + (2 * i), "%02x", data[i]);
531 out[data_len * 2] = '\0';
537 static CONF_PARSER module_config[] = {
539 * Cache the password by default.
541 { "use_mppe", PW_TYPE_BOOLEAN,
542 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
543 { "require_encryption", PW_TYPE_BOOLEAN,
544 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
545 { "require_strong", PW_TYPE_BOOLEAN,
546 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
547 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
548 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
549 { "passwd", PW_TYPE_STRING_PTR,
550 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
551 { "authtype", PW_TYPE_STRING_PTR,
552 offsetof(rlm_mschap_t, auth_type), NULL, NULL },
553 { "ntlm_auth", PW_TYPE_STRING_PTR,
554 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
556 { NULL, -1, 0, NULL, NULL } /* end the list */
560 * deinstantiate module, free all memory allocated during
561 * mschap_instantiate()
563 static int mschap_detach(void *instance){
564 #define inst ((rlm_mschap_t *)instance)
565 if (inst->passwd_file) free(inst->passwd_file);
566 if (inst->auth_type) free(inst->auth_type);
567 if (inst->ntlm_auth) free(inst->ntlm_auth);
568 if (inst->xlat_name) {
569 xlat_unregister(inst->xlat_name, mschap_xlat);
570 free(inst->xlat_name);
578 * Create instance for our module. Allocate space for
579 * instance structure and read configuration parameters
581 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
583 const char *xlat_name;
586 inst = *instance = rad_malloc(sizeof(*inst));
590 memset(inst, 0, sizeof(*inst));
592 if (cf_section_parse(conf, inst, module_config) < 0) {
598 * This module used to support SMB Password files, but it
599 * made it too complicated. If the user tries to
600 * configure an SMB Password file, then die, with an
603 if (inst->passwd_file) {
604 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
610 * Create the dynamic translation.
612 if (cf_section_name1(conf))
613 xlat_register(cf_section_name1(conf),mschap_xlat, inst);
615 if ((xlat_name = cf_section_name2(conf)) != NULL)
616 xlat_register(xlat_name, mschap_xlat, inst);
617 if (xlat_name == NULL)
618 xlat_name = cf_section_name1(conf);
620 inst->xlat_name = strdup(xlat_name);
626 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
627 * attribute to reply packet
629 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
630 const char* name, const char* value, int len)
632 VALUE_PAIR *reply_attr;
633 reply_attr = pairmake(name, "", T_OP_EQ);
635 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
639 reply_attr->strvalue[0] = ident;
640 memcpy(reply_attr->strvalue + 1, value, len);
641 reply_attr->length = len + 1;
642 pairadd(vp, reply_attr);
646 * Add MPPE attributes to the reply.
648 static void mppe_add_reply(VALUE_PAIR **vp,
649 const char* name, const char* value, int len)
651 VALUE_PAIR *reply_attr;
652 reply_attr = pairmake(name, "", T_OP_EQ);
654 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
658 memcpy(reply_attr->strvalue, value, len);
659 reply_attr->length = len;
660 pairadd(vp, reply_attr);
665 * Do the MS-CHAP stuff.
667 * This function is here so that all of the MS-CHAP related
668 * authentication is in one place, and we can perhaps later replace
669 * it with code to call winbindd, or something similar.
671 static int do_mschap(rlm_mschap_t *inst,
672 REQUEST *request, VALUE_PAIR *password,
673 uint8_t *challenge, uint8_t *response,
676 int do_ntlm_auth = 0;
677 uint8_t calculated[24];
678 VALUE_PAIR *vp = NULL;
681 * If we have ntlm_auth configured, use it unless told
684 if (inst->ntlm_auth) do_ntlm_auth = 1;
687 * If we have an ntlm_auth configuration, then we may
690 vp = pairfind(request->config_items,
691 PW_MS_CHAP_USE_NTLM_AUTH);
692 if (vp) do_ntlm_auth = vp->lvalue;
695 * No ntlm_auth configured, attribute to tell us to
696 * use it exists, and we're told to use it. We don't
699 if (!inst->ntlm_auth && do_ntlm_auth) {
700 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
705 * Do normal authentication.
709 * No password: can't do authentication.
712 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
716 smbdes_mschap(password->strvalue, challenge, calculated);
717 if (memcmp(response, calculated, 24) != 0) {
722 * If the password exists, and is an NT-Password,
723 * then calculate the hash of the NT hash. Doing this
724 * here minimizes work for later.
726 if (password && (password->attribute == PW_NT_PASSWORD)) {
727 md4_calc(nthashhash, password->strvalue, 16);
729 memset(nthashhash, 0, 16);
731 } else { /* run ntlm_auth */
735 memset(nthashhash, 0, 16);
738 * Run the program, and expect that we get 16
740 result = radius_exec_program(inst->ntlm_auth, request,
742 buffer, sizeof(buffer),
745 DEBUG2(" rlm_mschap: External script failed.");
750 * Parse the answer as an nthashhash.
752 * ntlm_auth currently returns:
753 * NT_KEY: 000102030405060708090a0b0c0d0e0f
755 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
756 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
761 * Check the length. It should be at least 32,
762 * with an LF at the end.
764 if (strlen(buffer + 8) < 32) {
765 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
770 * Update the NT hash hash, from the NT key.
772 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
773 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
783 * Data for the hashes.
785 static const uint8_t SHSpad1[40] =
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,
789 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
791 static const uint8_t SHSpad2[40] =
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,
795 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
797 static const uint8_t magic1[27] =
798 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
799 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
800 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
802 static const uint8_t magic2[84] =
803 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
804 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
805 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
806 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
807 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
808 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
809 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
810 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
811 0x6b, 0x65, 0x79, 0x2e };
813 static const uint8_t magic3[84] =
814 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
815 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
816 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
817 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
818 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
819 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
820 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
821 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
822 0x6b, 0x65, 0x79, 0x2e };
825 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
832 SHA1Update(&Context,nt_hashhash,16);
833 SHA1Update(&Context,nt_response,24);
834 SHA1Update(&Context,magic1,27);
835 SHA1Final(digest,&Context);
837 memcpy(masterkey,digest,16);
841 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
842 int keylen,int issend)
857 SHA1Update(&Context,masterkey,16);
858 SHA1Update(&Context,SHSpad1,40);
859 SHA1Update(&Context,s,84);
860 SHA1Update(&Context,SHSpad2,40);
861 SHA1Final(digest,&Context);
863 memcpy(sesskey,digest,keylen);
867 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
868 uint8_t *sendkey,uint8_t *recvkey)
870 uint8_t masterkey[16];
872 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
874 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
875 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
879 * Generate MPPE keys.
881 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
882 uint8_t *sendkey,uint8_t *recvkey)
887 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
890 * dictionary.microsoft defines these attributes as
891 * 'encrypt=2'. The functions in src/lib/radius.c will
892 * take care of encrypting/decrypting them as appropriate,
893 * so that we don't have to.
895 memcpy (sendkey, enckey1, 16);
896 memcpy (recvkey, enckey2, 16);
901 * mschap_authorize() - authorize user if we can authenticate
902 * it later. Add Auth-Type attribute if present in module
903 * configuration (usually Auth-Type must be "MS-CHAP")
905 static int mschap_authorize(void * instance, REQUEST *request)
907 #define inst ((rlm_mschap_t *)instance)
908 VALUE_PAIR *challenge = NULL, *response = NULL;
910 const char *authtype_name = "MS-CHAP";
912 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
914 return RLM_MODULE_NOOP;
917 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
919 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
922 * Nothing we recognize. Don't do anything.
925 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
926 return RLM_MODULE_NOOP;
930 * Choose MS-CHAP, or whatever else they told us to use.
932 if (inst->auth_type) {
933 authtype_name = inst->auth_type;
936 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", authtype_name);
939 * Set Auth-Type to MS-CHAP. The authentication code
940 * will take care of turning clear-text passwords into
943 pairdelete(&request->config_items, PW_AUTHTYPE);
944 vp = pairmake("Auth-Type", authtype_name, T_OP_EQ);
945 rad_assert(vp != NULL);
946 pairadd(&request->config_items, vp);
948 return RLM_MODULE_OK;
953 * mschap_authenticate() - authenticate user based on given
954 * attributes and configuration.
955 * We will try to find out password in configuration
956 * or in configured passwd file.
957 * If one is found we will check paraneters given by NAS.
959 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
961 * PAP: PW_PASSWORD or
962 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
963 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
964 * In case of password mismatch or locked account we MAY return
965 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
966 * If MS-CHAP2 succeeds we MUST return
969 static int mschap_authenticate(void * instance, REQUEST *request)
971 #define inst ((rlm_mschap_t *)instance)
972 VALUE_PAIR *challenge = NULL, *response = NULL;
973 VALUE_PAIR *password = NULL;
974 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
975 VALUE_PAIR *username;
976 VALUE_PAIR *reply_attr;
977 uint8_t nthashhash[16];
978 uint8_t msch2resp[42];
979 char *username_string;
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);
988 password = pairfind(request->config_items,
989 PW_SMB_ACCOUNT_CTRL_TEXT);
991 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
992 pairadd(&request->config_items, smb_ctrl);
993 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->strvalue);
998 * We're configured to do MS-CHAP authentication.
999 * and account control information exists. Enforce it.
1003 * Password is not required.
1005 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1006 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1007 return RLM_MODULE_OK;
1012 * Decide how to get the passwords.
1014 password = pairfind(request->config_items, PW_PASSWORD);
1017 * We need an LM-Password.
1019 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1024 if ((lm_password->length == 16) ||
1025 ((lm_password->length == 32) &&
1026 (lrad_hex2bin(lm_password->strvalue,
1027 lm_password->strvalue, 16) == 16))) {
1028 DEBUG2(" rlm_mschap: Found LM-Password");
1029 lm_password->length = 16;
1032 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1036 } else if (!password) {
1037 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create LM-Password.");
1039 } else { /* there is a configured User-Password */
1040 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1042 radlog(L_ERR, "No memory");
1044 smbdes_lmpwdhash(password->strvalue,
1045 lm_password->strvalue);
1046 lm_password->length = 16;
1047 pairadd(&request->config_items, lm_password);
1052 * We need an NT-Password.
1054 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1056 if ((nt_password->length == 16) ||
1057 ((nt_password->length == 32) &&
1058 (lrad_hex2bin(nt_password->strvalue,
1059 nt_password->strvalue, 16) == 16))) {
1060 DEBUG2(" rlm_mschap: Found NT-Password");
1061 nt_password->length = 16;
1064 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1067 } else if (!password) {
1068 DEBUG2(" rlm_mschap: No User-Password configured. Cannot create NT-Password.");
1070 } else { /* there is a configured User-Password */
1071 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1073 radlog(L_ERR, "No memory");
1074 return RLM_MODULE_FAIL;
1076 ntpwdhash(nt_password->strvalue, password->strvalue);
1077 nt_password->length = 16;
1078 pairadd(&request->config_items, nt_password);
1082 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1084 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1085 return RLM_MODULE_REJECT;
1089 * We also require an MS-CHAP-Response.
1091 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1094 * MS-CHAP-Response, means MS-CHAPv1
1100 * MS-CHAPv1 challenges are 8 octets.
1102 if (challenge->length < 8) {
1103 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1104 return RLM_MODULE_INVALID;
1108 * Responses are 50 octets.
1110 if (response->length < 50) {
1111 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1112 return RLM_MODULE_INVALID;
1116 * We are doing MS-CHAP. Calculate the MS-CHAP
1119 if (response->strvalue[1] & 0x01) {
1120 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1121 password = nt_password;
1124 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1125 password = lm_password;
1130 * Do the MS-CHAP authentication.
1132 if (do_mschap(inst, request, password, challenge->strvalue,
1133 response->strvalue + offset, nthashhash) < 0) {
1134 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1135 add_reply(&request->reply->vps, *response->strvalue,
1136 "MS-CHAP-Error", "E=691 R=1", 9);
1137 return RLM_MODULE_REJECT;
1142 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1143 uint8_t mschapv1_challenge[16];
1146 * MS-CHAPv2 challenges are 16 octets.
1148 if (challenge->length < 16) {
1149 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1150 return RLM_MODULE_INVALID;
1154 * Responses are 50 octets.
1156 if (response->length < 50) {
1157 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1158 return RLM_MODULE_INVALID;
1162 * We also require a User-Name
1164 username = pairfind(request->packet->vps, PW_USER_NAME);
1166 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1167 return RLM_MODULE_INVALID;
1172 * with_ntdomain_hack moved here
1174 if ((username_string = strchr(username->strvalue, '\\')) != NULL) {
1175 if (inst->with_ntdomain_hack) {
1178 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1179 username_string = username->strvalue;
1182 username_string = username->strvalue;
1186 * The old "mschapv2" function has been moved to
1189 * MS-CHAPv2 takes some additional data to create an
1190 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1192 challenge_hash(response->strvalue + 2, /* peer challenge */
1193 challenge->strvalue, /* our challenge */
1194 username_string, /* user name */
1195 mschapv1_challenge); /* resulting challenge */
1197 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1200 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1201 response->strvalue + 26, nthashhash) < 0) {
1202 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1203 add_reply(&request->reply->vps, *response->strvalue,
1204 "MS-CHAP-Error", "E=691 R=1", 9);
1205 return RLM_MODULE_REJECT;
1209 * Get the NT-hash-hash, if necessary
1214 auth_response(username_string, /* without the domain */
1215 nthashhash, /* nt-hash-hash */
1216 response->strvalue + 26, /* peer response */
1217 response->strvalue + 2, /* peer challenge */
1218 challenge->strvalue, /* our challenge */
1219 msch2resp); /* calculated MPPE key */
1220 add_reply( &request->reply->vps, *response->strvalue,
1221 "MS-CHAP2-Success", msch2resp, 42);
1224 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1225 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1226 return RLM_MODULE_INVALID;
1230 * We have a CHAP response, but the account may be
1231 * disabled. Reject the user with the same error code
1232 * we use when their password is invalid.
1236 * Account is disabled.
1238 * They're found, but they don't exist, so we
1239 * return 'not found'.
1241 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1242 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1243 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1244 add_reply( &request->reply->vps, *response->strvalue,
1245 "MS-CHAP-Error", "E=691 R=1", 9);
1246 return RLM_MODULE_NOTFOUND;
1250 * User is locked out.
1252 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1253 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1254 add_reply( &request->reply->vps, *response->strvalue,
1255 "MS-CHAP-Error", "E=647 R=0", 9);
1256 return RLM_MODULE_USERLOCK;
1260 /* now create MPPE attributes */
1261 if (inst->use_mppe) {
1262 uint8_t mppe_sendkey[34];
1263 uint8_t mppe_recvkey[34];
1266 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1267 memset(mppe_sendkey, 0, 32);
1269 memcpy(mppe_sendkey, lm_password->strvalue, 8);
1273 * According to RFC 2548 we
1274 * should send NT hash. But in
1275 * practice it doesn't work.
1276 * Instead, we should send nthashhash
1278 * This is an error on RFC 2548.
1281 * do_mschap cares to zero nthashhash if NT hash
1284 memcpy(mppe_sendkey + 8,
1286 mppe_add_reply(&request->reply->vps,
1287 "MS-CHAP-MPPE-Keys",
1289 } else if (chap == 2) {
1290 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1291 mppe_chap2_gen_keys128(nthashhash,
1292 response->strvalue + 26,
1293 mppe_sendkey, mppe_recvkey);
1295 mppe_add_reply(&request->reply->vps,
1298 mppe_add_reply(&request->reply->vps,
1303 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1304 (inst->require_encryption)? "0x00000002":"0x00000001",
1306 rad_assert(reply_attr != NULL);
1307 pairadd(&request->reply->vps, reply_attr);
1308 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1309 (inst->require_strong)? "0x00000004":"0x00000006",
1311 rad_assert(reply_attr != NULL);
1312 pairadd(&request->reply->vps, reply_attr);
1314 } /* else we weren't asked to use MPPE */
1316 return RLM_MODULE_OK;
1320 module_t rlm_mschap = {
1322 RLM_TYPE_THREAD_SAFE, /* type */
1323 NULL, /* initialize */
1324 mschap_instantiate, /* instantiation */
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 */
1335 mschap_detach, /* detach */