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