"Fossies" - the Fresh Open Source Software Archive

Member "freeradius-server-3.0.23/src/modules/rlm_mschap/rlm_mschap.c" (10 Jun 2021, 58363 Bytes) of package /linux/misc/freeradius-server-3.0.23.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "rlm_mschap.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.0.22_vs_3.0.23.

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