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
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> */
45 #include <freeradius-devel/ident.h>
48 #include <freeradius-devel/autoconf.h>
55 #include <freeradius-devel/radiusd.h>
56 #include <freeradius-devel/modules.h>
58 #include <freeradius-devel/md5.h>
59 #include <freeradius-devel/rad_assert.h>
63 /* Allowable account control bits */
64 #define ACB_DISABLED 0x0001 /* 1 = User account disabled */
65 #define ACB_HOMDIRREQ 0x0002 /* 1 = Home directory required */
66 #define ACB_PWNOTREQ 0x0004 /* 1 = User password not required */
67 #define ACB_TEMPDUP 0x0008 /* 1 = Temporary duplicate account */
68 #define ACB_NORMAL 0x0010 /* 1 = Normal user account */
69 #define ACB_MNS 0x0020 /* 1 = MNS logon user account */
70 #define ACB_DOMTRUST 0x0040 /* 1 = Interdomain trust account */
71 #define ACB_WSTRUST 0x0080 /* 1 = Workstation trust account */
72 #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */
73 #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */
74 #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */
76 static int pdb_decode_acct_ctrl(const char *p)
82 * Check if the account type bits have been encoded after the
83 * NT password (in the form [NDHTUWSLXI]).
86 if (*p != '[') return 0;
88 for (p++; *p && !finished; p++) {
90 case 'N': /* 'N'o password. */
91 acct_ctrl |= ACB_PWNOTREQ;
94 case 'D': /* 'D'isabled. */
95 acct_ctrl |= ACB_DISABLED ;
98 case 'H': /* 'H'omedir required. */
99 acct_ctrl |= ACB_HOMDIRREQ;
102 case 'T': /* 'T'emp account. */
103 acct_ctrl |= ACB_TEMPDUP;
106 case 'U': /* 'U'ser account (normal). */
107 acct_ctrl |= ACB_NORMAL;
110 case 'M': /* 'M'NS logon user account. What is this? */
111 acct_ctrl |= ACB_MNS;
114 case 'W': /* 'W'orkstation account. */
115 acct_ctrl |= ACB_WSTRUST;
118 case 'S': /* 'S'erver account. */
119 acct_ctrl |= ACB_SVRTRUST;
122 case 'L': /* 'L'ocked account. */
123 acct_ctrl |= ACB_AUTOLOCK;
126 case 'X': /* No 'X'piry on password */
127 acct_ctrl |= ACB_PWNOEXP;
130 case 'I': /* 'I'nterdomain trust account. */
131 acct_ctrl |= ACB_DOMTRUST;
134 case ' ': /* ignore spaces */
152 * ntpwdhash converts Unicode password to 16-byte NT hash
155 static void ntpwdhash (unsigned char *szHash, const char *szPassword)
157 char szUnicodePass[513];
162 * NT passwords are unicode. Convert plain text password
163 * to unicode by inserting a zero every other byte
165 nPasswordLen = strlen(szPassword);
166 for (i = 0; i < nPasswordLen; i++) {
167 szUnicodePass[i << 1] = szPassword[i];
168 szUnicodePass[(i << 1) + 1] = 0;
171 /* Encrypt Unicode password to a 16-byte MD4 hash */
172 md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
177 * challenge_hash() is used by mschap2() and auth_response()
178 * implements RFC2759 ChallengeHash()
179 * generates 64 bit challenge
181 static void challenge_hash( const uint8_t *peer_challenge,
182 const uint8_t *auth_challenge,
183 const char *user_name, uint8_t *challenge )
189 SHA1Update(&Context, peer_challenge, 16);
190 SHA1Update(&Context, auth_challenge, 16);
191 SHA1Update(&Context, user_name, strlen(user_name));
192 SHA1Final(hash, &Context);
193 memcpy(challenge, hash, 8);
197 * auth_response() generates MS-CHAP v2 SUCCESS response
198 * according to RFC 2759 GenerateAuthenticatorResponse()
199 * returns 42-octet response string
201 static void auth_response(const char *username,
202 const uint8_t *nt_hash_hash,
204 char *peer_challenge, char *auth_challenge,
208 const uint8_t magic1[39] =
209 {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
210 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
211 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
212 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
214 const uint8_t magic2[41] =
215 {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
216 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
217 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
218 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
225 SHA1Update(&Context, nt_hash_hash, 16);
226 SHA1Update(&Context, ntresponse, 24);
227 SHA1Update(&Context, magic1, 39);
228 SHA1Final(digest, &Context);
229 challenge_hash(peer_challenge, auth_challenge, username, challenge);
231 SHA1Update(&Context, digest, 20);
232 SHA1Update(&Context, challenge, 8);
233 SHA1Update(&Context, magic2, 41);
234 SHA1Final(digest, &Context);
237 * Encode the value of 'Digest' as "S=" followed by
238 * 40 ASCII hexadecimal digits and return it in
239 * AuthenticatorResponse.
241 * "S=0123456789ABCDEF0123456789ABCDEF01234567"
245 lrad_bin2hex(digest, response + 2, 20);
249 typedef struct rlm_mschap_t {
251 int require_encryption;
253 int with_ntdomain_hack; /* this should be in another module */
262 * Does dynamic translation of strings.
264 * Pulls NT-Response, LM-Response, or Challenge from MSCHAP
267 static int mschap_xlat(void *instance, REQUEST *request,
268 char *fmt, char *out, size_t outlen,
269 RADIUS_ESCAPE_STRING func)
272 uint8_t *data = NULL;
274 VALUE_PAIR *user_name;
275 VALUE_PAIR *chap_challenge, *response;
276 rlm_mschap_t *inst = instance;
278 chap_challenge = response = NULL;
280 func = func; /* -Wunused */
283 * Challenge means MS-CHAPv1 challenge, or
284 * hash of MS-CHAPv2 challenge, and peer challenge.
286 if (strcasecmp(fmt, "Challenge") == 0) {
287 chap_challenge = pairfind(request->packet->vps,
288 PW_MSCHAP_CHALLENGE);
289 if (!chap_challenge) {
290 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request.");
295 * MS-CHAP-Challenges are 8 octets,
298 if (chap_challenge->length == 8) {
299 DEBUG2(" mschap1: %02x",
300 chap_challenge->vp_octets[0]);
301 data = chap_challenge->vp_octets;
305 * MS-CHAP-Challenges are 16 octets,
308 } else if (chap_challenge->length == 16) {
309 char *username_string;
311 DEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
312 response = pairfind(request->packet->vps,
313 PW_MSCHAP2_RESPONSE);
315 DEBUG2(" rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
320 * Responses are 50 octets.
322 if (response->length < 50) {
323 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
327 user_name = pairfind(request->packet->vps,
330 DEBUG2(" rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
335 * with_ntdomain_hack moved here, too.
337 if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
338 if (inst->with_ntdomain_hack) {
341 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
342 username_string = user_name->vp_strvalue;
345 username_string = user_name->vp_strvalue;
349 * Get the MS-CHAPv1 challenge
350 * from the MS-CHAPv2 peer challenge,
351 * our challenge, and the user name.
353 challenge_hash(response->vp_octets + 2,
354 chap_challenge->vp_octets,
355 username_string, buffer);
359 DEBUG2(" rlm_mschap: Invalid MS-CHAP challenge length");
364 * Get the MS-CHAPv1 response, or the MS-CHAPv2
367 } else if (strcasecmp(fmt, "NT-Response") == 0) {
368 response = pairfind(request->packet->vps,
370 if (!response) response = pairfind(request->packet->vps,
371 PW_MSCHAP2_RESPONSE);
373 DEBUG2(" rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
378 * For MS-CHAPv1, the NT-Response exists only
379 * if the second octet says so.
381 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
382 ((response->vp_octets[1] & 0x01) == 0)) {
383 DEBUG2(" rlm_mschap: No NT-Response in MS-CHAP-Response");
388 * MS-CHAP-Response and MS-CHAP2-Response have
389 * the NT-Response at the same offset, and are
392 data = response->vp_octets + 26;
396 * LM-Response is deprecated, and exists only
397 * in MS-CHAPv1, and not often there.
399 } else if (strcasecmp(fmt, "LM-Response") == 0) {
400 response = pairfind(request->packet->vps,
403 DEBUG2(" rlm_mschap: No MS-CHAP-Response was found in the request.");
408 * For MS-CHAPv1, the NT-Response exists only
409 * if the second octet says so.
411 if ((response->vp_octets[1] & 0x01) != 0) {
412 DEBUG2(" rlm_mschap: No LM-Response in MS-CHAP-Response");
415 data = response->vp_octets + 2;
419 * Pull the NT-Domain out of the User-Name, if it exists.
421 } else if (strcasecmp(fmt, "NT-Domain") == 0) {
424 user_name = pairfind(request->packet->vps, PW_USER_NAME);
426 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
431 * First check to see if this is a host/ style User-Name
432 * (a la Kerberos host principal)
434 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
436 * If we're getting a User-Name formatted in this way,
437 * it's likely due to PEAP. The Windows Domain will be
438 * the first domain component following the hostname,
439 * or the machine name itself if only a hostname is supplied
441 p = strchr(user_name->vp_strvalue, '.');
443 DEBUG2(" rlm_mschap: setting NT-Domain to same as machine name");
444 strlcpy(out, user_name->vp_strvalue + 5, outlen);
446 p++; /* skip the period */
449 * use the same hack as below
450 * only if another period was found
453 strlcpy(out, p, outlen);
457 p = strchr(user_name->vp_strvalue, '\\');
459 DEBUG2(" rlm_mschap: No NT-Domain was found in the User-Name.");
464 * Hack. This is simpler than the alternatives.
467 strlcpy(out, user_name->vp_strvalue, outlen);
474 * Pull the User-Name out of the User-Name...
476 } else if (strcasecmp(fmt, "User-Name") == 0) {
479 user_name = pairfind(request->packet->vps, PW_USER_NAME);
481 DEBUG2(" rlm_mschap: No User-Name was found in the request.");
486 * First check to see if this is a host/ style User-Name
487 * (a la Kerberos host principal)
489 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
491 * If we're getting a User-Name formatted in this way,
492 * it's likely due to PEAP. When authenticating this against
493 * a Domain, Windows will expect the User-Name to be in the
494 * format of hostname$, the SAM version of the name, so we
495 * have to convert it to that here. We do so by stripping
496 * off the first 5 characters (host/), and copying everything
497 * from that point to the first period into a string and appending
500 p = strchr(user_name->vp_strvalue, '.');
502 * use the same hack as above
503 * only if a period was found
506 snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
509 p = strchr(user_name->vp_strvalue, '\\');
511 p++; /* skip the backslash */
513 p = user_name->vp_strvalue; /* use the whole User-Name */
515 strlcpy(out, p, outlen);
521 * Return the NT-Hash of the passed string
523 } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
526 p = fmt + 8; /* 7 is the length of 'NT-Hash' */
527 if ((p == '\0') || (outlen <= 32))
529 DEBUG("rlm_mschap: NT-Hash: %s",p);
532 lrad_bin2hex(buffer, out, 16);
534 DEBUG("rlm_mschap: NT-Hash: Result: %s",out);
538 * Return the LM-Hash of the passed string
540 } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
543 p = fmt + 8; /* 7 is the length of 'LM-Hash' */
544 if ((p == '\0') || (outlen <= 32))
547 DEBUG("rlm_mschap: LM-Hash: %s",p);
548 smbdes_lmpwdhash(p,buffer);
549 lrad_bin2hex(buffer, out, 16);
551 DEBUG("rlm_mschap: LM-Hash: Result: %s",out);
554 DEBUG2(" rlm_mschap: Unknown expansion string \"%s\"",
559 if (outlen == 0) return 0; /* nowhere to go, don't do anything */
562 * Didn't set anything: this is bad.
565 DEBUG2(" rlm_mschap: Failed to do anything intelligent");
570 * Check the output length.
572 if (outlen < ((data_len * 2) + 1)) {
573 data_len = (outlen - 1) / 2;
579 for (i = 0; i < data_len; i++) {
580 sprintf(out + (2 * i), "%02x", data[i]);
582 out[data_len * 2] = '\0';
588 static const CONF_PARSER module_config[] = {
590 * Cache the password by default.
592 { "use_mppe", PW_TYPE_BOOLEAN,
593 offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
594 { "require_encryption", PW_TYPE_BOOLEAN,
595 offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
596 { "require_strong", PW_TYPE_BOOLEAN,
597 offsetof(rlm_mschap_t,require_strong), NULL, "no" },
598 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
599 offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
600 { "passwd", PW_TYPE_STRING_PTR,
601 offsetof(rlm_mschap_t, passwd_file), NULL, NULL },
602 { "ntlm_auth", PW_TYPE_STRING_PTR,
603 offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL },
605 { NULL, -1, 0, NULL, NULL } /* end the list */
609 * deinstantiate module, free all memory allocated during
610 * mschap_instantiate()
612 static int mschap_detach(void *instance){
613 #define inst ((rlm_mschap_t *)instance)
614 if (inst->xlat_name) {
615 xlat_unregister(inst->xlat_name, mschap_xlat);
616 free(inst->xlat_name);
624 * Create instance for our module. Allocate space for
625 * instance structure and read configuration parameters
627 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
629 const char *xlat_name;
632 inst = *instance = rad_malloc(sizeof(*inst));
636 memset(inst, 0, sizeof(*inst));
638 if (cf_section_parse(conf, inst, module_config) < 0) {
644 * This module used to support SMB Password files, but it
645 * made it too complicated. If the user tries to
646 * configure an SMB Password file, then die, with an
649 if (inst->passwd_file) {
650 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module. Use rlm_passwd module instead");
656 * Create the dynamic translation.
658 if (cf_section_name1(conf))
659 xlat_register(cf_section_name1(conf),mschap_xlat, inst);
661 if ((xlat_name = cf_section_name2(conf)) != NULL)
662 xlat_register(xlat_name, mschap_xlat, inst);
663 if (xlat_name == NULL)
664 xlat_name = cf_section_name1(conf);
666 inst->xlat_name = strdup(xlat_name);
669 * For backwards compatibility
671 if (!dict_valbyname(PW_AUTH_TYPE, inst->xlat_name)) {
672 inst->auth_type = "MS-CHAP";
679 * add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
680 * attribute to reply packet
682 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
683 const char* name, const char* value, int len)
685 VALUE_PAIR *reply_attr;
686 reply_attr = pairmake(name, "", T_OP_EQ);
688 DEBUG(" rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
692 reply_attr->vp_octets[0] = ident;
693 memcpy(reply_attr->vp_octets + 1, value, len);
694 reply_attr->length = len + 1;
695 pairadd(vp, reply_attr);
699 * Add MPPE attributes to the reply.
701 static void mppe_add_reply(VALUE_PAIR **vp,
702 const char* name, const char* value, int len)
704 VALUE_PAIR *reply_attr;
705 reply_attr = pairmake(name, "", T_OP_EQ);
707 DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
711 memcpy(reply_attr->vp_octets, value, len);
712 reply_attr->length = len;
713 pairadd(vp, reply_attr);
718 * Do the MS-CHAP stuff.
720 * This function is here so that all of the MS-CHAP related
721 * authentication is in one place, and we can perhaps later replace
722 * it with code to call winbindd, or something similar.
724 static int do_mschap(rlm_mschap_t *inst,
725 REQUEST *request, VALUE_PAIR *password,
726 uint8_t *challenge, uint8_t *response,
729 int do_ntlm_auth = 0;
730 uint8_t calculated[24];
731 VALUE_PAIR *vp = NULL;
734 * If we have ntlm_auth configured, use it unless told
737 if (inst->ntlm_auth) do_ntlm_auth = 1;
740 * If we have an ntlm_auth configuration, then we may
743 vp = pairfind(request->config_items,
744 PW_MS_CHAP_USE_NTLM_AUTH);
745 if (vp) do_ntlm_auth = vp->lvalue;
748 * No ntlm_auth configured, attribute to tell us to
749 * use it exists, and we're told to use it. We don't
752 if (!inst->ntlm_auth && do_ntlm_auth) {
753 DEBUG2(" rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
758 * Do normal authentication.
762 * No password: can't do authentication.
765 DEBUG2(" rlm_mschap: FAILED: No NT/LM-Password. Cannot perform authentication.");
769 smbdes_mschap(password->vp_strvalue, challenge, calculated);
770 if (memcmp(response, calculated, 24) != 0) {
775 * If the password exists, and is an NT-Password,
776 * then calculate the hash of the NT hash. Doing this
777 * here minimizes work for later.
779 if (password && (password->attribute == PW_NT_PASSWORD)) {
780 md4_calc(nthashhash, password->vp_strvalue, 16);
782 memset(nthashhash, 0, 16);
784 } else { /* run ntlm_auth */
788 memset(nthashhash, 0, 16);
791 * Run the program, and expect that we get 16
793 result = radius_exec_program(inst->ntlm_auth, request,
795 buffer, sizeof(buffer),
798 DEBUG2(" rlm_mschap: External script failed.");
803 * Parse the answer as an nthashhash.
805 * ntlm_auth currently returns:
806 * NT_KEY: 000102030405060708090a0b0c0d0e0f
808 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
809 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
814 * Check the length. It should be at least 32,
815 * with an LF at the end.
817 if (strlen(buffer + 8) < 32) {
818 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
823 * Update the NT hash hash, from the NT key.
825 if (lrad_hex2bin(buffer + 8, nthashhash, 16) != 16) {
826 DEBUG2(" rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
836 * Data for the hashes.
838 static const uint8_t SHSpad1[40] =
839 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
840 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
841 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
842 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
844 static const uint8_t SHSpad2[40] =
845 { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
846 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
847 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
848 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
850 static const uint8_t magic1[27] =
851 { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
852 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
853 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
855 static const uint8_t magic2[84] =
856 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
857 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
858 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
859 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
860 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
861 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
862 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
863 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
864 0x6b, 0x65, 0x79, 0x2e };
866 static const uint8_t magic3[84] =
867 { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
868 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
869 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
870 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
871 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
872 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
873 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
874 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
875 0x6b, 0x65, 0x79, 0x2e };
878 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
885 SHA1Update(&Context,nt_hashhash,16);
886 SHA1Update(&Context,nt_response,24);
887 SHA1Update(&Context,magic1,27);
888 SHA1Final(digest,&Context);
890 memcpy(masterkey,digest,16);
894 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
895 int keylen,int issend)
910 SHA1Update(&Context,masterkey,16);
911 SHA1Update(&Context,SHSpad1,40);
912 SHA1Update(&Context,s,84);
913 SHA1Update(&Context,SHSpad2,40);
914 SHA1Final(digest,&Context);
916 memcpy(sesskey,digest,keylen);
920 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
921 uint8_t *sendkey,uint8_t *recvkey)
923 uint8_t masterkey[16];
925 mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
927 mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
928 mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
932 * Generate MPPE keys.
934 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
935 uint8_t *sendkey,uint8_t *recvkey)
940 mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
943 * dictionary.microsoft defines these attributes as
944 * 'encrypt=2'. The functions in src/lib/radius.c will
945 * take care of encrypting/decrypting them as appropriate,
946 * so that we don't have to.
948 memcpy (sendkey, enckey1, 16);
949 memcpy (recvkey, enckey2, 16);
954 * mschap_authorize() - authorize user if we can authenticate
955 * it later. Add Auth-Type attribute if present in module
956 * configuration (usually Auth-Type must be "MS-CHAP")
958 static int mschap_authorize(void * instance, REQUEST *request)
960 #define inst ((rlm_mschap_t *)instance)
961 VALUE_PAIR *challenge = NULL, *response = NULL;
964 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
966 return RLM_MODULE_NOOP;
969 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
971 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
974 * Nothing we recognize. Don't do anything.
977 DEBUG2(" rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
978 return RLM_MODULE_NOOP;
981 DEBUG2(" rlm_mschap: Found MS-CHAP attributes. Setting 'Auth-Type = %s'", inst->xlat_name);
984 * Set Auth-Type to MS-CHAP. The authentication code
985 * will take care of turning clear-text passwords into
988 vp = pairmake("Auth-Type", inst->auth_type, T_OP_EQ);
989 if (!vp) return RLM_MODULE_FAIL;
990 pairmove(&request->config_items, &vp);
991 pairfree(&vp); /* may be NULL */
993 return RLM_MODULE_OK;
998 * mschap_authenticate() - authenticate user based on given
999 * attributes and configuration.
1000 * We will try to find out password in configuration
1001 * or in configured passwd file.
1002 * If one is found we will check paraneters given by NAS.
1004 * If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1006 * PAP: PW_USER_PASSWORD or
1007 * MS-CHAP: PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1008 * MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1009 * In case of password mismatch or locked account we MAY return
1010 * PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1011 * If MS-CHAP2 succeeds we MUST return
1012 * PW_MSCHAP2_SUCCESS
1014 static int mschap_authenticate(void * instance, REQUEST *request)
1016 #define inst ((rlm_mschap_t *)instance)
1017 VALUE_PAIR *challenge = NULL;
1018 VALUE_PAIR *response = NULL;
1019 VALUE_PAIR *password = NULL;
1020 VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1021 VALUE_PAIR *username;
1022 VALUE_PAIR *reply_attr;
1023 uint8_t nthashhash[16];
1024 uint8_t msch2resp[42];
1025 char *username_string;
1029 * Find the SMB-Account-Ctrl attribute, or the
1030 * SMB-Account-Ctrl-Text attribute.
1032 smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
1034 password = pairfind(request->config_items,
1035 PW_SMB_ACCOUNT_CTRL_TEXT);
1037 smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
1038 pairadd(&request->config_items, smb_ctrl);
1039 smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->vp_strvalue);
1044 * We're configured to do MS-CHAP authentication.
1045 * and account control information exists. Enforce it.
1049 * Password is not required.
1051 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
1052 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says no password is required.");
1053 return RLM_MODULE_OK;
1058 * Decide how to get the passwords.
1060 password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);
1063 * We need an LM-Password.
1065 lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1070 if ((lm_password->length == 16) ||
1071 ((lm_password->length == 32) &&
1072 (lrad_hex2bin(lm_password->vp_strvalue,
1073 lm_password->vp_strvalue, 16) == 16))) {
1074 DEBUG2(" rlm_mschap: Found LM-Password");
1075 lm_password->length = 16;
1078 radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1082 } else if (!password) {
1083 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create LM-Password.");
1085 } else { /* there is a configured Cleartext-Password */
1086 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1088 radlog(L_ERR, "No memory");
1090 smbdes_lmpwdhash(password->vp_strvalue,
1091 lm_password->vp_strvalue);
1092 lm_password->length = 16;
1093 pairadd(&request->config_items, lm_password);
1098 * We need an NT-Password.
1100 nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1102 if ((nt_password->length == 16) ||
1103 ((nt_password->length == 32) &&
1104 (lrad_hex2bin(nt_password->vp_strvalue,
1105 nt_password->vp_strvalue, 16) == 16))) {
1106 DEBUG2(" rlm_mschap: Found NT-Password");
1107 nt_password->length = 16;
1110 radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1113 } else if (!password) {
1114 DEBUG2(" rlm_mschap: No Cleartext-Password configured. Cannot create NT-Password.");
1116 } else { /* there is a configured Cleartext-Password */
1117 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1119 radlog(L_ERR, "No memory");
1120 return RLM_MODULE_FAIL;
1122 ntpwdhash(nt_password->vp_strvalue,
1123 password->vp_strvalue);
1124 nt_password->length = 16;
1125 pairadd(&request->config_items, nt_password);
1129 challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1131 DEBUG2(" rlm_mschap: No MS-CHAP-Challenge in the request");
1132 return RLM_MODULE_REJECT;
1136 * We also require an MS-CHAP-Response.
1138 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1141 * MS-CHAP-Response, means MS-CHAPv1
1147 * MS-CHAPv1 challenges are 8 octets.
1149 if (challenge->length < 8) {
1150 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1151 return RLM_MODULE_INVALID;
1155 * Responses are 50 octets.
1157 if (response->length < 50) {
1158 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1159 return RLM_MODULE_INVALID;
1163 * We are doing MS-CHAP. Calculate the MS-CHAP
1166 if (response->vp_octets[1] & 0x01) {
1167 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with NT-Password");
1168 password = nt_password;
1171 DEBUG2(" rlm_mschap: Told to do MS-CHAPv1 with LM-Password");
1172 password = lm_password;
1177 * Do the MS-CHAP authentication.
1179 if (do_mschap(inst, request, password, challenge->vp_octets,
1180 response->vp_octets + offset, nthashhash) < 0) {
1181 DEBUG2(" rlm_mschap: MS-CHAP-Response is incorrect.");
1182 add_reply(&request->reply->vps, *response->vp_octets,
1183 "MS-CHAP-Error", "E=691 R=1", 9);
1184 return RLM_MODULE_REJECT;
1189 } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1190 uint8_t mschapv1_challenge[16];
1193 * MS-CHAPv2 challenges are 16 octets.
1195 if (challenge->length < 16) {
1196 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1197 return RLM_MODULE_INVALID;
1201 * Responses are 50 octets.
1203 if (response->length < 50) {
1204 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1205 return RLM_MODULE_INVALID;
1209 * We also require a User-Name
1211 username = pairfind(request->packet->vps, PW_USER_NAME);
1213 radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1214 return RLM_MODULE_INVALID;
1219 * with_ntdomain_hack moved here
1221 if ((username_string = strchr(username->vp_strvalue, '\\')) != NULL) {
1222 if (inst->with_ntdomain_hack) {
1225 DEBUG2(" rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1226 username_string = username->vp_strvalue;
1229 username_string = username->vp_strvalue;
1233 * The old "mschapv2" function has been moved to
1236 * MS-CHAPv2 takes some additional data to create an
1237 * MS-CHAPv1 challenge, and then does MS-CHAPv1.
1239 challenge_hash(response->vp_octets + 2, /* peer challenge */
1240 challenge->vp_octets, /* our challenge */
1241 username_string, /* user name */
1242 mschapv1_challenge); /* resulting challenge */
1244 DEBUG2(" rlm_mschap: Told to do MS-CHAPv2 for %s with NT-Password",
1247 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1248 response->vp_octets + 26, nthashhash) < 0) {
1249 DEBUG2(" rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1250 add_reply(&request->reply->vps, *response->vp_octets,
1251 "MS-CHAP-Error", "E=691 R=1", 9);
1252 return RLM_MODULE_REJECT;
1256 * Get the NT-hash-hash, if necessary
1261 auth_response(username_string, /* without the domain */
1262 nthashhash, /* nt-hash-hash */
1263 response->vp_octets + 26, /* peer response */
1264 response->vp_octets + 2, /* peer challenge */
1265 challenge->vp_octets, /* our challenge */
1266 msch2resp); /* calculated MPPE key */
1267 add_reply( &request->reply->vps, *response->vp_octets,
1268 "MS-CHAP2-Success", msch2resp, 42);
1271 } else { /* Neither CHAPv1 or CHAPv2 response: die */
1272 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1273 return RLM_MODULE_INVALID;
1277 * We have a CHAP response, but the account may be
1278 * disabled. Reject the user with the same error code
1279 * we use when their password is invalid.
1283 * Account is disabled.
1285 * They're found, but they don't exist, so we
1286 * return 'not found'.
1288 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1289 ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1290 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1291 add_reply( &request->reply->vps, *response->vp_octets,
1292 "MS-CHAP-Error", "E=691 R=1", 9);
1293 return RLM_MODULE_NOTFOUND;
1297 * User is locked out.
1299 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1300 DEBUG2(" rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1301 add_reply( &request->reply->vps, *response->vp_octets,
1302 "MS-CHAP-Error", "E=647 R=0", 9);
1303 return RLM_MODULE_USERLOCK;
1307 /* now create MPPE attributes */
1308 if (inst->use_mppe) {
1309 uint8_t mppe_sendkey[34];
1310 uint8_t mppe_recvkey[34];
1313 DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1314 memset(mppe_sendkey, 0, 32);
1316 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1320 * According to RFC 2548 we
1321 * should send NT hash. But in
1322 * practice it doesn't work.
1323 * Instead, we should send nthashhash
1325 * This is an error on RFC 2548.
1328 * do_mschap cares to zero nthashhash if NT hash
1331 memcpy(mppe_sendkey + 8,
1333 mppe_add_reply(&request->reply->vps,
1334 "MS-CHAP-MPPE-Keys",
1336 } else if (chap == 2) {
1337 DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1338 mppe_chap2_gen_keys128(nthashhash,
1339 response->vp_octets + 26,
1340 mppe_sendkey, mppe_recvkey);
1342 mppe_add_reply(&request->reply->vps,
1345 mppe_add_reply(&request->reply->vps,
1350 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1351 (inst->require_encryption)? "0x00000002":"0x00000001",
1353 rad_assert(reply_attr != NULL);
1354 pairadd(&request->reply->vps, reply_attr);
1355 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1356 (inst->require_strong)? "0x00000004":"0x00000006",
1358 rad_assert(reply_attr != NULL);
1359 pairadd(&request->reply->vps, reply_attr);
1361 } /* else we weren't asked to use MPPE */
1363 return RLM_MODULE_OK;
1367 module_t rlm_mschap = {
1370 RLM_TYPE_THREAD_SAFE, /* type */
1371 mschap_instantiate, /* instantiation */
1372 mschap_detach, /* detach */
1374 mschap_authenticate, /* authenticate */
1375 mschap_authorize, /* authorize */
1376 NULL, /* pre-accounting */
1377 NULL, /* accounting */
1378 NULL, /* checksimul */
1379 NULL, /* pre-proxy */
1380 NULL, /* post-proxy */
1381 NULL /* post-auth */