Remove compile warnings
[freeradius.git] / src / modules / rlm_mschap / rlm_mschap.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15  
16 /**
17  * $Id$
18  * @file rlm_mschap.c
19  * @brief Implemented mschap authentication.
20  *
21  * @copyright 2000,2001,2006  The FreeRADIUS server project
22  */
23
24 /*  MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
25 #include        <freeradius-devel/ident.h>
26 RCSID("$Id$")
27
28 #include        <freeradius-devel/radiusd.h>
29 #include        <freeradius-devel/modules.h>
30 #include        <freeradius-devel/rad_assert.h>
31 #include        <freeradius-devel/md5.h>
32 #include        <freeradius-devel/sha1.h>
33
34 #include        <ctype.h>
35
36 #include        "mschap.h"
37 #include        "smbdes.h"
38
39 #ifdef HAVE_OPENSSL_CRYPTO_H
40 #include        <openssl/rc4.h>
41 #endif
42
43 #ifdef __APPLE__
44 extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair);
45 #endif
46
47 /* Allowable account control bits */
48 #define ACB_DISABLED   0x00010000       //!< User account disabled.
49 #define ACB_HOMDIRREQ  0x00020000       //!< Home directory required.
50 #define ACB_PWNOTREQ   0x00040000       //!< User password not required.
51 #define ACB_TEMPDUP    0x00080000       //!< Temporary duplicate account.
52 #define ACB_NORMAL     0x00100000       //!< Normal user account.
53 #define ACB_MNS        0x00200000       //!< MNS logon user account.
54 #define ACB_DOMTRUST   0x00400000       //!< Interdomain trust account.
55 #define ACB_WSTRUST    0x00800000       //!< Workstation trust account.
56 #define ACB_SVRTRUST   0x01000000       //!< Server trust account.
57 #define ACB_PWNOEXP    0x02000000       //!< User password does not expire.
58 #define ACB_AUTOLOCK   0x04000000       //!< Account auto locked.
59 #define ACB_PW_EXPIRED 0x00020000       //!< Password Expired.
60
61 static int pdb_decode_acct_ctrl(const char *p)
62 {
63         int acct_ctrl = 0;
64         int done = 0;
65
66         /*
67          * Check if the account type bits have been encoded after the
68          * NT password (in the form [NDHTUWSLXI]).
69          */
70
71         if (*p != '[') return 0;
72
73         for (p++; *p && !done; p++) {
74                 switch (*p) {
75                         case 'N': /* 'N'o password. */
76                           acct_ctrl |= ACB_PWNOTREQ;
77                           break;
78
79                         case 'D':  /* 'D'isabled. */
80                           acct_ctrl |= ACB_DISABLED ;
81                           break;
82
83                         case 'H':  /* 'H'omedir required. */
84                           acct_ctrl |= ACB_HOMDIRREQ;
85                           break;
86
87                         case 'T': /* 'T'emp account. */
88                           acct_ctrl |= ACB_TEMPDUP;
89                           break;
90
91                         case 'U': /* 'U'ser account (normal). */
92                           acct_ctrl |= ACB_NORMAL;
93                           break;
94
95                         case 'M': /* 'M'NS logon user account. What is this? */
96                           acct_ctrl |= ACB_MNS;
97                           break;
98
99                         case 'W': /* 'W'orkstation account. */
100                           acct_ctrl |= ACB_WSTRUST;
101                           break;
102
103                         case 'S': /* 'S'erver account. */
104                           acct_ctrl |= ACB_SVRTRUST;
105                           break;
106
107                         case 'L': /* 'L'ocked account. */
108                           acct_ctrl |= ACB_AUTOLOCK;
109                           break;
110
111                         case 'X': /* No 'X'piry on password */
112                           acct_ctrl |= ACB_PWNOEXP;
113                           break;
114
115                         case 'I': /* 'I'nterdomain trust account. */
116                           acct_ctrl |= ACB_DOMTRUST;
117                           break;
118
119                         case 'e': /* 'e'xpired, the password has */
120                           acct_ctrl |= ACB_PW_EXPIRED;
121                           break;
122
123                         case ' ': /* ignore spaces */
124                           break;
125
126                         case ':':
127                         case '\n':
128                         case '\0':
129                         case ']':
130                         default:
131                           done = 1;
132                           break;
133                 }
134         }
135
136         return acct_ctrl;
137 }
138
139
140 typedef struct rlm_mschap_t {
141         int use_mppe;
142         int require_encryption;
143         int require_strong;
144         int with_ntdomain_hack; /* this should be in another module */
145         char *passwd_file;
146         const char *xlat_name;
147         char *ntlm_auth;
148         char *ntlm_cpw;
149         char *ntlm_cpw_username;
150         char *ntlm_cpw_domain;
151         char *local_cpw;
152         const char *auth_type;
153         int allow_retry;
154         char *retry_msg;
155 #ifdef __APPLE__
156         int  open_directory;
157 #endif  
158 } rlm_mschap_t;
159
160
161 /*
162  *      Does dynamic translation of strings.
163  *
164  *      Pulls NT-Response, LM-Response, or Challenge from MSCHAP
165  *      attributes.
166  */
167 static size_t mschap_xlat(void *instance, REQUEST *request,
168                        const char *fmt, char *out, size_t outlen)
169 {
170         size_t          i, data_len;
171         uint8_t         *data = NULL;
172         uint8_t         buffer[32];
173         VALUE_PAIR      *user_name;
174         VALUE_PAIR      *chap_challenge, *response;
175         rlm_mschap_t    *inst = instance;
176
177         response = NULL;
178
179         /*
180          *      Challenge means MS-CHAPv1 challenge, or
181          *      hash of MS-CHAPv2 challenge, and peer challenge.
182          */
183         if (strncasecmp(fmt, "Challenge", 9) == 0) {
184                 chap_challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
185                 if (!chap_challenge) {
186                         RDEBUG2("No MS-CHAP-Challenge in the request.");
187                         return 0;
188                 }
189
190                 /*
191                  *      MS-CHAP-Challenges are 8 octets,
192                  *      for MS-CHAPv2
193                  */
194                 if (chap_challenge->length == 8) {
195                         RDEBUG2(" mschap1: %02x",
196                                chap_challenge->vp_octets[0]);
197                         data = chap_challenge->vp_octets;
198                         data_len = 8;
199
200                         /*
201                          *      MS-CHAP-Challenges are 16 octets,
202                          *      for MS-CHAPv2.
203                          */
204                 } else if (chap_challenge->length == 16) {
205                         VALUE_PAIR *name_attr, *response_name;
206                         char *username_string;
207
208                         response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
209                         if (!response) {
210                                 RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
211                                 return 0;
212                         }
213
214                         /*
215                          *      FIXME: Much of this is copied from
216                          *      below.  We should put it into a
217                          *      separate function.
218                          */
219
220                         /*
221                          *      Responses are 50 octets.
222                          */
223                         if (response->length < 50) {
224                                 radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
225                                 return 0;
226                         }
227
228                         user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
229                         if (!user_name) {
230                                 RDEBUG2("User-Name is required to calculate MS-CHAPv1 Challenge.");
231                                 return 0;
232                         }
233
234                         /*
235                          *      Check for MS-CHAP-User-Name and if found, use it
236                          *      to construct the MSCHAPv1 challenge.  This is
237                          *      set by rlm_eap_mschap to the MS-CHAP Response
238                          *      packet Name field.
239                          *
240                          *      We prefer this to the User-Name in the
241                          *      packet.
242                          */
243                         response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
244                         if (response_name) {
245                                 name_attr = response_name;
246                         } else {
247                                 name_attr = user_name;
248                         }
249
250                         /*
251                          *      with_ntdomain_hack moved here, too.
252                          */
253                         if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
254                                 if (inst->with_ntdomain_hack) {
255                                         username_string++;
256                                 } else {
257                                         RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
258                                         username_string = name_attr->vp_strvalue;
259                                 }
260                         } else {
261                                 username_string = name_attr->vp_strvalue;
262                         }
263
264                         if (response_name &&
265                             ((user_name->length != response_name->length) ||
266                              (strncasecmp(user_name->vp_strvalue, response_name->vp_strvalue, user_name->length) != 0))) {
267                                 RDEBUGW("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", user_name->vp_strvalue, response_name->vp_strvalue);
268                         }
269
270                         /*
271                          *      Get the MS-CHAPv1 challenge
272                          *      from the MS-CHAPv2 peer challenge,
273                          *      our challenge, and the user name.
274                          */
275                         RDEBUG2("Creating challenge hash with username: %s",
276                                 username_string);
277                         mschap_challenge_hash(response->vp_octets + 2,
278                                        chap_challenge->vp_octets,
279                                        username_string, buffer);
280                         data = buffer;
281                         data_len = 8;
282                 } else {
283                         RDEBUG2("Invalid MS-CHAP challenge length");
284                         return 0;
285                 }
286
287                 /*
288                  *      Get the MS-CHAPv1 response, or the MS-CHAPv2
289                  *      response.
290                  */
291         } else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
292                 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
293                 if (!response) response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
294                 if (!response) {
295                         RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
296                         return 0;
297                 }
298
299                 /*
300                  *      For MS-CHAPv1, the NT-Response exists only
301                  *      if the second octet says so.
302                  */
303                 if ((response->da->vendor == VENDORPEC_MICROSOFT) &&
304                     (response->da->attr == PW_MSCHAP_RESPONSE) &&
305                     ((response->vp_octets[1] & 0x01) == 0)) {
306                         RDEBUG2("No NT-Response in MS-CHAP-Response");
307                         return 0;
308                 }
309
310                 /*
311                  *      MS-CHAP-Response and MS-CHAP2-Response have
312                  *      the NT-Response at the same offset, and are
313                  *      the same length.
314                  */
315                 data = response->vp_octets + 26;
316                 data_len = 24;
317
318                 /*
319                  *      LM-Response is deprecated, and exists only
320                  *      in MS-CHAPv1, and not often there.
321                  */
322         } else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
323                 response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
324                 if (!response) {
325                         RDEBUG2("No MS-CHAP-Response was found in the request.");
326                         return 0;
327                 }
328
329                 /*
330                  *      For MS-CHAPv1, the NT-Response exists only
331                  *      if the second octet says so.
332                  */
333                 if ((response->vp_octets[1] & 0x01) != 0) {
334                         RDEBUG2("No LM-Response in MS-CHAP-Response");
335                         return 0;
336                 }
337                 data = response->vp_octets + 2;
338                 data_len = 24;
339
340                 /*
341                  *      Pull the NT-Domain out of the User-Name, if it exists.
342                  */
343         } else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
344                 char *p, *q;
345
346                 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
347                 if (!user_name) {
348                         RDEBUG2("No User-Name was found in the request.");
349                         return 0;
350                 }
351
352                 /*
353                  *      First check to see if this is a host/ style User-Name
354                  *      (a la Kerberos host principal)
355                  */
356                 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
357                         /*
358                          *      If we're getting a User-Name formatted in this way,
359                          *      it's likely due to PEAP.  The Windows Domain will be
360                          *      the first domain component following the hostname,
361                          *      or the machine name itself if only a hostname is supplied
362                          */
363                         p = strchr(user_name->vp_strvalue, '.');
364                         if (!p) {
365                                 RDEBUG2("setting NT-Domain to same as machine name");
366                                 strlcpy(out, user_name->vp_strvalue + 5, outlen);
367                         } else {
368                                 p++;    /* skip the period */
369                                 q = strchr(p, '.');
370                                 /*
371                                  * use the same hack as below
372                                  * only if another period was found
373                                  */
374                                 if (q) *q = '\0';
375                                 strlcpy(out, p, outlen);
376                                 if (q) *q = '.';
377                         }
378                 } else {
379                         p = strchr(user_name->vp_strvalue, '\\');
380                         if (!p) {
381                                 RDEBUG2("No NT-Domain was found in the User-Name.");
382                                 return 0;
383                         }
384
385                         /*
386                          *      Hack.  This is simpler than the alternatives.
387                          */
388                         *p = '\0';
389                         strlcpy(out, user_name->vp_strvalue, outlen);
390                         *p = '\\';
391                 }
392
393                 return strlen(out);
394
395                 /*
396                  *      Pull the User-Name out of the User-Name...
397                  */
398         } else if (strncasecmp(fmt, "User-Name", 9) == 0) {
399                 char *p;
400
401                 user_name = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
402                 if (!user_name) {
403                         RDEBUG2("No User-Name was found in the request.");
404                         return 0;
405                 }
406
407                 /*
408                  *      First check to see if this is a host/ style User-Name
409                  *      (a la Kerberos host principal)
410                  */
411                 if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
412                         /*
413                          *      If we're getting a User-Name formatted in this way,
414                          *      it's likely due to PEAP.  When authenticating this against
415                          *      a Domain, Windows will expect the User-Name to be in the
416                          *      format of hostname$, the SAM version of the name, so we
417                          *      have to convert it to that here.  We do so by stripping
418                          *      off the first 5 characters (host/), and copying everything
419                          *      from that point to the first period into a string and appending
420                          *      a $ to the end.
421                          */
422                         p = strchr(user_name->vp_strvalue, '.');
423                         /*
424                          * use the same hack as above
425                          * only if a period was found
426                          */
427                         if (p) *p = '\0';
428                         snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
429                         if (p) *p = '.';
430                 } else {
431                         p = strchr(user_name->vp_strvalue, '\\');
432                         if (p) {
433                                 p++;    /* skip the backslash */
434                         } else {
435                                 p = user_name->vp_strvalue; /* use the whole User-Name */
436                         }
437                         strlcpy(out, p, outlen);
438                 }
439
440                 return strlen(out);
441
442                 /*
443                  * Return the NT-Hash of the passed string
444                  */
445         } else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
446                 const char *p;
447                 char buf2[1024];
448
449                 p = fmt + 8;    /* 7 is the length of 'NT-Hash' */
450                 if ((p == '\0')  || (outlen <= 32))
451                         return 0;
452
453                 while (isspace(*p)) p++;
454
455                 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL,NULL)) {
456                         RDEBUG("xlat failed");
457                         *buffer = '\0';
458                         return 0;
459                 }
460
461                 mschap_ntpwdhash(buffer,buf2);
462
463                 fr_bin2hex(buffer, out, 16);
464                 out[32] = '\0';
465                 RDEBUG("NT-Hash of %s = %s", buf2, out);
466                 return 32;
467
468                 /*
469                  * Return the LM-Hash of the passed string
470                  */
471         } else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
472                 const char *p;
473                 char buf2[1024];
474
475                 p = fmt + 8;    /* 7 is the length of 'LM-Hash' */
476                 if ((p == '\0') || (outlen <= 32))
477                         return 0;
478
479                 while (isspace(*p)) p++;
480
481                 if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL,NULL)) {
482                         RDEBUG("xlat failed");
483                         *buffer = '\0';
484                         return 0;
485                 }
486
487                 smbdes_lmpwdhash(buf2, buffer);
488                 fr_bin2hex(buffer, out, 16);
489                 out[32] = '\0';
490                 RDEBUG("LM-Hash of %s = %s", buf2, out);
491                 return 32;
492         } else {
493                 RDEBUG2("Unknown expansion string \"%s\"",
494                        fmt);
495                 return 0;
496         }
497
498         if (outlen == 0) return 0; /* nowhere to go, don't do anything */
499
500         /*
501          *      Didn't set anything: this is bad.
502          */
503         if (!data) {
504                 RDEBUG2("Failed to do anything intelligent");
505                 return 0;
506         }
507
508         /*
509          *      Check the output length.
510          */
511         if (outlen < ((data_len * 2) + 1)) {
512                 data_len = (outlen - 1) / 2;
513         }
514
515         /*
516          *
517          */
518         for (i = 0; i < data_len; i++) {
519                 sprintf(out + (2 * i), "%02x", data[i]);
520         }
521         out[data_len * 2] = '\0';
522
523         return data_len * 2;
524 }
525
526
527 static const CONF_PARSER passchange_config[] = {
528         { "ntlm_auth",   PW_TYPE_STRING_PTR,
529           offsetof(rlm_mschap_t, ntlm_cpw), NULL,  NULL },
530         { "ntlm_auth_username",   PW_TYPE_STRING_PTR,
531           offsetof(rlm_mschap_t, ntlm_cpw_username), NULL,  NULL },
532         { "ntlm_auth_domain",   PW_TYPE_STRING_PTR,
533           offsetof(rlm_mschap_t, ntlm_cpw_domain), NULL,  NULL },
534         { "local_cpw",   PW_TYPE_STRING_PTR,
535           offsetof(rlm_mschap_t, local_cpw), NULL,  NULL },
536         { NULL, -1, 0, NULL, NULL }             /* end the list */
537 };
538 static const CONF_PARSER module_config[] = {
539         /*
540          *      Cache the password by default.
541          */
542         { "use_mppe",    PW_TYPE_BOOLEAN,
543           offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
544         { "require_encryption",    PW_TYPE_BOOLEAN,
545           offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
546         { "require_strong",    PW_TYPE_BOOLEAN,
547           offsetof(rlm_mschap_t,require_strong), NULL, "no" },
548         { "with_ntdomain_hack",     PW_TYPE_BOOLEAN,
549           offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "yes" },
550         { "passwd",   PW_TYPE_STRING_PTR,
551           offsetof(rlm_mschap_t, passwd_file), NULL,  NULL },
552         { "ntlm_auth",   PW_TYPE_STRING_PTR,
553           offsetof(rlm_mschap_t, ntlm_auth), NULL,  NULL },
554         { "passchange", PW_TYPE_SUBSECTION, 0, NULL, (const void *) passchange_config },
555         { "allow_retry",   PW_TYPE_BOOLEAN,
556           offsetof(rlm_mschap_t, allow_retry), NULL,  "yes" },
557         { "retry_msg",   PW_TYPE_STRING_PTR,
558           offsetof(rlm_mschap_t, retry_msg), NULL,  NULL },
559 #ifdef __APPLE__
560         { "use_open_directory",    PW_TYPE_BOOLEAN,
561           offsetof(rlm_mschap_t,open_directory), NULL, "yes" },
562 #endif
563
564         { NULL, -1, 0, NULL, NULL }             /* end the list */
565 };
566
567 /*
568  *      deinstantiate module, free all memory allocated during
569  *      mschap_instantiate()
570  */
571 static int mschap_detach(void *instance)
572 {
573         rlm_mschap_t *inst = instance;
574         if (inst->xlat_name) {
575                 xlat_unregister(inst->xlat_name, mschap_xlat, instance);
576         }
577         return 0;
578 }
579
580 /*
581  *      Create instance for our module. Allocate space for
582  *      instance structure and read configuration parameters
583  */
584 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
585 {
586         const char *name;
587         rlm_mschap_t *inst;
588
589         *instance = inst = talloc_zero(conf, rlm_mschap_t);
590         if (!inst) return -1;
591
592         if (cf_section_parse(conf, inst, module_config) < 0) {
593                 return -1;
594         }
595
596         /*
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
600          *      error message.
601          */
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");
604                 return -1;
605         }
606
607         /*
608          *      Create the dynamic translation.
609          */
610         name = cf_section_name2(conf);
611         if (!name) name = cf_section_name1(conf);
612         inst->xlat_name = name;
613         xlat_register(inst->xlat_name, mschap_xlat, inst);
614
615         /*
616          *      For backwards compatibility
617          */
618         if (!dict_valbyname(PW_AUTH_TYPE, 0, inst->xlat_name)) {
619                 inst->auth_type = "MS-CHAP";
620         } else {
621                 inst->auth_type = inst->xlat_name;
622         }
623
624         return 0;
625 }
626
627 /*
628  *      add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
629  *      attribute to reply packet
630  */
631 void mschap_add_reply(REQUEST *request, VALUE_PAIR** vp, unsigned char ident,
632                       const char* name, const char* value, int len)
633 {
634         VALUE_PAIR *reply_attr;
635         reply_attr = pairmake(name, "", T_OP_EQ);
636         if (!reply_attr) {
637                 RDEBUG("Failed to create attribute %s: %s\n", name, fr_strerror());
638                 return;
639         }
640
641         reply_attr->vp_octets[0] = ident;
642         memcpy(reply_attr->vp_octets + 1, value, len);
643         reply_attr->length = len + 1;
644         pairadd(vp, reply_attr);
645 }
646
647 /*
648  *      Add MPPE attributes to the reply.
649  */
650 static void mppe_add_reply(REQUEST *request,
651                            const char* name, const uint8_t * value, int len)
652 {
653        VALUE_PAIR *vp;
654        vp = radius_pairmake(request, &request->reply->vps, name, "", T_OP_EQ);
655        if (!vp) {
656                RDEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, fr_strerror());
657                return;
658        }
659
660        memcpy(vp->vp_octets, value, len);
661        vp->length = len;
662 }
663
664 static int write_all(int fd, const char *buf, int len) {
665         int rv,done=0;
666
667         while (done < len) {
668                 rv = write(fd, buf+done, len-done);
669                 if (rv <= 0)
670                         break;
671                 done += rv;
672         }
673         return done;
674 }
675
676 /*
677  * Perform an MS-CHAP2 password change
678  */
679
680 static int do_mschap_cpw(rlm_mschap_t *inst,
681                      REQUEST *request, VALUE_PAIR *nt_password,
682                      uint8_t *new_nt_password,
683                      uint8_t *old_nt_hash,
684                      int do_ntlm_auth)
685 {
686         if (inst->ntlm_cpw && do_ntlm_auth) {
687                 /*
688                  * we're going to run ntlm_auth in helper-mode
689                  * we're expecting to use the ntlm-change-password-1 protocol
690                  * which needs the following on stdin:
691                  *
692                  * username: %{mschap:User-Name}
693                  * nt-domain: %{mschap:NT-Domain}
694                  * new-nt-password-blob: bin2hex(new_nt_password) - 1032 bytes encoded
695                  * old-nt-hash-blob: bin2hex(old_nt_hash) - 32 bytes encoded
696                  * new-lm-password-blob: 00000...0000 - 1032 bytes null
697                  * old-lm-hash-blob: 000....000 - 32 bytes null
698                  * .\n
699                  *
700                  * ...and it should then print out
701                  *
702                  * Password-Change: Yes
703                  *
704                  * or
705                  *
706                  * Password-Change: No
707                  * Password-Change-Error: blah
708                  */
709
710                 int to_child=-1;
711                 int from_child=-1;
712                 pid_t pid, child_pid;
713                 int status, len;
714                 char buf[2048];
715                 char *pmsg;
716                 const char *emsg;
717
718                 RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper");
719
720                 /*
721                  * Start up ntlm_auth with a pipe on stdin and stdout
722                  */
723
724                 pid = radius_start_program(inst->ntlm_cpw, request, 1, &to_child, &from_child, NULL, 0);
725                 if (pid < 0) {
726                         RDEBUG2("could not exec ntlm_auth cpw command");
727                         return -1;
728                 }
729
730                 /*
731                  * write the stuff to the client
732                  */
733
734                 if (inst->ntlm_cpw_username) {
735                         len = radius_xlat(buf, sizeof(buf) - 2, inst->ntlm_cpw_username, request, NULL, NULL);
736                         strcat(buf, "\n");
737                         len++;
738
739                         if (write_all(to_child, buf, len) != len) {
740                                 RDEBUG2("failed to write username to child");
741                                 goto ntlm_auth_err;
742                         }
743                 } else {
744                         RDEBUG("No ntlm_auth username set - passchange will definitely fail!");
745                 }
746
747                 if (inst->ntlm_cpw_domain) {
748                         len = radius_xlat(buf, sizeof(buf) - 2, inst->ntlm_cpw_domain, request, NULL, NULL);
749                         strcat(buf, "\n");
750                         len++;
751
752                         if (write_all(to_child, buf, len) != len) {
753                                 RDEBUG2("failed to write domain to child");
754                                 goto ntlm_auth_err;
755                         }
756                 } else {
757                         RDEBUG("No ntlm_auth domain set - username must be full-username to work");
758                 }
759
760                 /* now the password blobs */
761                 len = sprintf(buf, "new-nt-password-blob: ");
762                 fr_bin2hex(new_nt_password, buf+len, 516);
763                 buf[len+1032] = '\n';
764                 buf[len+1033] = '\0';
765                 len = strlen(buf);
766                 if (write_all(to_child, buf, len) != len) {
767                         RDEBUG2("failed to write new password blob to child");
768                         goto ntlm_auth_err;
769                 }
770
771                 len = sprintf(buf, "old-nt-hash-blob: ");
772                 fr_bin2hex(old_nt_hash, buf+len, 16);
773                 buf[len+32] = '\n';
774                 buf[len+33] = '\0';
775                 len = strlen(buf);
776                 if (write_all(to_child, buf, len) != len) {
777                         RDEBUG2("failed to write old hash blob to child");
778                         goto ntlm_auth_err;
779                 }
780
781                 /*
782                  * in current samba versions, failure to supply empty
783                  * LM password/hash blobs causes the change to fail
784                  */
785                 len = sprintf(buf, "new-lm-password-blob: %01032i\n", 0);
786                 if (write_all(to_child, buf, len) != len) {
787                         RDEBUG2("failed to write dummy LM password to child");
788                         goto ntlm_auth_err;
789                 }
790                 len = sprintf(buf, "old-lm-hash-blob: %032i\n", 0);
791                 if (write_all(to_child, buf, len) != len) {
792                         RDEBUG2("failed to write dummy LM hash to child");
793                         goto ntlm_auth_err;
794                 }
795                 if (write_all(to_child, ".\n", 2) != 2) {
796                         RDEBUG2("failed to send finish to child");
797                         goto ntlm_auth_err;
798                 }
799                 close(to_child);
800                 to_child = -1;
801
802                 /*
803                  * Read from the child
804                  */
805                 len = radius_readfrom_program(from_child, pid, 10, buf, sizeof(buf));
806                 if (len < 0) {
807                         /* radius_readfrom_program will have closed from_child for us */
808                         RDEBUG2("Failure reading from child");
809                         return -1;
810                 }
811                 close(from_child);
812                 from_child = -1;
813
814                 buf[len] = 0;
815                 RDEBUG2("ntlm_auth said: %s", buf);
816
817                 child_pid = rad_waitpid(pid, &status);
818                 if (child_pid == 0) {
819                         RDEBUG2("Timeout waiting for child");
820                         return -1;
821                 }
822                 if (child_pid != pid) {
823                         RDEBUG("Abnormal exit status: %s", strerror(errno));
824                         return -1;
825                 }
826
827                 if (strstr(buf, "Password-Change: Yes")) {
828                         RDEBUG2("ntlm_auth password change succeeded");
829                         return 0;
830                 }
831
832                 pmsg = strstr(buf, "Password-Change-Error: ");
833                 if (pmsg) {
834                         emsg = strsep(&pmsg, "\n");
835                 } else {
836                         emsg = "could not find error";
837                 }
838                 RDEBUG2("ntlm auth password change failed: %s", emsg);
839
840 ntlm_auth_err:
841                 /* safe because these either need closing or are == -1 */
842                 close(to_child);
843                 close(from_child);
844
845                 return -1;
846
847         } else if (inst->local_cpw) {
848 #ifdef HAVE_OPENSSL_CRYPTO_H
849                 /*
850                  * decrypt the new password blob, add it as a temporary request
851                  * variable, xlat the local_cpw string, then remove it
852                  *
853                  * this allows is to write e..g
854                  *
855                  * %{sql:insert into ...}
856                  *
857                  * ...or...
858                  *
859                  * %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}"
860                  *
861                  */
862
863                 VALUE_PAIR *new_pass, *new_hash;
864                 uint8_t *p;
865                 size_t i, result_len;
866                 size_t passlen;
867                 char result[253];
868                 uint8_t nt_pass_decrypted[516], old_nt_hash_expected[16];
869                 RC4_KEY key;
870
871                 if (!nt_password) {
872                         RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute");
873                         return -1;
874                 } else {
875                         RDEBUG("Doing MS-CHAPv2 password change locally");
876                 }
877
878                 /*
879                  * decrypt the blob
880                  */
881                 RC4_set_key(&key, nt_password->length, nt_password->vp_octets);
882                 RC4(&key, 516, new_nt_password, nt_pass_decrypted);
883
884                 /*
885                  * pwblock is
886                  * 512-N bytes random pad
887                  * N bytes password as utf-16-le
888                  * 4 bytes - N as big-endian int
889                  */
890
891                 passlen = nt_pass_decrypted[512];
892                 passlen += nt_pass_decrypted[513] << 8;
893                 if ((nt_pass_decrypted[514] != 0) ||
894                     (nt_pass_decrypted[515] != 0)) {
895                         RDEBUG2("Decrypted new password blob claims length > 65536 - probably an invalid NT-Password");
896                         return -1;
897                 }
898
899                 /*
900                  * sanity check - passlen positive and <= 512
901                  * if not, crypto has probably gone wrong
902                  */
903                 if (passlen > 512) {
904                         RDEBUG2("Decrypted new password blob claims length %u > 512 - probably an invalid NT-Password", passlen);
905                         return -1;
906                 }
907
908                 p = nt_pass_decrypted + 512 - passlen;
909
910                 /*
911                  * the new NT hash - this should be preferred over the
912                  * cleartext password as it avoids unicode hassles
913                  */
914                 new_hash = radius_pairmake(request, &request->packet->vps,
915                                 "MS-CHAP-New-NT-Password", "",
916                                 T_OP_EQ);
917                 fr_md4_calc(new_hash->vp_octets, p, passlen);
918                 new_hash->length = 16;
919
920                 /*
921                  * check that nt_password encrypted with new_hash
922                  * matches the old_hash value from the client
923                  */
924                 smbhash(old_nt_hash_expected, nt_password->vp_octets, new_hash->vp_octets);
925                 smbhash(old_nt_hash_expected+8, nt_password->vp_octets+8, new_hash->vp_octets+7);
926                 if (memcmp(old_nt_hash_expected, old_nt_hash, 16)!=0) {
927                         RDEBUG2("old NT hash value from client does not match our value");
928                         return -1;
929                 }
930
931                 /*
932                  * the new cleartext password, which is utf-16
933                  * do some unpleasant vileness to turn it into
934                  * utf8 without pulling in libraries like iconv
935                  */
936                 new_pass = radius_pairmake(request, &request->packet->vps,
937                                 "MS-CHAP-New-Cleartext-Password", "",
938                                 T_OP_EQ);
939                 new_pass->length = 0;
940                 i = 0;
941                 while (i<passlen) {
942                         /*
943                          * The client-supplied password is utf-16.
944                          * We really must perform a proper conversion
945                          * to utf8 here, and the same in the other direction
946                          * when we calculate NT-Password below, else non-ascii
947                          * characters will fail - I know from experience that
948                          * UK pound and Euro symbols are common in users
949                          * passwords (money obsessed!)
950                          */
951                         int c;
952
953                         c = p[i++];
954                         c += p[i++] << 8;
955
956                         /*
957                          * gah. nasty. maybe we should just pull in iconv?
958                          */
959
960                         if (c < 0x7f) {
961                                 /* ascii char */
962                                 if (new_pass->length >= 253) {
963                                         RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
964                                         break;
965                                 }
966                                 new_pass->vp_strvalue[new_pass->length++] = c;
967                         } else if (c < 0x7ff) {
968                                 /* 2-byte */
969                                 if (new_pass->length >= 252) {
970                                         RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
971                                         break;
972                                 }
973                                 new_pass->vp_strvalue[new_pass->length++] = 0xc0 + (c >> 6);
974                                 new_pass->vp_strvalue[new_pass->length++] = 0x80 + (c & 0x3f);
975                         } else {
976                                 /* 3-byte */
977                                 if (new_pass->length >= 251) {
978                                         RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i);
979                                         break;
980                                 }
981                                 new_pass->vp_strvalue[new_pass->length++] = 0xe0 + (c >> 12);
982                                 new_pass->vp_strvalue[new_pass->length++] = 0x80 + ((c>>6) & 0x3f);
983                                 new_pass->vp_strvalue[new_pass->length++] = 0x80 + (c & 0x3f);
984                         }
985                 }
986
987
988
989
990                 /*
991                  * perform the xlat
992                  */
993                 result_len = radius_xlat(result, sizeof(result), inst->local_cpw, request, NULL, NULL);
994                 if (!result_len) {
995                         RDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure");
996                         return -1;
997                 }
998
999                 RDEBUG("MS-CHAPv2 password change succeeded: %s", result);
1000
1001                 /*
1002                  * update the NT-Password attribute with the new hash
1003                  * this lets us fall through to the authentication
1004                  * code using the new hash, not the old one
1005                  */
1006                 memcpy(nt_password->vp_octets, new_hash->vp_octets, new_hash->length);
1007
1008                 /*
1009                  * rock on! password change succeeded
1010                  */
1011                 return 0;
1012 #else
1013                 RDEBUG("Local MS-CHAPv2 password changes require OpenSSL support");
1014                 return -1;
1015 #endif
1016         } else {
1017                 RDEBUG("MS-CHAPv2 password change not configured");
1018         }
1019
1020         return -1;
1021 }
1022
1023 /*
1024  *      Do the MS-CHAP stuff.
1025  *
1026  *      This function is here so that all of the MS-CHAP related
1027  *      authentication is in one place, and we can perhaps later replace
1028  *      it with code to call winbindd, or something similar.
1029  */
1030 static int do_mschap(rlm_mschap_t *inst,
1031                      REQUEST *request, VALUE_PAIR *password,
1032                      uint8_t *challenge, uint8_t *response,
1033                      uint8_t *nthashhash, int do_ntlm_auth)
1034 {
1035         uint8_t         calculated[24];
1036
1037         rad_assert(request != NULL);
1038
1039         /*
1040          *      Do normal authentication.
1041          */
1042         if (!do_ntlm_auth) {
1043                 /*
1044                  *      No password: can't do authentication.
1045                  */
1046                 if (!password) {
1047                         RDEBUG2("FAILED: No NT/LM-Password.  Cannot perform authentication.");
1048                         return -1;
1049                 }
1050
1051                 smbdes_mschap(password->vp_octets, challenge, calculated);
1052                 if (rad_digest_cmp(response, calculated, 24) != 0) {
1053                         return -1;
1054                 }
1055
1056                 /*
1057                  *      If the password exists, and is an NT-Password,
1058                  *      then calculate the hash of the NT hash.  Doing this
1059                  *      here minimizes work for later.
1060                  */
1061                 if (password && !password->da->vendor &&
1062                     (password->da->attr == PW_NT_PASSWORD)) {
1063                         fr_md4_calc(nthashhash, password->vp_octets, 16);
1064                 } else {
1065                         memset(nthashhash, 0, 16);
1066                 }
1067         } else {                /* run ntlm_auth */
1068                 int     result;
1069                 char    buffer[256];
1070
1071                 memset(nthashhash, 0, 16);
1072
1073                 /*
1074                  *      Run the program, and expect that we get 16
1075                  */
1076                 result = radius_exec_program(inst->ntlm_auth, request,
1077                                              TRUE, /* wait */
1078                                              buffer, sizeof(buffer),
1079                                              NULL, NULL, 1);
1080                 if (result != 0) {
1081                         char *p;
1082                         VALUE_PAIR *vp = NULL;
1083
1084                         /*
1085                          * look for "Password expired", or "Must
1086                          * change password".
1087                          */
1088                         if (strstr(buffer, "Password expired") ||
1089                             strstr(buffer, "Must change password")) {
1090                                 RDEBUG2("ntlm_auth says %s", buffer);
1091                                 return -648;
1092                         }
1093
1094                         RDEBUG2("External script failed.");
1095
1096                         vp = pairmake("Module-Failure-Message", "", T_OP_EQ);
1097                         if (!vp) {
1098                                 radlog_request(L_ERR, 0, request, "No memory to allocate Module-Failure-Message");
1099                                 return RLM_MODULE_FAIL;
1100                         }
1101
1102                         p = strchr(buffer, '\n');
1103                         if (p) *p = '\0';
1104                         snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
1105                                 "%s: External script says %s",
1106                                  inst->xlat_name, buffer);
1107                         vp->length = strlen(vp->vp_strvalue);
1108                         pairadd(&request->packet->vps, vp);
1109                         return -1;
1110                 }
1111
1112                 /*
1113                  *      Parse the answer as an nthashhash.
1114                  *
1115                  *      ntlm_auth currently returns:
1116                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
1117                  */
1118                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
1119                         RDEBUG2("Invalid output from ntlm_auth: expecting NT_KEY");
1120                         return -1;
1121                 }
1122
1123                 /*
1124                  *      Check the length.  It should be at least 32,
1125                  *      with an LF at the end.
1126                  */
1127                 if (strlen(buffer + 8) < 32) {
1128                         RDEBUG2("Invalid output from ntlm_auth: NT_KEY has unexpected length");
1129                         return -1;
1130                 }
1131
1132                 /*
1133                  *      Update the NT hash hash, from the NT key.
1134                  */
1135                 if (fr_hex2bin(buffer + 8, nthashhash, 16) != 16) {
1136                         RDEBUG2("Invalid output from ntlm_auth: NT_KEY has non-hex values");
1137                         return -1;
1138                 }
1139         }
1140
1141         return 0;
1142 }
1143
1144
1145 /*
1146  *      Data for the hashes.
1147  */
1148 static const uint8_t SHSpad1[40] =
1149                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1150                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1151                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1152                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
1153
1154 static const uint8_t SHSpad2[40] =
1155                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1156                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1157                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
1158                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
1159
1160 static const uint8_t magic1[27] =
1161                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
1162                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
1163                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
1164
1165 static const uint8_t magic2[84] =
1166                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1167                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1168                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1169                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
1170                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
1171                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
1172                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1173                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1174                  0x6b, 0x65, 0x79, 0x2e };
1175
1176 static const uint8_t magic3[84] =
1177                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
1178                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
1179                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
1180                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
1181                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
1182                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
1183                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
1184                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
1185                  0x6b, 0x65, 0x79, 0x2e };
1186
1187
1188 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
1189                               uint8_t *masterkey)
1190 {
1191        uint8_t digest[20];
1192        fr_SHA1_CTX Context;
1193
1194        fr_SHA1Init(&Context);
1195        fr_SHA1Update(&Context,nt_hashhash,16);
1196        fr_SHA1Update(&Context,nt_response,24);
1197        fr_SHA1Update(&Context,magic1,27);
1198        fr_SHA1Final(digest,&Context);
1199
1200        memcpy(masterkey,digest,16);
1201 }
1202
1203
1204 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
1205                                        int keylen,int issend)
1206 {
1207        uint8_t digest[20];
1208        const uint8_t *s;
1209        fr_SHA1_CTX Context;
1210
1211        memset(digest,0,20);
1212
1213        if(issend) {
1214                s = magic3;
1215        } else {
1216                s = magic2;
1217        }
1218
1219        fr_SHA1Init(&Context);
1220        fr_SHA1Update(&Context,masterkey,16);
1221        fr_SHA1Update(&Context,SHSpad1,40);
1222        fr_SHA1Update(&Context,s,84);
1223        fr_SHA1Update(&Context,SHSpad2,40);
1224        fr_SHA1Final(digest,&Context);
1225
1226        memcpy(sesskey,digest,keylen);
1227 }
1228
1229
1230 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
1231                                    uint8_t *sendkey,uint8_t *recvkey)
1232 {
1233        uint8_t masterkey[16];
1234
1235        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
1236
1237        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
1238        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
1239 }
1240
1241 /*
1242  *      Generate MPPE keys.
1243  */
1244 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
1245                                    uint8_t *sendkey,uint8_t *recvkey)
1246 {
1247         uint8_t enckey1[16];
1248         uint8_t enckey2[16];
1249
1250         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
1251
1252         /*
1253          *      dictionary.microsoft defines these attributes as
1254          *      'encrypt=2'.  The functions in src/lib/radius.c will
1255          *      take care of encrypting/decrypting them as appropriate,
1256          *      so that we don't have to.
1257          */
1258         memcpy (sendkey, enckey1, 16);
1259         memcpy (recvkey, enckey2, 16);
1260 }
1261
1262
1263 /*
1264  *      mschap_authorize() - authorize user if we can authenticate
1265  *      it later. Add Auth-Type attribute if present in module
1266  *      configuration (usually Auth-Type must be "MS-CHAP")
1267  */
1268 static rlm_rcode_t mschap_authorize(void * instance, REQUEST *request)
1269 {
1270         rlm_mschap_t *inst = instance;
1271         VALUE_PAIR *challenge = NULL;
1272
1273         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1274         if (!challenge) {
1275                 return RLM_MODULE_NOOP;
1276         }
1277
1278         if (!pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1279             !pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY) &&
1280             !pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY)) {
1281                 RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password");
1282                 return RLM_MODULE_NOOP;
1283         }
1284
1285         if (pairfind(request->config_items, PW_AUTH_TYPE, 0, TAG_ANY)) {
1286                 RDEBUG2W("Auth-Type already set.  Not setting to MS-CHAP");
1287                 return RLM_MODULE_NOOP;
1288         }
1289
1290         RDEBUG2("Found MS-CHAP attributes.  Setting 'Auth-Type  = %s'", inst->xlat_name);
1291
1292         /*
1293          *      Set Auth-Type to MS-CHAP.  The authentication code
1294          *      will take care of turning clear-text passwords into
1295          *      NT/LM passwords.
1296          */
1297         if (!radius_pairmake(request, &request->config_items,
1298                              "Auth-Type", inst->auth_type, T_OP_EQ)) {
1299                 return RLM_MODULE_FAIL;
1300         }
1301
1302         return RLM_MODULE_OK;
1303 }
1304
1305 /*
1306  *      mschap_authenticate() - authenticate user based on given
1307  *      attributes and configuration.
1308  *      We will try to find out password in configuration
1309  *      or in configured passwd file.
1310  *      If one is found we will check paraneters given by NAS.
1311  *
1312  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
1313  *      one of:
1314  *              PAP:      PW_USER_PASSWORD or
1315  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
1316  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
1317  *      In case of password mismatch or locked account we MAY return
1318  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
1319  *      If MS-CHAP2 succeeds we MUST return
1320  *      PW_MSCHAP2_SUCCESS
1321  */
1322 static rlm_rcode_t mschap_authenticate(void * instance, REQUEST *request)
1323 {
1324 #define inst ((rlm_mschap_t *)instance)
1325         VALUE_PAIR *challenge = NULL;
1326         VALUE_PAIR *response = NULL;
1327         VALUE_PAIR *cpw = NULL;
1328         VALUE_PAIR *password = NULL;
1329         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
1330         VALUE_PAIR *username;
1331         uint8_t nthashhash[16];
1332         char msch2resp[42];
1333         char *username_string;
1334         int chap = 0;
1335         int             do_ntlm_auth;
1336
1337         /*
1338          *      If we have ntlm_auth configured, use it unless told
1339          *      otherwise
1340          */
1341         do_ntlm_auth = (inst->ntlm_auth != NULL);
1342
1343         /*
1344          *      If we have an ntlm_auth configuration, then we may
1345          *      want to suppress it.
1346          */
1347         if (do_ntlm_auth) {
1348                 VALUE_PAIR *vp = pairfind(request->config_items, PW_MS_CHAP_USE_NTLM_AUTH, 0, TAG_ANY);
1349                 if (vp) do_ntlm_auth = vp->vp_integer;
1350         }
1351
1352         /*
1353          *      Find the SMB-Account-Ctrl attribute, or the
1354          *      SMB-Account-Ctrl-Text attribute.
1355          */
1356         smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL, 0, TAG_ANY);
1357         if (!smb_ctrl) {
1358                 password = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL_TEXT, 0, TAG_ANY);
1359                 if (password) {
1360                         smb_ctrl = radius_pairmake(request,
1361                                                    &request->config_items,
1362                                                    "SMB-Account-CTRL", "0",
1363                                                    T_OP_SET);
1364                         if (smb_ctrl) {
1365                                 smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
1366                         }
1367                 }
1368         }
1369
1370         /*
1371          *      We're configured to do MS-CHAP authentication.
1372          *      and account control information exists.  Enforce it.
1373          */
1374         if (smb_ctrl) {
1375                 /*
1376                  *      Password is not required.
1377                  */
1378                 if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
1379                         RDEBUG2("SMB-Account-Ctrl says no password is required.");
1380                         return RLM_MODULE_OK;
1381                 }
1382         }
1383
1384         /*
1385          *      Decide how to get the passwords.
1386          */
1387         password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
1388
1389         /*
1390          *      We need an LM-Password.
1391          */
1392         lm_password = pairfind(request->config_items, PW_LM_PASSWORD, 0, TAG_ANY);
1393         if (lm_password) {
1394                 /*
1395                  *      Allow raw octets.
1396                  */
1397                 if ((lm_password->length == 16) ||
1398                     ((lm_password->length == 32) &&
1399                      (fr_hex2bin(lm_password->vp_strvalue,
1400                                  lm_password->vp_octets, 16) == 16))) {
1401                         RDEBUG2("Found LM-Password");
1402                         lm_password->length = 16;
1403
1404                 } else {
1405                         radlog_request(L_ERR, 0, request, "Invalid LM-Password");
1406                         lm_password = NULL;
1407                 }
1408
1409         } else if (!password) {
1410                 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password.");
1411
1412         } else {                /* there is a configured Cleartext-Password */
1413                 lm_password = radius_pairmake(request, &request->config_items,
1414                                               "LM-Password", "", T_OP_EQ);
1415                 if (!lm_password) {
1416                         radlog_request(L_ERR, 0, request, "No memory");
1417                 } else {
1418                         smbdes_lmpwdhash(password->vp_strvalue,
1419                                          lm_password->vp_octets);
1420                         lm_password->length = 16;
1421                 }
1422         }
1423
1424         /*
1425          *      We need an NT-Password.
1426          */
1427         nt_password = pairfind(request->config_items, PW_NT_PASSWORD, 0, TAG_ANY);
1428         if (nt_password) {
1429                 if ((nt_password->length == 16) ||
1430                     ((nt_password->length == 32) &&
1431                      (fr_hex2bin(nt_password->vp_strvalue,
1432                                  nt_password->vp_octets, 16) == 16))) {
1433                         RDEBUG2("Found NT-Password");
1434                         nt_password->length = 16;
1435
1436                 } else {
1437                         radlog_request(L_ERR, 0, request, "Invalid NT-Password");
1438                         nt_password = NULL;
1439                 }
1440         } else if (!password) {
1441                 if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password.");
1442
1443         } else {                /* there is a configured Cleartext-Password */
1444                 nt_password = radius_pairmake(request, &request->config_items,
1445                                               "NT-Password", "", T_OP_EQ);
1446                 if (!nt_password) {
1447                         radlog_request(L_ERR, 0, request, "No memory");
1448                         return RLM_MODULE_FAIL;
1449                 } else {
1450                         mschap_ntpwdhash(nt_password->vp_octets,
1451                                   password->vp_strvalue);
1452                         nt_password->length = 16;
1453                 }
1454         }
1455
1456         cpw = pairfind(request->packet->vps, PW_MSCHAP2_CPW, VENDORPEC_MICROSOFT, TAG_ANY);
1457         if (cpw) {
1458                 /*
1459                  * mschap2 password change request
1460                  * we cheat - first decode and execute the passchange
1461                  * we then extract the response, add it into the request
1462                  * then jump into mschap2 auth with the chal/resp
1463                  */
1464                 uint8_t new_nt_encrypted[516], old_nt_encrypted[16];
1465                 VALUE_PAIR *nt_enc=NULL;
1466                 int seq, new_nt_enc_len=0;
1467
1468                 RDEBUG("MS-CHAPv2 password change request received");
1469
1470                 if (cpw->length != 68) {
1471                         RDEBUG2("MS-CHAP2-CPW has the wrong format - length %d!=68", cpw->length);
1472                         return RLM_MODULE_INVALID;
1473                 } else if (cpw->vp_octets[0]!=7) {
1474                         RDEBUG2("MS-CHAP2-CPW has the wrong format - code %d!=7", cpw->vp_octets[0]);
1475                         return RLM_MODULE_INVALID;
1476                 }
1477
1478                 /*
1479                  * look for the new (encrypted) password
1480                  * bah stupid composite attributes
1481                  * we're expecting 3 attributes with the leading bytes
1482                  * 06:<mschapid>:00:01:<1st chunk>
1483                  * 06:<mschapid>:00:02:<2nd chunk>
1484                  * 06:<mschapid>:00:03:<3rd chunk>
1485                  */
1486                 for (seq=1;seq<4;seq++) {
1487                         int found=0;
1488                         for (nt_enc=request->packet->vps; nt_enc; nt_enc=nt_enc->next) {
1489                                 if (nt_enc->da->vendor != VENDORPEC_MICROSOFT)
1490                                         continue;
1491
1492                                 if (nt_enc->da->attr != PW_MSCHAP_NT_ENC_PW)
1493                                         continue;
1494
1495                                 if (nt_enc->vp_octets[0] != 6) {
1496                                         RDEBUG2("MS-CHAP-NT-Enc-PW with invalid format");
1497                                         return RLM_MODULE_INVALID;
1498                                 }
1499                                 if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) {
1500                                         found = 1;
1501                                         break;
1502                                 }
1503                         }
1504
1505                         if (!found) {
1506                                 RDEBUG2("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq);
1507                                 return RLM_MODULE_INVALID;
1508                         }
1509
1510                         /*
1511                          * copy the data into the buffer
1512                          */
1513                         memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->length - 4);
1514                         new_nt_enc_len += nt_enc->length - 4;
1515                 }
1516                 if (new_nt_enc_len != 516) {
1517                         RDEBUG2("Unpacked MS-CHAP-NT-Enc-PW length != 516");
1518                         return RLM_MODULE_INVALID;
1519                 }
1520
1521                 /*
1522                  * RFC 2548 is confusing here
1523                  * it claims:
1524                  *
1525                  * 1 byte code
1526                  * 1 byte ident
1527                  * 16 octets - old hash encrypted with new hash
1528                  * 24 octets - peer challenge
1529                  *   this is actually:
1530                  *   16 octets - peer challenge
1531                  *    8 octets - reserved
1532                  * 24 octets - nt response
1533                  * 2 octets  - flags (ignored)
1534                  */
1535
1536                 memcpy(old_nt_encrypted, cpw->vp_octets+2, sizeof(old_nt_encrypted));
1537
1538                 RDEBUG2("Password change payload valid");
1539
1540                 /* perform the actual password change */
1541                 if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, do_ntlm_auth) < 0) {
1542                         char buffer[128];
1543
1544                         RDEBUG("Password change failed");
1545
1546                         snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed");
1547                         mschap_add_reply(request, &request->reply->vps,
1548                                         cpw->vp_octets[1], "MS-CHAP-Error",
1549                                         buffer, strlen(buffer));
1550                         return RLM_MODULE_REJECT;
1551                 }
1552                 RDEBUG("Password change successful");
1553
1554                 /*
1555                  * Clear any expiry bit so the user can now login;
1556                  * obviously the password change action will need
1557                  * to have cleared this bit in the config/SQL/wherever
1558                  */
1559                 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1560                         RDEBUG("clearing expiry bit in SMB-Acct-Ctrl to allow authentication");
1561                         smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED;
1562                 } else {
1563                 }
1564
1565                 /*
1566                  * extract the challenge & response from the end of the password
1567                  * change, add them into the request and then continue with
1568                  * the authentication
1569                  */
1570
1571                 response = radius_paircreate(request, &request->packet->vps,
1572                                              PW_MSCHAP2_RESPONSE,
1573                                              VENDORPEC_MICROSOFT);
1574                 response->length = 50;
1575                 /* ident & flags */
1576                 response->vp_octets[0] = cpw->vp_octets[1];
1577                 response->vp_octets[1] = 0;
1578                 /* peer challenge and client NT response */
1579                 memcpy(response->vp_octets+2, cpw->vp_octets + 18, 48);
1580         }
1581
1582         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY);
1583         if (!challenge) {
1584                 RDEBUGE("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1585                 return RLM_MODULE_REJECT;
1586         }
1587
1588         /*
1589          *      We also require an MS-CHAP-Response.
1590          */
1591         response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY);
1592
1593         /*
1594          *      MS-CHAP-Response, means MS-CHAPv1
1595          */
1596         if (response) {
1597                 int offset;
1598
1599                 /*
1600                  *      MS-CHAPv1 challenges are 8 octets.
1601                  */
1602                 if (challenge->length < 8) {
1603                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1604                         return RLM_MODULE_INVALID;
1605                 }
1606
1607                 /*
1608                  *      Responses are 50 octets.
1609                  */
1610                 if (response->length < 50) {
1611                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1612                         return RLM_MODULE_INVALID;
1613                 }
1614
1615                 /*
1616                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1617                  *      response
1618                  */
1619                 if (response->vp_octets[1] & 0x01) {
1620                         RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
1621                         password = nt_password;
1622                         offset = 26;
1623                 } else {
1624                         RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
1625                         password = lm_password;
1626                         offset = 2;
1627                 }
1628
1629                 /*
1630                  *      Do the MS-CHAP authentication.
1631                  */
1632                 if (do_mschap(inst, request, password, challenge->vp_octets,
1633                               response->vp_octets + offset, nthashhash,
1634                               do_ntlm_auth) < 0) {
1635                         RDEBUG2("MS-CHAP-Response is incorrect.");
1636                         goto do_error;
1637                 }
1638
1639                 chap = 1;
1640
1641         } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT, TAG_ANY)) != NULL) {
1642                 int mschap_result;
1643                 uint8_t mschapv1_challenge[16];
1644                 VALUE_PAIR *name_attr, *response_name;
1645
1646                 /*
1647                  *      MS-CHAPv2 challenges are 16 octets.
1648                  */
1649                 if (challenge->length < 16) {
1650                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
1651                         return RLM_MODULE_INVALID;
1652                 }
1653
1654                 /*
1655                  *      Responses are 50 octets.
1656                  */
1657                 if (response->length < 50) {
1658                         radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
1659                         return RLM_MODULE_INVALID;
1660                 }
1661
1662                 /*
1663                  *      We also require a User-Name
1664                  */
1665                 username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
1666                 if (!username) {
1667                         radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
1668                         return RLM_MODULE_INVALID;
1669                 }
1670
1671                 /*
1672                  *      Check for MS-CHAP-User-Name and if found, use it
1673                  *      to construct the MSCHAPv1 challenge.  This is
1674                  *      set by rlm_eap_mschap to the MS-CHAP Response
1675                  *      packet Name field.
1676                  *
1677                  *      We prefer this to the User-Name in the
1678                  *      packet.
1679                  */
1680                 response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME, 0, TAG_ANY);
1681                 if (response_name) {
1682                         name_attr = response_name;
1683                 } else {
1684                         name_attr = username;
1685                 }
1686                 
1687                 /*
1688                  *      with_ntdomain_hack moved here, too.
1689                  */
1690                 if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
1691                         if (inst->with_ntdomain_hack) {
1692                                 username_string++;
1693                         } else {
1694                                 RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1695                                 username_string = name_attr->vp_strvalue;
1696                         }
1697                 } else {
1698                         username_string = name_attr->vp_strvalue;
1699                 }
1700                 
1701                 if (response_name &&
1702                     ((username->length != response_name->length) ||
1703                      (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->length) != 0))) {
1704                         RDEBUGE("User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", username->vp_strvalue, response_name->vp_strvalue);
1705                         return RLM_MODULE_REJECT;
1706                 }
1707
1708 #ifdef __APPLE__
1709                 /*
1710                  *  No "known good" NT-Password attribute.  Try to do
1711                  *  OpenDirectory authentication.
1712                  *
1713                  *  If OD determines the user is an AD user it will return noop, which
1714                  *  indicates the auth process should continue directly to AD.
1715                  *  Otherwise OD will determine auth success/fail.
1716                  */
1717                 if (!nt_password && inst->open_directory) {
1718                         RDEBUG2("No NT-Password configured. Trying OpenDirectory Authentication.");
1719                         int odStatus = od_mschap_auth(request, challenge, username);
1720                         if (odStatus != RLM_MODULE_NOOP) {
1721                                 return odStatus;
1722                         }
1723                 }
1724 #endif
1725                 /*
1726                  *      The old "mschapv2" function has been moved to
1727                  *      here.
1728                  *
1729                  *      MS-CHAPv2 takes some additional data to create an
1730                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1731                  */
1732                 RDEBUG2("Creating challenge hash with username: %s",
1733                         username_string);
1734                 mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
1735                                challenge->vp_octets, /* our challenge */
1736                                username_string, /* user name */
1737                                mschapv1_challenge); /* resulting challenge */
1738
1739                 RDEBUG2("Client is using MS-CHAPv2 for %s, we need NT-Password",
1740                        username_string);
1741
1742                 mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge,
1743                                 response->vp_octets + 26, nthashhash,
1744                                 do_ntlm_auth);
1745                 if (mschap_result == -648)
1746                         goto password_expired;
1747
1748                 if (mschap_result < 0) {
1749                         int i;
1750                         char buffer[128];
1751
1752                         RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");
1753
1754                 do_error:
1755                         snprintf(buffer, sizeof(buffer), "E=691 R=%d",
1756                                  inst->allow_retry);
1757
1758                         if (inst->retry_msg) {
1759                                 snprintf(buffer + 9, sizeof(buffer) - 9, " C=");
1760                                 for (i = 0; i < 16; i++) {
1761                                         snprintf(buffer + 12 + i*2,
1762                                                  sizeof(buffer) - 12 - i*2, "%02x",
1763                                                  fr_rand() & 0xff);
1764                                 }
1765                                 snprintf(buffer + 45, sizeof(buffer) - 45,
1766                                          " V=3 M=%s", inst->retry_msg);
1767                         }
1768                         mschap_add_reply(request, &request->reply->vps,
1769                                          *response->vp_octets, "MS-CHAP-Error",
1770                                          buffer, strlen(buffer));
1771                         return RLM_MODULE_REJECT;
1772                 }
1773
1774                 if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) {
1775                         /*
1776                          * if the password is correct and it has expired
1777                          * we can permit password changes (only in MS-CHAPv2)
1778                          */
1779                         char newchal[33], buffer[128];
1780                         int i;
1781                 password_expired:
1782
1783                         for (i = 0; i < 16; i++)
1784                                 snprintf(newchal + i*2, 3, "%02x", fr_rand() & 0xff);
1785
1786                         snprintf(buffer, sizeof(buffer), "E=648 R=0 C=%s V=3 M=Password Expired", newchal);
1787
1788                         mschap_add_reply(request, &request->reply->vps,
1789                                          *response->vp_octets, "MS-CHAP-Error",
1790                                          buffer, strlen(buffer));
1791                         return RLM_MODULE_REJECT;
1792                 }
1793
1794                 mschap_auth_response(username_string, /* without the domain */
1795                               nthashhash, /* nt-hash-hash */
1796                               response->vp_octets + 26, /* peer response */
1797                               response->vp_octets + 2, /* peer challenge */
1798                               challenge->vp_octets, /* our challenge */
1799                               msch2resp); /* calculated MPPE key */
1800                 mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
1801                                  "MS-CHAP2-Success", msch2resp, 42);
1802                 chap = 2;
1803
1804         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1805                 RDEBUGE("You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
1806                 return RLM_MODULE_INVALID;
1807         }
1808
1809         /*
1810          *      We have a CHAP response, but the account may be
1811          *      disabled.  Reject the user with the same error code
1812          *      we use when their password is invalid.
1813          */
1814         if (smb_ctrl) {
1815                 /*
1816                  *      Account is disabled.
1817                  *
1818                  *      They're found, but they don't exist, so we
1819                  *      return 'not found'.
1820                  */
1821                 if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
1822                     ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)) {
1823                         RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal or workstatin trust account.");
1824                         mschap_add_reply(request, &request->reply->vps,
1825                                           *response->vp_octets,
1826                                           "MS-CHAP-Error", "E=691 R=1", 9);
1827                         return RLM_MODULE_NOTFOUND;
1828                 }
1829
1830                 /*
1831                  *      User is locked out.
1832                  */
1833                 if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
1834                         RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
1835                         mschap_add_reply(request, &request->reply->vps,
1836                                           *response->vp_octets,
1837                                           "MS-CHAP-Error", "E=647 R=0", 9);
1838                         return RLM_MODULE_USERLOCK;
1839                 }
1840         }
1841
1842         /* now create MPPE attributes */
1843         if (inst->use_mppe) {
1844                 uint8_t mppe_sendkey[34];
1845                 uint8_t mppe_recvkey[34];
1846
1847                 if (chap == 1){
1848                         RDEBUG2("adding MS-CHAPv1 MPPE keys");
1849                         memset(mppe_sendkey, 0, 32);
1850                         if (lm_password) {
1851                                 memcpy(mppe_sendkey, lm_password->vp_octets, 8);
1852                         }
1853
1854                         /*
1855                          *      According to RFC 2548 we
1856                          *      should send NT hash.  But in
1857                          *      practice it doesn't work.
1858                          *      Instead, we should send nthashhash
1859                          *
1860                          *      This is an error on RFC 2548.
1861                          */
1862                         /*
1863                          *      do_mschap cares to zero nthashhash if NT hash
1864                          *      is not available.
1865                          */
1866                         memcpy(mppe_sendkey + 8,
1867                                nthashhash, 16);
1868                         mppe_add_reply(request,
1869                                        "MS-CHAP-MPPE-Keys",
1870                                        mppe_sendkey, 32);
1871                 } else if (chap == 2) {
1872                         RDEBUG2("adding MS-CHAPv2 MPPE keys");
1873                         mppe_chap2_gen_keys128(nthashhash,
1874                                                response->vp_octets + 26,
1875                                                mppe_sendkey, mppe_recvkey);
1876
1877                         mppe_add_reply(request,
1878                                        "MS-MPPE-Recv-Key",
1879                                        mppe_recvkey, 16);
1880                         mppe_add_reply(request,
1881                                        "MS-MPPE-Send-Key",
1882                                        mppe_sendkey, 16);
1883
1884                 }
1885                 radius_pairmake(request, &request->reply->vps,
1886                                 "MS-MPPE-Encryption-Policy",
1887                                 (inst->require_encryption)? "0x00000002":"0x00000001",
1888                                 T_OP_EQ);
1889                 radius_pairmake(request, &request->reply->vps,
1890                                 "MS-MPPE-Encryption-Types",
1891                                 (inst->require_strong)? "0x00000004":"0x00000006",
1892                                 T_OP_EQ);
1893         } /* else we weren't asked to use MPPE */
1894
1895         return RLM_MODULE_OK;
1896 #undef inst
1897 }
1898
1899 module_t rlm_mschap = {
1900         RLM_MODULE_INIT,
1901         "MS-CHAP",
1902         RLM_TYPE_THREAD_SAFE | RLM_TYPE_HUP_SAFE,               /* type */
1903         mschap_instantiate,             /* instantiation */
1904         mschap_detach,          /* detach */
1905         {
1906                 mschap_authenticate,    /* authenticate */
1907                 mschap_authorize,       /* authorize */
1908                 NULL,                   /* pre-accounting */
1909                 NULL,                   /* accounting */
1910                 NULL,                   /* checksimul */
1911                 NULL,                   /* pre-proxy */
1912                 NULL,                   /* post-proxy */
1913                 NULL                    /* post-auth */
1914         },
1915 };