Response from ntlm_auth is 33 characters, including a terminating
[freeradius.git] / src / modules / rlm_mschap / rlm_mschap.c
1 /*
2  * rlm_mschap.c
3  *
4  * Version:     $Id$
5  *
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.
10  *
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.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Copyright 2000,2001  The FreeRADIUS server project
21  */
22
23
24 /*
25  *  mschap.c    MS-CHAP module
26  *
27  *  This implements MS-CHAP, as described in RFC 2548
28  *
29  *  http://www.freeradius.org/rfc/rfc2548.txt
30  *
31  */
32
33 /*
34  *  If you have any questions on NTLM (Samba) passwords
35  *  support, LM authentication and MS-CHAP v2 support
36  *  please contact
37  *
38  *  Vladimir Dubrovin   vlad@sandy.ru
39  *  aka
40  *  ZARAZA              3APA3A@security.nnov.ru
41  */
42
43 /*  MPPE support from Takahiro Wagatsuma <waga@sic.shibaura-it.ac.jp> */
44
45 #include        "autoconf.h"
46 #include        "libradius.h"
47
48 #include        <stdio.h>
49 #include        <stdlib.h>
50 #include        <string.h>
51 #include        <ctype.h>
52
53 #include        "radiusd.h"
54 #include        "modules.h"
55
56 #include        "md4.h"
57 #include        "md5.h"
58 #include        "sha1.h"
59 #include        "rad_assert.h"
60
61 static const char rcsid[] = "$Id$";
62
63 static const char *letters = "0123456789ABCDEF";
64
65 /*
66  *      hex2bin converts hexadecimal strings into binary
67  */
68 static int hex2bin (const char *szHex, unsigned char* szBin, int len)
69 {
70         char * c1, * c2;
71         int i;
72
73         for (i = 0; i < len; i++) {
74                 if( !(c1 = memchr(letters, toupper((int) szHex[i << 1]), 16)) ||
75                     !(c2 = memchr(letters, toupper((int) szHex[(i << 1) + 1]), 16)))
76                      break;
77                  szBin[i] = ((c1-letters)<<4) + (c2-letters);
78         }
79         return i;
80 }
81
82 /*
83  *      bin2hex creates hexadecimal presentation
84  *      of binary data
85  */
86 static void bin2hex (const unsigned char *szBin, char *szHex, int len)
87 {
88         int i;
89         for (i = 0; i < len; i++) {
90                 szHex[i<<1] = letters[szBin[i] >> 4];
91                 szHex[(i<<1) + 1] = letters[szBin[i] & 0x0F];
92         }
93 }
94
95
96 /* Allowable account control bits */
97 #define ACB_DISABLED   0x0001  /* 1 = User account disabled */
98 #define ACB_HOMDIRREQ  0x0002  /* 1 = Home directory required */
99 #define ACB_PWNOTREQ   0x0004  /* 1 = User password not required */
100 #define ACB_TEMPDUP    0x0008  /* 1 = Temporary duplicate account */
101 #define ACB_NORMAL     0x0010  /* 1 = Normal user account */
102 #define ACB_MNS        0x0020  /* 1 = MNS logon user account */
103 #define ACB_DOMTRUST   0x0040  /* 1 = Interdomain trust account */
104 #define ACB_WSTRUST    0x0080  /* 1 = Workstation trust account */
105 #define ACB_SVRTRUST   0x0100  /* 1 = Server trust account */
106 #define ACB_PWNOEXP    0x0200  /* 1 = User password does not expire */
107 #define ACB_AUTOLOCK   0x0400  /* 1 = Account auto locked */
108
109 static int pdb_decode_acct_ctrl(const char *p)
110 {
111         int acct_ctrl = 0;
112         int finished = 0;
113
114         /*
115          * Check if the account type bits have been encoded after the
116          * NT password (in the form [NDHTUWSLXI]).
117          */
118
119         if (*p != '[') return 0;
120
121         for (p++; *p && !finished; p++) {
122                 switch (*p) {
123                         case 'N': /* 'N'o password. */
124                           acct_ctrl |= ACB_PWNOTREQ;
125                           break;
126
127                         case 'D':  /* 'D'isabled. */
128                           acct_ctrl |= ACB_DISABLED ;
129                           break;
130
131                         case 'H':  /* 'H'omedir required. */
132                           acct_ctrl |= ACB_HOMDIRREQ;
133                           break;
134
135                         case 'T': /* 'T'emp account. */
136                           acct_ctrl |= ACB_TEMPDUP;
137                           break;
138
139                         case 'U': /* 'U'ser account (normal). */
140                           acct_ctrl |= ACB_NORMAL;
141                           break;
142
143                         case 'M': /* 'M'NS logon user account. What is this? */
144                           acct_ctrl |= ACB_MNS;
145                           break;
146
147                         case 'W': /* 'W'orkstation account. */
148                           acct_ctrl |= ACB_WSTRUST;
149                           break;
150
151                         case 'S': /* 'S'erver account. */
152                           acct_ctrl |= ACB_SVRTRUST;
153                           break;
154
155                         case 'L': /* 'L'ocked account. */
156                           acct_ctrl |= ACB_AUTOLOCK;
157                           break;
158
159                         case 'X': /* No 'X'piry on password */
160                           acct_ctrl |= ACB_PWNOEXP;
161                           break;
162
163                         case 'I': /* 'I'nterdomain trust account. */
164                           acct_ctrl |= ACB_DOMTRUST;
165                           break;
166
167                         case ' ': /* ignore spaces */
168                           break;
169
170                         case ':':
171                         case '\n':
172                         case '\0':
173                         case ']':
174                         default:
175                           finished = 1;
176                           break;
177                 }
178         }
179
180         return acct_ctrl;
181 }
182
183
184 /*
185  *      ntpwdhash converts Unicode password to 16-byte NT hash
186  *      with MD4
187  */
188 static void ntpwdhash (char *szHash, const char *szPassword)
189 {
190         char szUnicodePass[513];
191         char nPasswordLen;
192         int i;
193
194         /*
195          *      NT passwords are unicode.  Convert plain text password
196          *      to unicode by inserting a zero every other byte
197          */
198         nPasswordLen = strlen(szPassword);
199         for (i = 0; i < nPasswordLen; i++) {
200                 szUnicodePass[i << 1] = szPassword[i];
201                 szUnicodePass[(i << 1) + 1] = 0;
202         }
203
204         /* Encrypt Unicode password to a 16-byte MD4 hash */
205         md4_calc(szHash, szUnicodePass, (nPasswordLen<<1) );
206 }
207
208
209 /*
210  *      challenge_hash() is used by mschap2() and auth_response()
211  *      implements RFC2759 ChallengeHash()
212  *      generates 64 bit challenge
213  */
214 static void challenge_hash( const char *peer_challenge,
215                             const char *auth_challenge,
216                             const char *user_name, char *challenge )
217 {
218         SHA1_CTX Context;
219         char hash[20];
220
221         SHA1Init(&Context);
222         SHA1Update(&Context, peer_challenge, 16);
223         SHA1Update(&Context, auth_challenge, 16);
224         SHA1Update(&Context, user_name, strlen(user_name));
225         SHA1Final(hash, &Context);
226         memcpy(challenge, hash, 8);
227 }
228
229 /*
230  *      auth_response() generates MS-CHAP v2 SUCCESS response
231  *      according to RFC 2759 GenerateAuthenticatorResponse()
232  *      returns 42-octet response string
233  */
234 static void auth_response(const char *username,
235                           const char *nt_hash_hash,
236                           char *ntresponse,
237                           char *peer_challenge, char *auth_challenge,
238                           char *response)
239 {
240         SHA1_CTX Context;
241         const char magic1[39] =
242         {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
243          0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
244          0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
245          0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
246
247         const char magic2[41] =
248         {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
249          0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
250          0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
251          0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
252          0x6E};
253
254         char challenge[8];
255         char digest[20];
256
257         SHA1Init(&Context);
258         SHA1Update(&Context, nt_hash_hash, 16);
259         SHA1Update(&Context, ntresponse, 24);
260         SHA1Update(&Context, magic1, 39);
261         SHA1Final(digest, &Context);
262         challenge_hash(peer_challenge, auth_challenge, username, challenge);
263         SHA1Init(&Context);
264         SHA1Update(&Context, digest, 20);
265         SHA1Update(&Context, challenge, 8);
266         SHA1Update(&Context, magic2, 41);
267         SHA1Final(digest, &Context);
268
269         /*
270          *      Encode the value of 'Digest' as "S=" followed by
271          *      40 ASCII hexadecimal digits and return it in
272          *      AuthenticatorResponse.
273          *      For example,
274          *      "S=0123456789ABCDEF0123456789ABCDEF01234567"
275          */
276         response[0] = 'S';
277         response[1] = '=';
278         bin2hex(digest, response + 2, 20);
279 }
280
281
282 typedef struct rlm_mschap_t {
283         int use_mppe;
284         int require_encryption;
285         int require_strong;
286         int with_ntdomain_hack; /* this should be in another module */
287         char *passwd_file;
288         char *xlat_name;
289         char *auth_type;        /* I don't think this is needed... */
290         char *ntlm_auth;
291 } rlm_mschap_t;
292
293
294 /*
295  *      Does dynamic translation of strings.
296  *
297  *      Pulls NT-Response, LM-Response, or Challenge from MSCHAP
298  *      attributes.
299  */
300 static int mschap_xlat(void *instance, REQUEST *request,
301                        char *fmt, char *out, size_t outlen,
302                        RADIUS_ESCAPE_STRING func)
303 {
304         int             i, data_len;
305         uint8_t         *data = NULL;
306         uint8_t         buffer[8];
307         VALUE_PAIR      *user_name;
308         VALUE_PAIR      *chap_challenge, *response;
309         rlm_mschap_t    *inst = instance;
310
311         chap_challenge = response = NULL;
312
313         func = func;            /* -Wunused */
314
315         /*
316          *      Challenge means MS-CHAPv1 challenge, or
317          *      hash of MS-CHAPv2 challenge, and peer challenge.
318          */
319         if (strcasecmp(fmt, "Challenge") == 0) {
320                 chap_challenge = pairfind(request->packet->vps,
321                                           PW_MSCHAP_CHALLENGE);
322                 if (!chap_challenge) {
323                         DEBUG2("  rlm_mschap: No MS-CHAP-Challenge in the request.");
324                         return 0;
325                 }
326
327                 /*
328                  *      MS-CHAP-Challenges are 8 octets,
329                  *      for MS-CHAPv2
330                  */
331                 if (chap_challenge->length == 8) {
332                         DEBUG2(" mschap1: %02x", chap_challenge->strvalue[0]);
333                         data = chap_challenge->strvalue;
334                         data_len = 8;
335
336                         /*
337                          *      MS-CHAP-Challenges are 16 octets,
338                          *      for MS-CHAPv2.
339                          */
340                 } else if (chap_challenge->length == 16) {
341                         char *username_string;
342
343                         DEBUG2(" mschap2: %02x", chap_challenge->strvalue[0]);
344                         response = pairfind(request->packet->vps,
345                                             PW_MSCHAP2_RESPONSE);
346                         if (!response) {
347                                 DEBUG2("  rlm_mschap: MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
348                                 return 0;
349                         }
350
351                         /*
352                          *      Responses are 50 octets.
353                          */
354                         if (response->length < 50) {
355                                 radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
356                                 return 0;
357                         }
358
359                         user_name = pairfind(request->packet->vps,
360                                              PW_USER_NAME);
361                         if (!user_name) {
362                                 DEBUG2("  rlm_mschap: User-Name is required to calculateMS-CHAPv1 Challenge.");
363                                 return 0;
364                         }
365
366                         /*
367                          *      with_ntdomain_hack moved here, too.
368                          */
369                         if ((username_string = strchr(user_name->strvalue, '\\')) != NULL) {
370                                 if (inst->with_ntdomain_hack) {
371                                         username_string++;
372                                 } else {
373                                         DEBUG2("  rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
374                                         username_string = user_name->strvalue;
375                                 }
376                         } else {
377                                 username_string = user_name->strvalue;
378                         }
379
380                         /*
381                          *      Get the MS-CHAPv1 challenge
382                          *      from the MS-CHAPv2 peer challenge,
383                          *      our challenge, and the user name.
384                          */
385                         challenge_hash(response->strvalue + 2,
386                                        chap_challenge->strvalue,
387                                        user_name->strvalue, buffer);
388                         data = buffer;
389                         data_len = 8;
390                 } else {
391                         DEBUG2("  rlm_mschap: Invalid MS-CHAP challenge length");
392                         return 0;
393                 }
394                 
395                 /*
396                  *      Get the MS-CHAPv1 response, or the MS-CHAPv2
397                  *      response.
398                  */
399         } else if (strcasecmp(fmt, "NT-Response") == 0) {
400                 response = pairfind(request->packet->vps,
401                                     PW_MSCHAP_RESPONSE);
402                 if (!response) response = pairfind(request->packet->vps,
403                                                    PW_MSCHAP2_RESPONSE);
404                 if (!response) {
405                         DEBUG2("  rlm_mschap: No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
406                         return 0;
407                 }
408
409                 /*
410                  *      For MS-CHAPv1, the NT-Response exists only
411                  *      if the second octet says so.
412                  */
413                 if ((response->attribute == PW_MSCHAP_RESPONSE) &&
414                     ((response->strvalue[1] & 0x01) == 0)) {
415                         DEBUG2("  rlm_mschap: No NT-Response in MS-CHAP-Response");
416                         return 0;
417                 }
418
419                 /*
420                  *      MS-CHAP-Response and MS-CHAP2-Response have
421                  *      the NT-Response at the same offset, and are
422                  *      the same length.
423                  */
424                 data = response->strvalue + 26;
425                 data_len = 24;
426                 
427                 /*
428                  *      LM-Response is deprecated, and exists only
429                  *      in MS-CHAPv1, and not often there.
430                  */
431         } else if (strcasecmp(fmt, "LM-Response") == 0) {
432                 response = pairfind(request->packet->vps,
433                                     PW_MSCHAP_RESPONSE);
434                 if (!response) {
435                         DEBUG2("  rlm_mschap: No MS-CHAP-Response was found in the request.");
436                         return 0;
437                 }
438
439                 /*
440                  *      For MS-CHAPv1, the NT-Response exists only
441                  *      if the second octet says so.
442                  */
443                 if ((response->strvalue[1] & 0x01) != 0) {
444                         DEBUG2("  rlm_mschap: No LM-Response in MS-CHAP-Response");
445                         return 0;
446                 }
447                 data = response->strvalue + 2;
448                 data_len = 24;
449
450                 /*
451                  *      Pull the NT-Domain out of the User-Name, if it exists.
452                  */
453         } else if (strcasecmp(fmt, "NT-Domain") == 0) {
454                 char *p;
455
456                 user_name = pairfind(request->packet->vps, PW_USER_NAME);
457                 if (!user_name) {
458                         DEBUG2("  rlm_mschap: No User-Name was found in the request.");
459                         return 0;
460                 }
461                 
462                 p = strchr(user_name->strvalue, '\\');
463                 if (!p) {
464                         DEBUG2("  rlm_mschap: No NT-Domain was found in the User-Name.");
465                         return 0;
466                 }
467
468                 /*
469                  *      Hack.  This is simpler than the alternatives.
470                  */
471                 *p = '\0';
472                 strNcpy(out, user_name->strvalue, outlen);
473                 *p = '\\';
474
475                 return strlen(out);
476
477                 /*
478                  *      Pull the User-Name out of the User-Name...
479                  */
480         } else if (strcasecmp(fmt, "User-Name") == 0) {
481                 char *p;
482
483                 user_name = pairfind(request->packet->vps, PW_USER_NAME);
484                 if (!user_name) {
485                         DEBUG2("  rlm_mschap: No User-Name was found in the request.");
486                         return 0;
487                 }
488                 
489                 p = strchr(user_name->strvalue, '\\');
490                 if (p) {
491                         p++;    /* skip the backslash */
492                 } else {
493                         p = user_name->strvalue; /* use the whole User-Name */
494                 }
495
496                 strNcpy(out, p, outlen);
497                 return strlen(out);
498
499         } else {
500                 DEBUG2("  rlm_mschap: Unknown expansion string \"%s\"",
501                        fmt);
502                 return 0;
503         }
504
505         if (outlen == 0) return 0; /* nowhere to go, don't do anything */
506
507         /*
508          *      Didn't set anything: this is bad.
509          */
510         if (!data) {
511                 DEBUG2("  rlm_mschap: Failed to do anything intelligent");
512                 return 0;
513         }
514
515         /*
516          *      Check the output length.
517          */
518         if (outlen < ((data_len * 2) + 1)) {
519                 data_len = (outlen - 1) / 2;
520         }
521
522         /*
523          *      
524          */
525         for (i = 0; i < data_len; i++) {
526                 sprintf(out + (2 * i), "%02x", data[i]);
527         }
528         out[data_len * 2] = '\0';
529         
530         return data_len * 2;
531 }
532
533
534 static CONF_PARSER module_config[] = {
535         /*
536          *      Cache the password by default.
537          */
538         { "use_mppe",    PW_TYPE_BOOLEAN,
539           offsetof(rlm_mschap_t,use_mppe), NULL, "yes" },
540         { "require_encryption",    PW_TYPE_BOOLEAN,
541           offsetof(rlm_mschap_t,require_encryption), NULL, "no" },
542         { "require_strong",    PW_TYPE_BOOLEAN,
543           offsetof(rlm_mschap_t,require_strong), NULL, "no" },
544         { "with_ntdomain_hack",     PW_TYPE_BOOLEAN,
545           offsetof(rlm_mschap_t,with_ntdomain_hack), NULL, "no" },
546         { "passwd",   PW_TYPE_STRING_PTR,
547           offsetof(rlm_mschap_t, passwd_file), NULL,  NULL },
548         { "authtype",   PW_TYPE_STRING_PTR,
549           offsetof(rlm_mschap_t, auth_type), NULL,  NULL },
550         { "ntlm_auth",   PW_TYPE_STRING_PTR,
551           offsetof(rlm_mschap_t, ntlm_auth), NULL,  NULL },
552
553         { NULL, -1, 0, NULL, NULL }             /* end the list */
554 };
555
556 /*
557  *      deinstantiate module, free all memory allocated during
558  *      mschap_instantiate()
559  */
560 static int mschap_detach(void *instance){
561 #define inst ((rlm_mschap_t *)instance)
562         if (inst->passwd_file) free(inst->passwd_file);
563         if (inst->auth_type) free(inst->auth_type);
564         if (inst->ntlm_auth) free(inst->ntlm_auth);
565         if (inst->xlat_name) {
566                 xlat_unregister(inst->xlat_name, mschap_xlat);
567                 free(inst->xlat_name);
568         }
569         free(instance);
570         return 0;
571 #undef inst
572 }
573
574 /*
575  *      Create instance for our module. Allocate space for
576  *      instance structure and read configuration parameters
577  */
578 static int mschap_instantiate(CONF_SECTION *conf, void **instance)
579 {
580         const char *xlat_name;
581         rlm_mschap_t *inst;
582
583         inst = *instance = rad_malloc(sizeof(*inst));
584         if (!inst) {
585                 return -1;
586         }
587         memset(inst, 0, sizeof(*inst));
588
589         if (cf_section_parse(conf, inst, module_config) < 0) {
590                 free(inst);
591                 return -1;
592         }
593
594         /*
595          *      This module used to support SMB Password files, but it
596          *      made it too complicated.  If the user tries to
597          *      configure an SMB Password file, then die, with an
598          *      error message.
599          */
600         if (inst->passwd_file) {
601                 radlog(L_ERR, "rlm_mschap: SMB password file is no longer supported in this module.  Use rlm_passwd module instead");
602                 mschap_detach(inst);
603                 return -1;
604         }
605
606         /*
607          *      Create the dynamic translation.
608          */
609         xlat_name = cf_section_name2(conf);
610         if (xlat_name == NULL)
611                 xlat_name = cf_section_name1(conf);
612         if (xlat_name){
613                 inst->xlat_name = strdup(xlat_name);
614                 xlat_register(xlat_name, mschap_xlat, inst);
615         }
616
617         return 0;
618 }
619
620 /*
621  *      add_reply() adds either MS-CHAP2-Success or MS-CHAP-Error
622  *      attribute to reply packet
623  */
624 static void add_reply(VALUE_PAIR** vp, unsigned char ident,
625                       const char* name, const char* value, int len)
626 {
627         VALUE_PAIR *reply_attr;
628         reply_attr = pairmake(name, "", T_OP_EQ);
629         if (!reply_attr) {
630                 DEBUG("  rlm_mschap: Failed to create attribute %s: %s\n", name, librad_errstr);
631                 return;
632         }
633
634         reply_attr->strvalue[0] = ident;
635         memcpy(reply_attr->strvalue + 1, value, len);
636         reply_attr->length = len + 1;
637         pairadd(vp, reply_attr);
638 }
639
640 /*
641  *      Add MPPE attributes to the reply.
642  */
643 static void mppe_add_reply(VALUE_PAIR **vp,
644                            const char* name, const char* value, int len)
645 {
646        VALUE_PAIR *reply_attr;
647        reply_attr = pairmake(name, "", T_OP_EQ);
648        if (!reply_attr) {
649                DEBUG("rlm_mschap: mppe_add_reply failed to create attribute %s: %s\n", name, librad_errstr);
650                return;
651        }
652
653        memcpy(reply_attr->strvalue, value, len);
654        reply_attr->length = len;
655        pairadd(vp, reply_attr);
656 }
657
658
659 /*
660  *      Do the MS-CHAP stuff.
661  *
662  *      This function is here so that all of the MS-CHAP related
663  *      authentication is in one place, and we can perhaps later replace
664  *      it with code to call winbindd, or something similar.
665  */
666 static int do_mschap(rlm_mschap_t *inst,
667                      REQUEST *request, VALUE_PAIR *password,
668                      uint8_t *challenge, uint8_t *response,
669                      uint8_t *nthashhash)
670 {
671         int             do_ntlm_auth = 0;
672         uint8_t         calculated[24];
673         VALUE_PAIR      *ntlm_auth = NULL;
674
675         /*
676          *      If we have an ntlm_auth configuration, then we may
677          *      want to use it.
678          */
679         ntlm_auth = pairfind(request->config_items,
680                              PW_MS_CHAP_USE_NTLM_AUTH);
681         if (ntlm_auth) do_ntlm_auth = ntlm_auth->lvalue;
682
683         /*
684          *      No ntlm_auth configured, attribute to tell us to
685          *      use it exists, and we're told to use it.  We don't
686          *      know what to do...
687          */
688         if (!inst->ntlm_auth && ntlm_auth &&
689             (ntlm_auth->lvalue != 0)) {
690                 DEBUG2("  rlm_mschap: Asked to use ntlm_auth, but it was not configured in the mschap{} section.");
691                 return -1;
692         }
693
694         /*
695          *      Do normal authentication.
696          */
697         if (!do_ntlm_auth) {
698                 /*
699                  *      No password: can't do authentication.
700                  */
701                 if (!password) {
702                         DEBUG2("  rlm_mschap: FAILED: No NT/LM-Password.  Cannot perform authentication.");
703                         return -1;
704                 }
705                 
706                 lrad_mschap(password->strvalue, challenge, calculated);
707                 if (memcmp(response, calculated, 24) != 0) {
708                         return -1;
709                 }
710                 
711                 /*
712                  *      If the password exists, and is an NT-Password,
713                  *      then calculate the hash of the NT hash.  Doing this
714                  *      here minimizes work for later.
715                  */
716                 if (password && (password->attribute == PW_NT_PASSWORD)) {
717                         md4_calc(nthashhash, password->strvalue, 16);
718                 } else {
719                         memset(nthashhash, 0, 16);
720                 }
721         } else {                /* run ntlm_auth */
722                 int     result;
723                 char    buffer[256];
724
725                 memset(nthashhash, 0, 16);
726
727                 /*
728                  *      Run the program, and expect that we get 16 
729                  */
730                 result = radius_exec_program(inst->ntlm_auth, request,
731                                              TRUE, /* wait */
732                                              buffer, sizeof(buffer),
733                                              NULL, NULL);
734                 if (result != 0) {
735                         DEBUG2("  rlm_mschap: External script failed.");
736                         return -1;
737                 }
738
739                 /*
740                  *      Parse the answer as an nthashhash.
741                  *
742                  *      ntlm_auth currently returns:
743                  *      NT_KEY: 000102030405060708090a0b0c0d0e0f
744                  */
745                 if (memcmp(buffer, "NT_KEY: ", 8) != 0) {
746                         DEBUG2("  rlm_mschap: Invalid output from ntlm_auth: expecting NT_KEY");
747                         return -1;
748                 }
749
750                 /*
751                  *      Check the length.  It should be at least 32,
752                  *      with an LF at the end.
753                  */
754                 if (strlen(buffer + 8) < 32) {
755                         DEBUG2("  rlm_mschap: Invalid output from ntlm_auth: NT_KEY has unexpected length");
756                         return -1;
757                 }
758
759                 /*
760                  *      Update the NT hash hash, from the NT key.
761                  */
762                 if (hex2bin(buffer + 8, nthashhash, 16) != 16) {
763                         DEBUG2("  rlm_mschap: Invalid output from ntlm_auth: NT_KEY has non-hex values");
764                         return -1;
765                 }
766         }
767
768         return 0;
769 }
770
771
772 /*
773  *      Data for the hashes.
774  */
775 static const uint8_t SHSpad1[40] =
776                { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
777                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
778                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
779                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
780
781 static const uint8_t SHSpad2[40] =
782                { 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
783                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
784                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
785                  0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2 };
786
787 static const uint8_t magic1[27] =
788                { 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
789                  0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
790                  0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79 };
791
792 static const uint8_t magic2[84] =
793                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
794                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
795                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
796                  0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
797                  0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
798                  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
799                  0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
800                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
801                  0x6b, 0x65, 0x79, 0x2e };
802
803 static const uint8_t magic3[84] =
804                { 0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
805                  0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
806                  0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
807                  0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
808                  0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
809                  0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
810                  0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
811                  0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
812                  0x6b, 0x65, 0x79, 0x2e };
813
814
815 static void mppe_GetMasterKey(uint8_t *nt_hashhash,uint8_t *nt_response,
816                               uint8_t *masterkey)
817 {
818        uint8_t digest[20];
819        SHA1_CTX Context;
820
821        SHA1Init(&Context);
822        SHA1Update(&Context,nt_hashhash,16);
823        SHA1Update(&Context,nt_response,24);
824        SHA1Update(&Context,magic1,27);
825        SHA1Final(digest,&Context);
826
827        memcpy(masterkey,digest,16);
828 }
829
830
831 static void mppe_GetAsymmetricStartKey(uint8_t *masterkey,uint8_t *sesskey,
832                                        int keylen,int issend)
833 {
834        uint8_t digest[20];
835        const uint8_t *s;
836        SHA1_CTX Context;
837
838        memset(digest,0,20);
839
840        if(issend) {
841                s = magic3;
842        } else {
843                s = magic2;
844        }
845
846        SHA1Init(&Context);
847        SHA1Update(&Context,masterkey,16);
848        SHA1Update(&Context,SHSpad1,40);
849        SHA1Update(&Context,s,84);
850        SHA1Update(&Context,SHSpad2,40);
851        SHA1Final(digest,&Context);
852
853        memcpy(sesskey,digest,keylen);
854 }
855
856
857 static void mppe_chap2_get_keys128(uint8_t *nt_hashhash,uint8_t *nt_response,
858                                    uint8_t *sendkey,uint8_t *recvkey)
859 {
860        uint8_t masterkey[16];
861
862        mppe_GetMasterKey(nt_hashhash,nt_response,masterkey);
863
864        mppe_GetAsymmetricStartKey(masterkey,sendkey,16,1);
865        mppe_GetAsymmetricStartKey(masterkey,recvkey,16,0);
866 }
867
868 /*
869  *      Generate MPPE keys.
870  */
871 static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response,
872                                    uint8_t *sendkey,uint8_t *recvkey)
873 {
874         uint8_t enckey1[16];
875         uint8_t enckey2[16];
876
877         mppe_chap2_get_keys128(nt_hashhash,response,enckey1,enckey2);
878
879         /*
880          *      dictionary.microsoft defines these attributes as
881          *      'encrypt=2'.  The functions in src/lib/radius.c will
882          *      take care of encrypting/decrypting them as appropriate,
883          *      so that we don't have to.
884          */
885         memcpy (sendkey, enckey1, 16);
886         memcpy (recvkey, enckey2, 16);
887 }
888
889
890 /*
891  *      mschap_authorize() - authorize user if we can authenticate
892  *      it later. Add Auth-Type attribute if present in module
893  *      configuration (usually Auth-Type must be "MS-CHAP")
894  */
895 static int mschap_authorize(void * instance, REQUEST *request)
896 {
897 #define inst ((rlm_mschap_t *)instance)
898         VALUE_PAIR *challenge = NULL, *response = NULL;
899         VALUE_PAIR *vp;
900         const char *authtype_name = "MS-CHAP";
901
902         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
903         if (!challenge) {
904                 return RLM_MODULE_NOOP;
905         }
906
907         response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
908         if (!response)
909                 response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE);
910
911         /*
912          *      Nothing we recognize.  Don't do anything.
913          */
914         if (!response) {
915                 DEBUG2("  rlm_mschap: Found MS-CHAP-Challenge, but no MS-CHAP-Response.");
916                 return RLM_MODULE_NOOP;
917         }
918
919         /*
920          *      Choose MS-CHAP, or whatever else they told us to use.
921          */
922         if (inst->auth_type) {
923                 authtype_name = inst->auth_type;
924         }
925
926         DEBUG2("  rlm_mschap: Found MS-CHAP attributes.  Setting 'Auth-Type  = %s'", authtype_name);
927
928         /*
929          *      Set Auth-Type to MS-CHAP.  The authentication code
930          *      will take care of turning clear-text passwords into
931          *      NT/LM passwords.
932          */
933         pairdelete(&request->config_items, PW_AUTHTYPE);
934         vp = pairmake("Auth-Type", authtype_name, T_OP_EQ);
935         rad_assert(vp != NULL);
936         pairadd(&request->config_items, vp);
937
938         return RLM_MODULE_OK;
939 #undef inst
940 }
941
942 /*
943  *      mschap_authenticate() - authenticate user based on given
944  *      attributes and configuration.
945  *      We will try to find out password in configuration
946  *      or in configured passwd file.
947  *      If one is found we will check paraneters given by NAS.
948  *
949  *      If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
950  *      one of:
951  *              PAP:      PW_PASSWORD or
952  *              MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
953  *              MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
954  *      In case of password mismatch or locked account we MAY return
955  *      PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
956  *      If MS-CHAP2 succeeds we MUST return
957  *      PW_MSCHAP2_SUCCESS
958  */
959 static int mschap_authenticate(void * instance, REQUEST *request)
960 {
961 #define inst ((rlm_mschap_t *)instance)
962         VALUE_PAIR *challenge = NULL, *response = NULL;
963         VALUE_PAIR *password = NULL;
964         VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
965         VALUE_PAIR *username;
966         VALUE_PAIR *reply_attr;
967         uint8_t nthashhash[16];
968         uint8_t msch2resp[42];
969         char *username_string;
970         int chap = 0;
971
972         /*
973          *      Find the SMB-Account-Ctrl attribute, or the
974          *      SMB-Account-Ctrl-Text attribute.
975          */
976         smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
977         if (!smb_ctrl) {
978                 password = pairfind(request->config_items,
979                                     PW_SMB_ACCOUNT_CTRL_TEXT);
980                 if (password) {
981                         smb_ctrl = pairmake("SMB-Account-CTRL", "0", T_OP_SET);
982                         pairadd(&request->config_items, smb_ctrl);
983                         smb_ctrl->lvalue = pdb_decode_acct_ctrl(password->strvalue);
984                 }
985         }
986
987         /*
988          *      We're configured to do MS-CHAP authentication.
989          *      and account control information exists.  Enforce it.
990          */
991         if (smb_ctrl) {
992                 /*
993                  *      Password is not required.
994                  */
995                 if ((smb_ctrl->lvalue & ACB_PWNOTREQ) != 0) {
996                         DEBUG2("  rlm_mschap: SMB-Account-Ctrl says no password is required.");
997                         return RLM_MODULE_OK;
998                 }
999         }
1000
1001         /*
1002          *      Decide how to get the passwords.
1003          */
1004         password = pairfind(request->config_items, PW_PASSWORD);
1005
1006         /*
1007          *      We need an LM-Password.
1008          */
1009         lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
1010         if (lm_password) {
1011                 /*
1012                  *      Allow raw octets.
1013                  */
1014                 if ((lm_password->length == 16) ||
1015                     ((lm_password->length == 32) &&
1016                      (hex2bin(lm_password->strvalue,
1017                               lm_password->strvalue, 16) == 16))) {
1018                         DEBUG2("  rlm_mschap: Found LM-Password");
1019                         lm_password->length = 16;
1020
1021                 } else {
1022                         radlog(L_ERR, "rlm_mschap: Invalid LM-Password");
1023                         lm_password = NULL;
1024                 }
1025
1026         } else if (!password) {
1027                 DEBUG2("  rlm_mschap: No User-Password configured.  Cannot create LM-Password.");
1028
1029         } else {                /* there is a configured User-Password */
1030                 lm_password = pairmake("LM-Password", "", T_OP_EQ);
1031                 if (!lm_password) {
1032                         radlog(L_ERR, "No memory");
1033                 } else {
1034                         lrad_lmpwdhash(password->strvalue,
1035                                        lm_password->strvalue);
1036                         lm_password->length = 16;
1037                         pairadd(&request->config_items, lm_password);
1038                 }
1039         }
1040
1041         /*
1042          *      We need an NT-Password.
1043          */
1044         nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
1045         if (nt_password) {
1046                 if ((nt_password->length == 16) ||
1047                     ((nt_password->length == 32) &&
1048                      (hex2bin(nt_password->strvalue,
1049                               nt_password->strvalue, 16) == 16))) {
1050                         DEBUG2("  rlm_mschap: Found NT-Password");
1051                         nt_password->length = 16;
1052
1053                 } else {
1054                         radlog(L_ERR, "rlm_mschap: Invalid NT-Password");
1055                         nt_password = NULL;
1056                 }
1057         } else if (!password) {
1058                 DEBUG2("  rlm_mschap: No User-Password configured.  Cannot create NT-Password.");
1059
1060         } else {                /* there is a configured User-Password */
1061                 nt_password = pairmake("NT-Password", "", T_OP_EQ);
1062                 if (!nt_password) {
1063                         radlog(L_ERR, "No memory");
1064                 } else {
1065                         ntpwdhash(nt_password->strvalue, password->strvalue);
1066                         nt_password->length = 16;
1067                         pairadd(&request->config_items, nt_password);
1068                 }
1069         }
1070
1071         challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
1072         if (!challenge) {
1073                 DEBUG2("  rlm_mschap: No MS-CHAP-Challenge in the request");
1074                 return RLM_MODULE_REJECT;
1075         }
1076
1077         /*
1078          *      We also require an MS-CHAP-Response.
1079          */
1080         response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);
1081
1082         /*
1083          *      MS-CHAP-Response, means MS-CHAPv1
1084          */
1085         if (response) {
1086                 int offset;
1087
1088                 /*
1089                  *      MS-CHAPv1 challenges are 8 octets.
1090                  */
1091                 if (challenge->length < 8) {
1092                         radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1093                         return RLM_MODULE_INVALID;
1094                 }
1095
1096                 /*
1097                  *      Responses are 50 octets.
1098                  */
1099                 if (response->length < 50) {
1100                         radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1101                         return RLM_MODULE_INVALID;
1102                 }
1103
1104                 /*
1105                  *      We are doing MS-CHAP.  Calculate the MS-CHAP
1106                  *      response
1107                  */
1108                 if (response->strvalue[1] & 0x01) {
1109                         DEBUG2("  rlm_mschap: doing MS-CHAPv1 with NT-Password");
1110                         password = nt_password;
1111                         offset = 26;
1112                 } else {
1113                         DEBUG2("  rlm_mschap: doing MS-CHAPv1 with LM-Password");
1114                         password = lm_password;
1115                         offset = 2;
1116                 }
1117
1118                 /*
1119                  *      Do the MS-CHAP authentication.
1120                  */
1121                 if (do_mschap(inst, request, password, challenge->strvalue,
1122                               response->strvalue + offset, nthashhash) < 0) {
1123                         DEBUG2("  rlm_mschap: MS-CHAP-Response is incorrect.");
1124                         add_reply(&request->reply->vps, *response->strvalue,
1125                                   "MS-CHAP-Error", "E=691 R=1", 9);
1126                         return RLM_MODULE_REJECT;
1127                 }
1128
1129                 chap = 1;
1130
1131         } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
1132                 uint8_t mschapv1_challenge[16];
1133
1134                 /*
1135                  *      MS-CHAPv2 challenges are 16 octets.
1136                  */
1137                 if (challenge->length < 16) {
1138                         radlog(L_AUTH, "rlm_mschap: MS-CHAP-Challenge has the wrong format.");
1139                         return RLM_MODULE_INVALID;
1140                 }
1141
1142                 /*
1143                  *      Responses are 50 octets.
1144                  */
1145                 if (response->length < 50) {
1146                         radlog(L_AUTH, "rlm_mschap: MS-CHAP-Response has the wrong format.");
1147                         return RLM_MODULE_INVALID;
1148                 }
1149
1150                 /*
1151                  *      We also require a User-Name
1152                  */
1153                 username = pairfind(request->packet->vps, PW_USER_NAME);
1154                 if (!username) {
1155                         radlog(L_AUTH, "rlm_mschap: We require a User-Name for MS-CHAPv2");
1156                         return RLM_MODULE_INVALID;
1157                 }
1158
1159
1160                 /*
1161                  *      with_ntdomain_hack moved here
1162                  */
1163                 if ((username_string = strchr(username->strvalue, '\\')) != NULL) {
1164                         if (inst->with_ntdomain_hack) {
1165                                 username_string++;
1166                         } else {
1167                                 DEBUG2("  rlm_mschap: NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
1168                                 username_string = username->strvalue;
1169                         }
1170                 } else {
1171                         username_string = username->strvalue;
1172                 }
1173
1174                 /*
1175                  *      The old "mschapv2" function has been moved to
1176                  *      here.
1177                  *
1178                  *      MS-CHAPv2 takes some additional data to create an
1179                  *      MS-CHAPv1 challenge, and then does MS-CHAPv1.
1180                  */
1181                 challenge_hash(response->strvalue + 2, /* peer challenge */
1182                                challenge->strvalue, /* our challenge */
1183                                username_string, /* user name */
1184                                mschapv1_challenge); /* resulting challenge */
1185                 
1186                 DEBUG2("  rlm_mschap: doing MS-CHAPv2 with NT-Password");
1187
1188                 if (do_mschap(inst, request, nt_password, mschapv1_challenge,
1189                               response->strvalue + 26, nthashhash) < 0) {
1190                         DEBUG2("  rlm_mschap: FAILED: MS-CHAP2-Response is incorrect");
1191                         add_reply(&request->reply->vps, *response->strvalue,
1192                                   "MS-CHAP-Error", "E=691 R=1", 9);
1193                         return RLM_MODULE_REJECT;
1194                 }
1195
1196                 /*
1197                  *      Get the NT-hash-hash, if necessary
1198                  */
1199                 if (nt_password) {
1200                 }
1201
1202                 auth_response(username_string, /* without the domain */
1203                               nthashhash, /* nt-hash-hash */
1204                               response->strvalue + 26, /* peer response */
1205                               response->strvalue + 2, /* peer challenge */
1206                               challenge->strvalue, /* our challenge */
1207                               msch2resp); /* calculated MPPE key */
1208                 add_reply( &request->reply->vps, *response->strvalue,
1209                            "MS-CHAP2-Success", msch2resp, 42);
1210                 chap = 2;
1211
1212         } else {                /* Neither CHAPv1 or CHAPv2 response: die */
1213                 radlog(L_AUTH, "rlm_mschap: No MS-CHAP response found");
1214                 return RLM_MODULE_INVALID;
1215         }
1216
1217         /*
1218          *      We have a CHAP response, but the account may be
1219          *      disabled.  Reject the user with the same error code
1220          *      we use when their password is invalid.
1221          */
1222         if (smb_ctrl) {
1223                 /*
1224                  *      Account is disabled.
1225                  *
1226                  *      They're found, but they don't exist, so we
1227                  *      return 'not found'.
1228                  */
1229                 if (((smb_ctrl->lvalue & ACB_DISABLED) != 0) ||
1230                     ((smb_ctrl->lvalue & ACB_NORMAL) == 0)) {
1231                         DEBUG2("  rlm_mschap: SMB-Account-Ctrl says that the account is disabled, or is not a normal account.");
1232                         add_reply( &request->reply->vps, *response->strvalue,
1233                                    "MS-CHAP-Error", "E=691 R=1", 9);
1234                         return RLM_MODULE_NOTFOUND;
1235                 }
1236
1237                 /*
1238                  *      User is locked out.
1239                  */
1240                 if ((smb_ctrl->lvalue & ACB_AUTOLOCK) != 0) {
1241                         DEBUG2("  rlm_mschap: SMB-Account-Ctrl says that the account is locked out.");
1242                         add_reply( &request->reply->vps, *response->strvalue,
1243                                    "MS-CHAP-Error", "E=647 R=0", 9);
1244                         return RLM_MODULE_USERLOCK;
1245                 }
1246         }
1247
1248         /* now create MPPE attributes */
1249         if (inst->use_mppe) {
1250                 uint8_t mppe_sendkey[34];
1251                 uint8_t mppe_recvkey[34];
1252
1253                 if (chap == 1){
1254                         DEBUG2("rlm_mschap: adding MS-CHAPv1 MPPE keys");
1255                         memset(mppe_sendkey, 0, 32);
1256                         if (lm_password) {
1257                                 memcpy(mppe_sendkey, lm_password->strvalue, 8);
1258                         }
1259
1260                         if (nt_password) {
1261                                 /*
1262                                  *      According to RFC 2548 we
1263                                  *      should send NT hash.  But in
1264                                  *      practice it doesn't work.
1265                                  *      Instead, we should send nthashhash
1266                                  *
1267                                  *      This is an error on RFC 2548.
1268                                  */
1269                                 memcpy(mppe_sendkey + 8,
1270                                        nthashhash, 16);
1271                                 mppe_add_reply(&request->reply->vps,
1272                                                "MS-CHAP-MPPE-Keys",
1273                                                mppe_sendkey, 32);
1274                         }
1275
1276                 } else if (chap == 2) {
1277                         DEBUG2("rlm_mschap: adding MS-CHAPv2 MPPE keys");
1278                         mppe_chap2_gen_keys128(nthashhash,
1279                                                response->strvalue + 26,
1280                                                mppe_sendkey, mppe_recvkey);
1281                         
1282                         mppe_add_reply(&request->reply->vps,
1283                                        "MS-MPPE-Recv-Key",
1284                                        mppe_recvkey, 16);
1285                         mppe_add_reply(&request->reply->vps,
1286                                        "MS-MPPE-Send-Key",
1287                                        mppe_sendkey, 16);
1288
1289                 }
1290                 reply_attr = pairmake("MS-MPPE-Encryption-Policy",
1291                                       (inst->require_encryption)? "0x00000002":"0x00000001",
1292                                       T_OP_EQ);
1293                 rad_assert(reply_attr != NULL);
1294                 pairadd(&request->reply->vps, reply_attr);
1295                 reply_attr = pairmake("MS-MPPE-Encryption-Types",
1296                                       (inst->require_strong)? "0x00000004":"0x00000006",
1297                                       T_OP_EQ);
1298                 rad_assert(reply_attr != NULL);
1299                 pairadd(&request->reply->vps, reply_attr);
1300
1301         } /* else we weren't asked to use MPPE */
1302
1303         return RLM_MODULE_OK;
1304 #undef inst
1305 }
1306
1307 module_t rlm_mschap = {
1308   "MS-CHAP",
1309   RLM_TYPE_THREAD_SAFE,         /* type */
1310   NULL,                         /* initialize */
1311   mschap_instantiate,           /* instantiation */
1312   {
1313           mschap_authenticate,  /* authenticate */
1314           mschap_authorize,     /* authorize */
1315           NULL,                 /* pre-accounting */
1316           NULL,                 /* accounting */
1317           NULL,                 /* checksimul */
1318           NULL,                 /* pre-proxy */
1319           NULL,                 /* post-proxy */
1320           NULL                  /* post-auth */
1321   },
1322   mschap_detach,                /* detach */
1323   NULL,                         /* destroy */
1324 };