X-Git-Url: http://git.ozlabs.org/?p=ppp.git;a=blobdiff_plain;f=pppd%2Fchap.c;h=a63dde818fa01e0db3fcb14b062541d6180044e1;hp=208a144fc8569051fbf18f2925c8aa93daf0449b;hb=9b5e4bcdc6e25f18d20200af78df94d695e428ac;hpb=8ed456439d306257173fb0c74d77c4e3c472dfc6;ds=sidebyside diff --git a/pppd/chap.c b/pppd/chap.c index 208a144..a63dde8 100644 --- a/pppd/chap.c +++ b/pppd/chap.c @@ -33,13 +33,14 @@ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#define RCSID "$Id: chap.c,v 1.28 2002/03/04 14:59:51 dfs Exp $" +#define RCSID "$Id: chap.c,v 1.36 2002/10/12 02:30:21 fcusack Exp $" /* * TODO: */ #include +#include #include #include #include @@ -65,6 +66,14 @@ int (*chap_auth_hook) __P((char *user, static const char rcsid[] = RCSID; +#ifdef CHAPMS +/* For MPPE debug */ +/* Use "[]|}{?/><,`!2&&(" (sans quotes) for RFC 3079 MS-CHAPv2 test value */ +static char *mschap_challenge = NULL; +/* Use "!@\#$%^&*()_+:3|~" (sans quotes, backslash is to escape #) for ... */ +static char *mschap2_peer_challenge = NULL; +#endif + /* * Command-line options. */ @@ -78,6 +87,12 @@ static option_t chap_option_list[] = { #ifdef MSLANMAN { "ms-lanman", o_bool, &ms_lanman, "Use LanMan passwd when using MS-CHAP", 1 }, +#endif +#ifdef DEBUGMPPEKEY + { "mschap-challenge", o_string, &mschap_challenge, + "specify CHAP challenge" }, + { "mschap2-peer-challenge", o_string, &mschap2_peer_challenge, + "specify CHAP peer challenge" }, #endif { NULL } }; @@ -442,10 +457,8 @@ ChapReceiveChallenge(cstate, inp, id, len) rchallenge = inp; INCPTR(rchallenge_len, inp); - if (len >= sizeof(rhostname)) - len = sizeof(rhostname) - 1; - BCOPY(inp, rhostname, len); - rhostname[len] = '\000'; + /* Null terminate and clean remote name. */ + slprintf(rhostname, sizeof(rhostname), "%.*v", len, inp); /* Microsoft doesn't send their name back in the PPP packet */ if (explicit_remote || (remote_name[0] != 0 && rhostname[0] == 0)) { @@ -483,9 +496,18 @@ ChapReceiveChallenge(cstate, inp, id, len) #ifdef CHAPMS case CHAP_MICROSOFT: - ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len); + ChapMS(cstate, rchallenge, secret, secret_len, + (MS_ChapResponse *) cstate->response); break; -#endif + + case CHAP_MICROSOFT_V2: + ChapMS2(cstate, rchallenge, + mschap2_peer_challenge? mschap2_peer_challenge: NULL, + cstate->resp_name, secret, secret_len, + (MS_Chap2Response *) cstate->response, cstate->earesponse, + MS_CHAP2_AUTHENTICATEE); + break; +#endif /* CHAPMS */ default: CHAPDEBUG(("unknown digest type %d", cstate->resp_type)); @@ -559,6 +581,11 @@ ChapReceiveResponse(cstate, inp, id, len) BCOPY(inp, rhostname, len); rhostname[len] = '\000'; +#ifdef CHAPMS + /* copy the flags into cstate for use elsewhere */ + if (cstate->chal_type == CHAP_MICROSOFT_V2) + cstate->resp_flags = ((MS_Chap2Response *) remmd)->Flags[0]; +#endif /* CHAPMS */ /* * Get secret for authenticating them with us, * do the hash ourselves, and compare the result. @@ -570,6 +597,19 @@ ChapReceiveResponse(cstate, inp, id, len) code = (*chap_auth_hook) ( (explicit_remote ? remote_name : rhostname), remmd, (int) remmd_len, cstate ); + /* + * Check remote number authorization. A plugin may have filled in + * the remote number or added an allowed number, and rather than + * return an authenticate failure, is leaving it for us to verify. + */ + if (code == CHAP_SUCCESS) { + if (!auth_number()) { + /* We do not want to leak info about the chap result. */ + code = CHAP_FAILURE; /* XXX exit value will be "wrong" */ + warn("calling number %q is not authorized", remote_number); + } + } + } else { if (!get_secret(cstate->unit, (explicit_remote? remote_name: rhostname), cstate->chal_name, secret, &secret_len, 1)) { @@ -589,7 +629,7 @@ ChapReceiveResponse(cstate, inp, id, len) MD5Final(hash, &mdContext); /* compare MDs and send the appropriate status */ - if (memcmp (hash, remmd, MD5_SIGNATURE_SIZE) == 0) + if (memcmp(hash, remmd, MD5_SIGNATURE_SIZE) == 0) code = CHAP_SUCCESS; /* they are the same! */ break; @@ -598,27 +638,56 @@ ChapReceiveResponse(cstate, inp, id, len) { int response_offset, response_size; MS_ChapResponse *rmd = (MS_ChapResponse *) remmd; + MS_ChapResponse md; if (remmd_len != MS_CHAP_RESPONSE_LEN) break; /* not even the right length */ - ChapMS(cstate, cstate->challenge, cstate->chal_len, - secret, secret_len); /* Determine which part of response to verify against */ if (rmd->UseNT[0]) { response_offset = offsetof(MS_ChapResponse, NTResp); response_size = sizeof(rmd->NTResp); } else { +#ifdef MSLANMAN response_offset = offsetof(MS_ChapResponse, LANManResp); response_size = sizeof(rmd->LANManResp); +#else + /* Should really propagate this into the error packet. */ + notice("Peer request for LANMAN auth not supported"); + break; +#endif /* MSLANMAN */ } + /* Generate the expected response. */ + ChapMS(cstate, cstate->challenge, secret, secret_len, &md); + /* compare MDs and send the appropriate status */ - if (memcmp(cstate->response + response_offset, - remmd + response_offset, response_size) == 0) + if (memcmp((u_char *) &md + response_offset, + (u_char *) remmd + response_offset, + response_size) == 0) code = CHAP_SUCCESS; /* they are the same! */ break; } + + case CHAP_MICROSOFT_V2: + { + MS_Chap2Response *rmd = (MS_Chap2Response *) remmd; + MS_Chap2Response md; + + if (remmd_len != MS_CHAP2_RESPONSE_LEN) + break; /* not even the right length */ + + /* Generate the expected response and our mutual auth. */ + ChapMS2(cstate, cstate->challenge, rmd->PeerChallenge, + (explicit_remote? remote_name: rhostname), + secret, secret_len, &md, + cstate->saresponse, MS_CHAP2_AUTHENTICATOR); + + /* compare MDs and send the appropriate status */ + if (memcmp(md.NTResp, rmd->NTResp, sizeof(md.NTResp)) == 0) + code = CHAP_SUCCESS; /* yay! */ + break; + } #endif /* CHAPMS */ default: @@ -634,14 +703,15 @@ ChapReceiveResponse(cstate, inp, id, len) old_state = cstate->serverstate; cstate->serverstate = CHAPSS_OPEN; if (old_state == CHAPSS_INITIAL_CHAL) { - auth_peer_success(cstate->unit, PPP_CHAP, rhostname, len); + auth_peer_success(cstate->unit, PPP_CHAP, cstate->chal_type, + rhostname, len); } if (cstate->chal_interval != 0) TIMEOUT(ChapRechallenge, cstate, cstate->chal_interval); notice("CHAP peer authentication succeeded for %q", rhostname); } else { - error("CHAP peer authentication failed for remote host %q", rhostname); + warn("CHAP peer authentication failed for %q", rhostname); cstate->serverstate = CHAPSS_BADAUTH; auth_peer_fail(cstate->unit, PPP_CHAP); } @@ -670,6 +740,37 @@ ChapReceiveSuccess(cstate, inp, id, len) UNTIMEOUT(ChapResponseTimeout, cstate); +#ifdef CHAPMS + /* + * For MS-CHAPv2, we must verify that the peer knows our secret. + */ + if (cstate->resp_type == CHAP_MICROSOFT_V2) { + if ((len >= MS_AUTH_RESPONSE_LENGTH + 2) && !strncmp(inp, "S=", 2)) { + inp += 2; len -= 2; + if (!memcmp(inp, cstate->earesponse, MS_AUTH_RESPONSE_LENGTH)) { + /* Authenticator Response matches. */ + inp += MS_AUTH_RESPONSE_LENGTH; /* Eat it */ + len -= MS_AUTH_RESPONSE_LENGTH; + if ((len >= 3) && !strncmp(inp, " M=", 3)) { + inp += 3; len -= 3; /* Eat the delimiter */ + } else if (len) { + /* Packet has extra text which does not begin " M=" */ + error("MS-CHAPv2 Success packet is badly formed."); + auth_withpeer_fail(cstate->unit, PPP_CHAP); + } + } else { + /* Authenticator Response did not match expected. */ + error("MS-CHAPv2 mutual authentication failed."); + auth_withpeer_fail(cstate->unit, PPP_CHAP); + } + } else { + /* Packet does not start with "S=" */ + error("MS-CHAPv2 Success packet is badly formed."); + auth_withpeer_fail(cstate->unit, PPP_CHAP); + } + } +#endif + /* * Print message. */ @@ -678,7 +779,8 @@ ChapReceiveSuccess(cstate, inp, id, len) cstate->clientstate = CHAPCS_OPEN; - auth_withpeer_success(cstate->unit, PPP_CHAP); + notice("CHAP authentication succeeded"); + auth_withpeer_success(cstate->unit, PPP_CHAP, cstate->resp_type); } @@ -692,22 +794,103 @@ ChapReceiveFailure(cstate, inp, id, len) u_char id; int len; { + u_char *msg; + u_char *p = inp; + if (cstate->clientstate != CHAPCS_RESPONSE) { /* don't know what this is */ CHAPDEBUG(("ChapReceiveFailure: in state %d\n", cstate->clientstate)); return; } +#ifdef CHAPMS + /* We want a null-terminated string for strxxx(). */ + msg = malloc(len + 1); + if (!msg) { + p = NULL; + notice("Out of memory in ChapReceiveFailure"); + goto print_msg; + } + BCOPY(inp, msg, len); + p = msg + len; *p = '\0'; p = msg; +#endif + UNTIMEOUT(ChapResponseTimeout, cstate); +#ifdef CHAPMS + if ((cstate->resp_type == CHAP_MICROSOFT_V2) || + (cstate->resp_type == CHAP_MICROSOFT)) { + int error; + + /* + * Deal with MS-CHAP formatted failure messages; just print the + * M= part (if any). For MS-CHAP we're not really supposed + * to use M=, but it shouldn't hurt. See ChapSendStatus(). + */ + if (!strncmp(p, "E=", 2)) + error = (int) strtol(p, NULL, 10); /* Remember the error code. */ + else + goto print_msg; /* Message is badly formatted. */ + + if (len && ((p = strstr(p, " M=")) != NULL)) { + /* M= field found. */ + p += 3; + } else { + /* No M=; use the error code. */ + switch(error) { + case MS_CHAP_ERROR_RESTRICTED_LOGON_HOURS: + p = "E=646 Restricted logon hours"; + break; + + case MS_CHAP_ERROR_ACCT_DISABLED: + p = "E=647 Account disabled"; + break; + + case MS_CHAP_ERROR_PASSWD_EXPIRED: + p = "E=648 Password expired"; + break; + + case MS_CHAP_ERROR_NO_DIALIN_PERMISSION: + p = "E=649 No dialin permission"; + break; + + case MS_CHAP_ERROR_AUTHENTICATION_FAILURE: + p = "E=691 Authentication failure"; + break; + + case MS_CHAP_ERROR_CHANGING_PASSWORD: + /* Should never see this, we don't support Change Password. */ + p = "E=709 Error changing password"; + break; + + default: + free(msg); + p = msg = malloc(len + 33); + if (!msg) { + novm("ChapReceiveFailure"); + goto print_msg; + } + slprintf(p, len + 33, "Unknown authentication failure: %.*s", + len, inp); + break; + } + } + len = strlen(p); + } +#endif + /* * Print message. */ - if (len > 0) - PRINTMSG(inp, len); +print_msg: + if (len > 0 && p != NULL) + PRINTMSG(p, len); error("CHAP authentication failed"); auth_withpeer_fail(cstate->unit, PPP_CHAP); +#ifdef CHAPMS + if (msg) free(msg); +#endif } @@ -748,6 +931,7 @@ ChapSendChallenge(cstate) /* * ChapSendStatus - Send a status response (ack or nak). + * See RFC 2433 and RFC 2759 for MS-CHAP and MS-CHAPv2 message formats. */ static void ChapSendStatus(cstate, code) @@ -755,13 +939,87 @@ ChapSendStatus(cstate, code) int code; { u_char *outp; - int outlen, msglen; + int i, outlen, msglen; char msg[256]; + char *p, *q; - if (code == CHAP_SUCCESS) - slprintf(msg, sizeof(msg), "Welcome to %s.", hostname); - else - slprintf(msg, sizeof(msg), "I don't like you. Go 'way."); + p = msg; + q = p + sizeof(msg); /* points 1 byte past msg */ + + if (code == CHAP_SUCCESS) { +#ifdef CHAPMS + if (cstate->chal_type == CHAP_MICROSOFT_V2) { + /* + * Per RFC 2759, success message must be formatted as + * "S= M=" + * where + * is the Authenticator Response (mutual auth) + * is a text message + * + * However, some versions of Windows (win98 tested) do not know + * about the M= part (required per RFC 2759) and flag + * it as an error (reported incorrectly as an encryption error + * to the user). Since the RFC requires it, and it can be + * useful information, we supply it if the peer is a conforming + * system. Luckily (?), win98 sets the Flags field to 0x04 + * (contrary to RFC requirements) so we can use that to + * distinguish between conforming and non-conforming systems. + * + * Special thanks to Alex Swiridov for + * help debugging this. + */ + slprintf(p, q - p, "S="); + p += 2; + slprintf(p, q - p, "%s", cstate->saresponse); + p += strlen(cstate->saresponse); + if (cstate->resp_flags != 0) + goto msgdone; + slprintf(p, q - p, " M="); + p += 3; + } +#endif /* CHAPMS */ + + slprintf(p, q - p, "Welcome to %s.", hostname); + } else { +#ifdef CHAPMS + if ((cstate->chal_type == CHAP_MICROSOFT_V2) || + (cstate->chal_type == CHAP_MICROSOFT)) { + /* + * Failure message must be formatted as + * "E=e R=r C=c V=v M=m" + * where + * e = error code (we use 691, ERROR_AUTHENTICATION_FAILURE) + * r = retry (we use 1, ok to retry) + * c = challenge to use for next response, we reuse previous + * v = Change Password version supported, we use 0 + * m = text message + * + * The M=m part is only for MS-CHAPv2, but MS-CHAP should ignore + * any extra text according to RFC 2433. So we'll go the easy + * (read: lazy) route and include it always. Neither win2k nor + * win98 (others untested) display the message to the user anyway. + * They also both ignore the E=e code. + * + * Note that it's safe to reuse the same challenge as we don't + * actually accept another response based on the error message + * (and no clients try to resend a response anyway). + * + * Basically, this whole bit is useless code, even the small + * implementation here is only because of overspecification. + */ + slprintf(p, q - p, "E=691 R=1 C="); + p += 12; + for (i = 0; i < cstate->chal_len; i++) + sprintf(p + i * 2, "%02X", cstate->challenge[i]); + p += cstate->chal_len * 2; + slprintf(p, q - p, " V=0 M="); + p += 7; + } +#endif /* CHAPMS */ + + slprintf(p, q - p, "I don't like you. Go 'way."); + } +msgdone: msglen = strlen(msg); outlen = CHAP_HEADERLEN + msglen; @@ -807,6 +1065,11 @@ ChapGenChallenge(cstate) /* MS-CHAP is fixed to an 8 octet challenge. */ chal_len = 8; break; + + case CHAP_MICROSOFT_V2: + /* MS-CHAPv2 is fixed to a 16 octet challenge. */ + chal_len = 16; + break; #endif default: fatal("ChapGenChallenge: Unsupported challenge type %d", @@ -818,9 +1081,15 @@ ChapGenChallenge(cstate) cstate->chal_id = ++cstate->id; cstate->chal_transmits = 0; - /* generate a random string */ - for (i = 0; i < chal_len; i++) - *ptr++ = (char) (drand48() * 0xff); +#ifdef CHAPMS + if (mschap_challenge) + for (i = 0; i < chal_len; i++) + *ptr++ = mschap_challenge[i]; + else +#endif + /* generate a random string */ + for (i = 0; i < chal_len; i++) + *ptr++ = (char) (drand48() * 0xff); } /*