From: Phil Mayers Date: Tue, 19 Apr 2011 14:16:27 +0000 (+0100) Subject: add support for MS-CHAPv2 password changes to rlm_mschap X-Git-Tag: release_3_0_0_beta0~839 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7ff11d2d5e6f4d58c7661e048c48bea6f254c2cd;p=thirdparty%2Ffreeradius-server.git add support for MS-CHAPv2 password changes to rlm_mschap --- diff --git a/raddb/modules/mschap b/raddb/modules/mschap index 57393dc6dcc..d26e9aa543c 100644 --- a/raddb/modules/mschap +++ b/raddb/modules/mschap @@ -64,6 +64,32 @@ mschap { # # ntlm_auth = "/path/to/ntlm_auth --request-nt-key --username=%{%{Stripped-User-Name}:-%{%{User-Name}:-None}} --challenge=%{%{mschap:Challenge}:-00} --nt-response=%{%{mschap:NT-Response}:-00}" + passchange { + # This support MS-CHAPv2 (not v1) password change + # requests. See doc/mschap.rst for more IMPORTANT + # information. + # + # Samba/ntlm_auth - if you are using ntlm_auth to + # validate passwords, you will need to use ntlm_auth + # to change passwords. Uncomment the three lines + # below, and change the path to ntlm_auth. + # +# ntlm_auth = "/usr/bin/ntlm_auth --helper-protocol=ntlm-change-password-1" +# ntlm_auth_username = "username: %{mschap:User-Name}" +# ntlm_auth_domain = "username: %{mschap:NT-Domain}" + + # To implement a local password change, you need to + # supply a string which is then expanded, so that the + # password can be placed somewhere. e.g. passed to a + # script (exec), or written to SQL (UPDATE/INSERT). + # We give both examples here, but only one will be + # used. + # +# local_cpw = "%{exec:/path/to/script %{mschap:User-Name} %{MS-CHAP-New-Cleartext-Password}}" + # +# local_cpw = "%{sql:UPDATE radcheck set value='%{MS-CHAP-New-NT-Password}' where username='%{SQL-User-Name}' and attribute='NT-Password'}" + } + # For Apple Server, when running on the same machine as # Open Directory. It has no effect on other systems. # diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal index 73ba6634e7b..de6b80d0534 100644 --- a/share/dictionary.freeradius.internal +++ b/share/dictionary.freeradius.internal @@ -215,6 +215,8 @@ ATTRIBUTE Send-CoA-Type 1132 integer ATTRIBUTE MS-CHAP-Password 1133 string ATTRIBUTE Packet-Transmit-Counter 1134 integer ATTRIBUTE Cached-Session-Policy 1135 string +ATTRIBUTE MS-CHAP-New-Cleartext-Password 1136 string +ATTRIBUTE MS-CHAP-New-NT-Password 1137 octets # # Range: 1200-1279 diff --git a/src/include/radius.h b/src/include/radius.h index 5fc7042a92f..9d595929508 100644 --- a/src/include/radius.h +++ b/src/include/radius.h @@ -321,10 +321,13 @@ */ #define PW_MSCHAP_RESPONSE 1 #define PW_MSCHAP_ERROR 2 +#define PW_MSCHAP_CPW_1 3 +#define PW_MSCHAP_CPW_2 4 +#define PW_MSCHAP_NT_ENC_PW 6 #define PW_MSCHAP_CHALLENGE 11 #define PW_MSCHAP2_RESPONSE 25 #define PW_MSCHAP2_SUCCESS 26 - +#define PW_MSCHAP2_CPW 27 /* * Old nonsense. Will be deleted ASAP diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c index 6b8910897da..ab9c4b10df4 100644 --- a/src/modules/rlm_mschap/rlm_mschap.c +++ b/src/modules/rlm_mschap/rlm_mschap.c @@ -36,6 +36,10 @@ RCSID("$Id$") #include "mschap.h" #include "smbdes.h" +#ifdef HAVE_OPENSSL_CRYPTO_H +#include +#endif + #ifdef __APPLE__ extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * usernamepair); #endif @@ -52,11 +56,12 @@ extern int od_mschap_auth(REQUEST *request, VALUE_PAIR *challenge, VALUE_PAIR * #define ACB_SVRTRUST 0x0100 /* 1 = Server trust account */ #define ACB_PWNOEXP 0x0200 /* 1 = User password does not expire */ #define ACB_AUTOLOCK 0x0400 /* 1 = Account auto locked */ +#define ACB_PW_EXPIRED 0x00020000 /* 1 = Password Expired */ static int pdb_decode_acct_ctrl(const char *p) { int acct_ctrl = 0; - int finished = 0; + int done = 0; /* * Check if the account type bits have been encoded after the @@ -65,7 +70,7 @@ static int pdb_decode_acct_ctrl(const char *p) if (*p != '[') return 0; - for (p++; *p && !finished; p++) { + for (p++; *p && !done; p++) { switch (*p) { case 'N': /* 'N'o password. */ acct_ctrl |= ACB_PWNOTREQ; @@ -111,6 +116,10 @@ static int pdb_decode_acct_ctrl(const char *p) acct_ctrl |= ACB_DOMTRUST; break; + case 'e': /* 'e'xpired, the password has */ + acct_ctrl |= ACB_PW_EXPIRED; + break; + case ' ': /* ignore spaces */ break; @@ -119,7 +128,7 @@ static int pdb_decode_acct_ctrl(const char *p) case '\0': case ']': default: - finished = 1; + done = 1; break; } } @@ -136,6 +145,10 @@ typedef struct rlm_mschap_t { char *passwd_file; const char *xlat_name; char *ntlm_auth; + char *ntlm_cpw; + char *ntlm_cpw_username; + char *ntlm_cpw_domain; + char *local_cpw; const char *auth_type; int allow_retry; char *retry_msg; @@ -523,6 +536,17 @@ static size_t mschap_xlat(void *instance, REQUEST *request, } +static const CONF_PARSER passchange_config[] = { + { "ntlm_auth", PW_TYPE_STRING_PTR, + offsetof(rlm_mschap_t, ntlm_cpw), NULL, NULL }, + { "ntlm_auth_username", PW_TYPE_STRING_PTR, + offsetof(rlm_mschap_t, ntlm_cpw_username), NULL, NULL }, + { "ntlm_auth_domain", PW_TYPE_STRING_PTR, + offsetof(rlm_mschap_t, ntlm_cpw_domain), NULL, NULL }, + { "local_cpw", PW_TYPE_STRING_PTR, + offsetof(rlm_mschap_t, local_cpw), NULL, NULL }, + { NULL, -1, 0, NULL, NULL } /* end the list */ +}; static const CONF_PARSER module_config[] = { /* * Cache the password by default. @@ -539,6 +563,7 @@ static const CONF_PARSER module_config[] = { offsetof(rlm_mschap_t, passwd_file), NULL, NULL }, { "ntlm_auth", PW_TYPE_STRING_PTR, offsetof(rlm_mschap_t, ntlm_auth), NULL, NULL }, + { "passchange", PW_TYPE_SUBSECTION, 0, NULL, (const void *) passchange_config }, { "allow_retry", PW_TYPE_BOOLEAN, offsetof(rlm_mschap_t, allow_retry), NULL, "yes" }, { "retry_msg", PW_TYPE_STRING_PTR, @@ -654,6 +679,360 @@ static void mppe_add_reply(REQUEST *request, vp->length = len; } +static int write_all(int fd, const char *buf, int len) { + int rv,done=0; + + while (done < len) { + rv = write(fd, buf+done, len-done); + if (rv <= 0) + break; + done += rv; + } + return done; +} + +/* + * Perform an MS-CHAP2 password change + */ + +static int do_mschap_cpw(rlm_mschap_t *inst, + REQUEST *request, VALUE_PAIR *nt_password, + uint8_t *new_nt_password, + uint8_t *old_nt_hash, + int do_ntlm_auth) +{ + if (inst->ntlm_cpw && do_ntlm_auth) { + /* + * we're going to run ntlm_auth in helper-mode + * we're expecting to use the ntlm-change-password-1 protocol + * which needs the following on stdin: + * + * username: %{mschap:User-Name} + * nt-domain: %{mschap:NT-Domain} + * new-nt-password-blob: bin2hex(new_nt_password) - 1032 bytes encoded + * old-nt-hash-blob: bin2hex(old_nt_hash) - 32 bytes encoded + * new-lm-password-blob: 00000...0000 - 1032 bytes null + * old-lm-hash-blob: 000....000 - 32 bytes null + * .\n + * + * ...and it should then print out + * + * Password-Change: Yes + * + * or + * + * Password-Change: No + * Password-Change-Error: blah + */ + + int to_child=-1; + int from_child=-1; + pid_t pid, child_pid; + int status, len; + char buf[2048]; + char *emsg; + + RDEBUG("Doing MS-CHAPv2 password change via ntlm_auth helper"); + + /* + * Start up ntlm_auth with a pipe on stdin and stdout + */ + + pid = radius_start_program(inst->ntlm_cpw, request, 1, &to_child, &from_child, NULL, 0); + if (pid < 0) { + RDEBUG2("could not exec ntlm_auth cpw command"); + return -1; + } + + /* + * write the stuff to the client + */ + + if (inst->ntlm_cpw_username) { + len = radius_xlat(buf, sizeof(buf) - 2, inst->ntlm_cpw_username, request, NULL); + strcat(buf, "\n"); + len++; + + if (write_all(to_child, buf, len) != len) { + RDEBUG2("failed to write username to child"); + goto ntlm_auth_err; + } + } else { + RDEBUG("No ntlm_auth username set - passchange will definitely fail!"); + } + + if (inst->ntlm_cpw_domain) { + len = radius_xlat(buf, sizeof(buf) - 2, inst->ntlm_cpw_domain, request, NULL); + strcat(buf, "\n"); + len++; + + if (write_all(to_child, buf, len) != len) { + RDEBUG2("failed to write domain to child"); + goto ntlm_auth_err; + } + } else { + RDEBUG("No ntlm_auth domain set - username must be full-username to work"); + } + + /* now the password blobs */ + len = sprintf(buf, "new-nt-password-blob: "); + fr_bin2hex(new_nt_password, buf+len, 516); + buf[len+1032] = '\n'; + buf[len+1033] = '\0'; + len = strlen(buf); + if (write_all(to_child, buf, len) != len) { + RDEBUG2("failed to write new password blob to child"); + goto ntlm_auth_err; + } + + len = sprintf(buf, "old-nt-hash-blob: "); + fr_bin2hex(old_nt_hash, buf+len, 16); + buf[len+32] = '\n'; + buf[len+33] = '\0'; + len = strlen(buf); + if (write_all(to_child, buf, len) != len) { + RDEBUG2("failed to write old hash blob to child"); + goto ntlm_auth_err; + } + + /* + * in current samba versions, failure to supply empty + * LM password/hash blobs causes the change to fail + */ + len = sprintf(buf, "new-lm-password-blob: %01032i\n", 0); + if (write_all(to_child, buf, len) != len) { + RDEBUG2("failed to write dummy LM password to child"); + goto ntlm_auth_err; + } + len = sprintf(buf, "old-lm-hash-blob: %032i\n", 0); + if (write_all(to_child, buf, len) != len) { + RDEBUG2("failed to write dummy LM hash to child"); + goto ntlm_auth_err; + } + if (write_all(to_child, ".\n", 2) != 2) { + RDEBUG2("failed to send finish to child"); + goto ntlm_auth_err; + } + close(to_child); + to_child = -1; + + /* + * Read from the child + */ + len = radius_readfrom_program(from_child, pid, 10, buf, sizeof(buf)); + if (len < 0) { + /* radius_readfrom_program will have closed from_child for us */ + RDEBUG2("Failure reading from child"); + return -1; + } + close(from_child); + from_child = -1; + + buf[len] = 0; + RDEBUG2("ntlm_auth said: %s", buf); + + child_pid = rad_waitpid(pid, &status); + if (child_pid == 0) { + RDEBUG2("Timeout waiting for child"); + return -1; + } + if (child_pid != pid) { + RDEBUG("Abnormal exit status: %s", strerror(errno)); + return -1; + } + + if (strstr(buf, "Password-Change: Yes")) { + RDEBUG2("ntlm_auth password change succeeded"); + return 0; + } + + emsg = strstr(buf, "Password-Change-Error: "); + if (emsg) { + emsg = strsep(&emsg, "\n"); + } else { + emsg = "could not find error"; + } + RDEBUG2("ntlm auth password change failed: %s", emsg); + +ntlm_auth_err: + /* safe because these either need closing or are == -1 */ + close(to_child); + close(from_child); + + return -1; + + } else if (inst->local_cpw) { +#ifdef HAVE_OPENSSL_CRYPTO_H + /* + * decrypt the new password blob, add it as a temporary request + * variable, xlat the local_cpw string, then remove it + * + * this allows is to write e..g + * + * %{sql:insert into ...} + * + * ...or... + * + * %{exec:/path/to %{mschap:User-Name} %{MS-CHAP-New-Password}}" + * + */ + + VALUE_PAIR *new_pass, *new_hash; + uint8_t *p; + int i, result_len; + uint32_t passlen; + char result[253]; + uint8_t nt_pass_decrypted[516], old_nt_hash_expected[16]; + RC4_KEY key; + + if (!nt_password) { + RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute"); + return -1; + } else { + RDEBUG("Doing MS-CHAPv2 password change locally"); + } + + /* + * decrypt the blob + */ + RC4_set_key(&key, nt_password->length, nt_password->vp_octets); + RC4(&key, 516, new_nt_password, nt_pass_decrypted); + + /* + * pwblock is + * 512-N bytes random pad + * N bytes password as utf-16-le + * 4 bytes - N as big-endian int + */ + + passlen = nt_pass_decrypted[512]; + passlen += nt_pass_decrypted[513] << 8; + passlen += nt_pass_decrypted[514] << 16; + passlen += nt_pass_decrypted[515] << 24; + + /* + * sanity check - passlen positive and <= 512 + * if not, crypto has probably gone wrong + */ + if (passlen > 512) { + RDEBUG2("Decrypted new password blob claims length %u > 512 - probably an invalid NT-Password", passlen); + return -1; + } + + p = nt_pass_decrypted + 512 - passlen; + + /* + * the new NT hash - this should be preferred over the + * cleartext password as it avoids unicode hassles + */ + new_hash = radius_pairmake(request, &request->packet->vps, + "MS-CHAP-New-NT-Password", "", + T_OP_EQ); + fr_md4_calc(new_hash->vp_octets, p, passlen); + new_hash->length = 16; + + /* + * check that nt_password encrypted with new_hash + * matches the old_hash value from the client + */ + smbhash(old_nt_hash_expected, nt_password->vp_octets, new_hash->vp_octets); + smbhash(old_nt_hash_expected+8, nt_password->vp_octets+8, new_hash->vp_octets+7); + if (memcmp(old_nt_hash_expected, old_nt_hash, 16)!=0) { + RDEBUG2("old NT hash value from client does not match our value"); + return -1; + } + + /* + * the new cleartext password, which is utf-16 + * do some unpleasant vileness to turn it into + * utf8 without pulling in libraries like iconv + */ + new_pass = radius_pairmake(request, &request->packet->vps, + "MS-CHAP-New-Cleartext-Password", "", + T_OP_EQ); + new_pass->length = 0; + i = 0; + while (ilength >= 253) { + RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i); + break; + } + new_pass->vp_strvalue[new_pass->length++] = c; + } else if (c < 0x7ff) { + /* 2-byte */ + if (new_pass->length >= 252) { + RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i); + break; + } + new_pass->vp_strvalue[new_pass->length++] = 0xc0 + (c >> 6); + new_pass->vp_strvalue[new_pass->length++] = 0x80 + (c & 0x3f); + } else { + /* 3-byte */ + if (new_pass->length >= 251) { + RDEBUG("Ran out of room turning new password into utf8 at %d - cleartext will be truncated!", i); + break; + } + new_pass->vp_strvalue[new_pass->length++] = 0xe0 + (c >> 12); + new_pass->vp_strvalue[new_pass->length++] = 0x80 + ((c>>6) & 0x3f); + new_pass->vp_strvalue[new_pass->length++] = 0x80 + (c & 0x3f); + } + } + + + + + /* + * perform the xlat + */ + result_len = radius_xlat(result, sizeof(result), inst->local_cpw, request, NULL); + if (!result_len) { + RDEBUG("Local MS-CHAPv2 password change - xlat didn't give any result, assuming failure"); + return -1; + } + + RDEBUG("MS-CHAPv2 password change succeeded: %s", result); + + /* + * update the NT-Password attribute with the new hash + * this lets us fall through to the authentication + * code using the new hash, not the old one + */ + memcpy(nt_password->vp_octets, new_hash->vp_octets, new_hash->length); + + /* + * rock on! password change succeeded + */ + return 0; +#else + RDEBUG("Local MS-CHAPv2 password changes require OpenSSL support"); + return -1; +#endif + } else { + RDEBUG("MS-CHAPv2 password change not configured"); + } + + return -1; +} /* * Do the MS-CHAP stuff. @@ -715,6 +1094,14 @@ static int do_mschap(rlm_mschap_t *inst, char *p; VALUE_PAIR *vp = NULL; + /* + * look for "Password expired" + */ + if (strstr(buffer, "Password expired")) { + RDEBUG2("ntlm_auth says password has expired"); + return -648; + } + RDEBUG2("External script failed."); vp = pairmake("Module-Failure-Message", "", T_OP_EQ); @@ -892,7 +1279,7 @@ static void mppe_chap2_gen_keys128(uint8_t *nt_hashhash,uint8_t *response, static int mschap_authorize(void * instance, REQUEST *request) { #define inst ((rlm_mschap_t *)instance) - VALUE_PAIR *challenge = NULL, *response = NULL; + VALUE_PAIR *challenge = NULL; challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, @@ -901,19 +1288,13 @@ static int mschap_authorize(void * instance, REQUEST *request) return RLM_MODULE_NOOP; } - response = pairfind(request->packet->vps, - PW_MSCHAP_RESPONSE, - VENDORPEC_MICROSOFT); - if (!response) - response = pairfind(request->packet->vps, - PW_MSCHAP2_RESPONSE, - VENDORPEC_MICROSOFT); - - /* - * Nothing we recognize. Don't do anything. - */ - if (!response) { - RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP-Response."); + if (!pairfind(request->packet->vps, PW_MSCHAP_RESPONSE, + VENDORPEC_MICROSOFT) && + !pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, + VENDORPEC_MICROSOFT) && + !pairfind(request->packet->vps, PW_MSCHAP2_CPW, + VENDORPEC_MICROSOFT)) { + RDEBUG2("Found MS-CHAP-Challenge, but no MS-CHAP response or change-password"); return RLM_MODULE_NOOP; } @@ -960,6 +1341,7 @@ static int mschap_authenticate(void * instance, REQUEST *request) #define inst ((rlm_mschap_t *)instance) VALUE_PAIR *challenge = NULL; VALUE_PAIR *response = NULL; + VALUE_PAIR *cpw = NULL; VALUE_PAIR *password = NULL; VALUE_PAIR *lm_password, *nt_password, *smb_ctrl; VALUE_PAIR *username; @@ -1090,6 +1472,131 @@ static int mschap_authenticate(void * instance, REQUEST *request) } } + cpw = pairfind(request->packet->vps, PW_MSCHAP2_CPW, + VENDORPEC_MICROSOFT); + if (cpw) { + /* + * mschap2 password change request + * we cheat - first decode and execute the passchange + * we then extract the response, add it into the request + * then jump into mschap2 auth with the chal/resp + */ + uint8_t new_nt_encrypted[516], old_nt_encrypted[16]; + VALUE_PAIR *nt_enc=NULL; + int seq, new_nt_enc_len=0; + + RDEBUG("MS-CHAPv2 password change request received"); + + if (cpw->length != 68) { + RDEBUG2("MS-CHAP2-CPW has the wrong format - length %d!=68", cpw->length); + return RLM_MODULE_INVALID; + } else if (cpw->vp_octets[0]!=7) { + RDEBUG2("MS-CHAP2-CPW has the wrong format - code %d!=7", cpw->vp_octets[0]); + return RLM_MODULE_INVALID; + } + + /* + * look for the new (encrypted) password + * bah stupid composite attributes + * we're expecting 3 attributes with the leading bytes + * 06::00:01:<1st chunk> + * 06::00:02:<2nd chunk> + * 06::00:03:<3rd chunk> + */ + for (seq=1;seq<4;seq++) { + int found=0; + for (nt_enc=request->packet->vps; nt_enc; nt_enc=nt_enc->next) { + if (nt_enc->attribute != PW_MSCHAP_NT_ENC_PW) + continue; + + if (nt_enc->vp_octets[0] != 6) { + RDEBUG2("MS-CHAP-NT-Enc-PW with invalid format"); + return RLM_MODULE_INVALID; + } + if (nt_enc->vp_octets[2]==0 && nt_enc->vp_octets[3]==seq) { + found = 1; + break; + } + } + + if (!found) { + RDEBUG2("Could not find MS-CHAP-NT-Enc-PW w/ sequence number %d", seq); + return RLM_MODULE_INVALID; + } + + /* + * copy the data into the buffer + */ + memcpy(new_nt_encrypted + new_nt_enc_len, nt_enc->vp_octets + 4, nt_enc->length - 4); + new_nt_enc_len += nt_enc->length - 4; + } + if (new_nt_enc_len != 516) { + RDEBUG2("Unpacked MS-CHAP-NT-Enc-PW length != 516"); + return RLM_MODULE_INVALID; + } + + /* + * RFC 2548 is confusing here + * it claims: + * + * 1 byte code + * 1 byte ident + * 16 octets - old hash encrypted with new hash + * 24 octets - peer challenge + * this is actually: + * 16 octets - peer challenge + * 8 octets - reserved + * 24 octets - nt response + * 2 octets - flags (ignored) + */ + + memcpy(old_nt_encrypted, cpw->vp_octets+2, sizeof(old_nt_encrypted)); + + RDEBUG2("Password change payload valid"); + + /* perform the actual password change */ + if (do_mschap_cpw(inst, request, nt_password, new_nt_encrypted, old_nt_encrypted, do_ntlm_auth) < 0) { + char buffer[128]; + + RDEBUG("Password change failed"); + + snprintf(buffer, sizeof(buffer), "E=709 R=0 M=Password change failed"); + mschap_add_reply(request, &request->reply->vps, + cpw->vp_octets[1], "MS-CHAP-Error", + buffer, strlen(buffer)); + return RLM_MODULE_REJECT; + } + RDEBUG("Password change successful"); + + /* + * Clear any expiry bit so the user can now login; + * obviously the password change action will need + * to have cleared this bit in the config/SQL/wherever + */ + if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) { + RDEBUG("clearing expiry bit in SMB-Acct-Ctrl to allow authentication"); + smb_ctrl->vp_integer &= ~ACB_PW_EXPIRED; + } else { + } + + /* + * extract the challenge & response from the end of the password + * change, add them into the request and then continue with + * the authentication + */ + + response = radius_paircreate(request, &request->packet->vps, + PW_MSCHAP2_RESPONSE, + VENDORPEC_MICROSOFT, + PW_TYPE_OCTETS); + response->length = 50; + /* ident & flags */ + response->vp_octets[0] = cpw->vp_octets[1]; + response->vp_octets[1] = 0; + /* peer challenge and client NT response */ + memcpy(response->vp_octets+2, cpw->vp_octets + 18, 48); + } + challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT); @@ -1153,7 +1660,10 @@ static int mschap_authenticate(void * instance, REQUEST *request) chap = 1; - } else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE, VENDORPEC_MICROSOFT)) != NULL) { + } else if ((response = pairfind(request->packet->vps, + PW_MSCHAP2_RESPONSE, + VENDORPEC_MICROSOFT)) != NULL) { + int mschap_result; uint8_t mschapv1_challenge[16]; VALUE_PAIR *name_attr, *response_name; @@ -1253,9 +1763,13 @@ static int mschap_authenticate(void * instance, REQUEST *request) RDEBUG2("Told to do MS-CHAPv2 for %s with NT-Password", username_string); - if (do_mschap(inst, request, nt_password, mschapv1_challenge, - response->vp_octets + 26, nthashhash, - do_ntlm_auth) < 0) { + mschap_result = do_mschap(inst, request, nt_password, mschapv1_challenge, + response->vp_octets + 26, nthashhash, + do_ntlm_auth); + if (mschap_result == -648) + goto password_expired; + + if (mschap_result < 0) { int i; char buffer[128]; @@ -1281,6 +1795,26 @@ static int mschap_authenticate(void * instance, REQUEST *request) return RLM_MODULE_REJECT; } + if (smb_ctrl && smb_ctrl->vp_integer & ACB_PW_EXPIRED) { + /* + * if the password is correct and it has expired + * we can permit password changes (only in MS-CHAPv2) + */ + char newchal[33], buffer[128]; + int i; + password_expired: + + for (i = 0; i < 16; i++) + snprintf(newchal + i*2, 3, "%02x", fr_rand() & 0xff); + + snprintf(buffer, sizeof(buffer), "E=648 R=0 C=%s V=3 M=Password Expired", newchal); + + mschap_add_reply(request, &request->reply->vps, + *response->vp_octets, "MS-CHAP-Error", + buffer, strlen(buffer)); + return RLM_MODULE_REJECT; + } + mschap_auth_response(username_string, /* without the domain */ nthashhash, /* nt-hash-hash */ response->vp_octets + 26, /* peer response */ diff --git a/src/modules/rlm_mschap/smbdes.c b/src/modules/rlm_mschap/smbdes.c index fac766d1923..e46e18fdf6b 100644 --- a/src/modules/rlm_mschap/smbdes.c +++ b/src/modules/rlm_mschap/smbdes.c @@ -284,7 +284,7 @@ static void str_to_key(unsigned char *str,unsigned char *key) } -static void smbhash(unsigned char *out, const unsigned char *in, unsigned char *key) +void smbhash(unsigned char *out, const unsigned char *in, unsigned char *key) { int i; char outb[64]; diff --git a/src/modules/rlm_mschap/smbdes.h b/src/modules/rlm_mschap/smbdes.h index 56ca71fa704..f64d56db071 100644 --- a/src/modules/rlm_mschap/smbdes.h +++ b/src/modules/rlm_mschap/smbdes.h @@ -6,6 +6,7 @@ #include RCSIDH(smbdes_h, "$Id$") +void smbhash(unsigned char *out, const unsigned char *in, unsigned char *key); void smbdes_lmpwdhash(const char *password, uint8_t *lmhash); void smbdes_mschap(const uint8_t win_password[16], const uint8_t *challenge, uint8_t *response);