X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;ds=sidebyside;f=pppd%2Fchap.c;h=2411b95c842a08ff34da66e6eeb977588043d61e;hb=HEAD;hp=b8a5d0f726ffe9f723d1eaf344a52d45b1591ef9;hpb=a3630de20e34796f434a728bfd9cf1a961380c82;p=ppp.git diff --git a/pppd/chap.c b/pppd/chap.c index b8a5d0f..d0009e8 100644 --- a/pppd/chap.c +++ b/pppd/chap.c @@ -1,860 +1,698 @@ /* - * chap.c - Challenge Handshake Authentication Protocol. + * chap-new.c - New CHAP implementation. * - * Copyright (c) 1993 The Australian National University. - * All rights reserved. + * Copyright (c) 2003 Paul Mackerras. All rights reserved. * - * Redistribution and use in source and binary forms are permitted - * provided that the above copyright notice and this paragraph are - * duplicated in all such forms and that any documentation, - * advertising materials, and other materials related to such - * distribution and use acknowledge that the software was developed - * by the Australian National University. The name of the University - * may not be used to endorse or promote products derived from this - * software without specific prior written permission. - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * Copyright (c) 1991 Gregory M. Christy. - * All rights reserved. + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * Redistribution and use in source and binary forms are permitted - * provided that the above copyright notice and this paragraph are - * duplicated in all such forms and that any documentation, - * advertising materials, and other materials related to such - * distribution and use acknowledge that the software was developed - * by Gregory M. Christy. The name of the author may not be used to - * endorse or promote products derived from this software without - * specific prior written permission. + * 2. The name(s) of the authors of this software must not be used to + * endorse or promote products derived from this software without + * prior written permission. * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * 3. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Paul Mackerras + * ". + * + * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#define RCSID "$Id: chap.c,v 1.23 1999/08/13 06:46:11 paulus Exp $" +#define RCSID "$Id: chap-new.c,v 1.9 2007/06/19 02:08:35 carlsonj Exp $" -/* - * TODO: - */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif -#include +#include #include -#include -#include - -#include "pppd.h" +#include "pppd-private.h" +#include "options.h" +#include "session.h" #include "chap.h" -#include "md5.h" -#ifdef CHAPMS +#include "chap-md5.h" + +#ifdef PPP_WITH_CHAPMS #include "chap_ms.h" +#define MDTYPE_ALL (MDTYPE_MICROSOFT_V2 | MDTYPE_MICROSOFT | MDTYPE_MD5) +#else +#define MDTYPE_ALL (MDTYPE_MD5) #endif -static const char rcsid[] = RCSID; +int chap_mdtype_all = MDTYPE_ALL; + +/* Hook for a plugin to validate CHAP challenge */ +chap_verify_hook_fn *chap_verify_hook = NULL; /* - * Command-line options. + * Option variables. */ -static option_t chap_option_list[] = { - { "chap-restart", o_int, &chap[0].timeouttime, - "Set timeout for CHAP" }, - { "chap-max-challenge", o_int, &chap[0].max_transmits, - "Set max #xmits for challenge" }, - { "chap-interval", o_int, &chap[0].chal_interval, - "Set interval for rechallenge" }, -#ifdef MSLANMAN - { "ms-lanman", o_bool, &ms_lanman, - "Use LanMan passwd when using MS-CHAP", 1 }, -#endif - { NULL } -}; +int chap_server_timeout_time = 3; +int chap_max_transmits = 10; +int chap_rechallenge_time = 0; +int chap_client_timeout_time = 60; +int chapms_strip_domain = 0; /* - * Protocol entry points. + * Command-line options. */ -static void ChapInit __P((int)); -static void ChapLowerUp __P((int)); -static void ChapLowerDown __P((int)); -static void ChapInput __P((int, u_char *, int)); -static void ChapProtocolReject __P((int)); -static int ChapPrintPkt __P((u_char *, int, - void (*) __P((void *, char *, ...)), void *)); - -struct protent chap_protent = { - PPP_CHAP, - ChapInit, - ChapInput, - ChapProtocolReject, - ChapLowerUp, - ChapLowerDown, - NULL, - NULL, - ChapPrintPkt, - NULL, - 1, - "CHAP", - NULL, - chap_option_list, - NULL, - NULL, - NULL +static struct option chap_option_list[] = { + { "chap-restart", o_int, &chap_server_timeout_time, + "Set timeout for CHAP (as server)", OPT_PRIO }, + { "chap-max-challenge", o_int, &chap_max_transmits, + "Set max #xmits for challenge", OPT_PRIO }, + { "chap-interval", o_int, &chap_rechallenge_time, + "Set interval for rechallenge", OPT_PRIO }, + { "chap-timeout", o_int, &chap_client_timeout_time, + "Set timeout for CHAP (as client)", OPT_PRIO }, + { "chapms-strip-domain", o_bool, &chapms_strip_domain, + "Strip the domain prefix before the Username", 1 }, + { NULL } }; -chap_state chap[NUM_PPP]; /* CHAP state; one for each unit */ +/* + * Internal state. + */ +static struct chap_client_state { + int flags; + char *name; + struct chap_digest_type *digest; + unsigned char priv[64]; /* private area for digest's use */ +} client; -static void ChapChallengeTimeout __P((void *)); -static void ChapResponseTimeout __P((void *)); -static void ChapReceiveChallenge __P((chap_state *, u_char *, int, int)); -static void ChapRechallenge __P((void *)); -static void ChapReceiveResponse __P((chap_state *, u_char *, int, int)); -static void ChapReceiveSuccess __P((chap_state *, u_char *, int, int)); -static void ChapReceiveFailure __P((chap_state *, u_char *, int, int)); -static void ChapSendStatus __P((chap_state *, int)); -static void ChapSendChallenge __P((chap_state *)); -static void ChapSendResponse __P((chap_state *)); -static void ChapGenChallenge __P((chap_state *)); +/* + * These limits apply to challenge and response packets we send. + * The +4 is the +1 that we actually need rounded up. + */ +#define CHAL_MAX_PKTLEN (PPP_HDRLEN + CHAP_HDRLEN + 4 + MAX_CHALLENGE_LEN + MAXNAMELEN) +#define RESP_MAX_PKTLEN (PPP_HDRLEN + CHAP_HDRLEN + 4 + MAX_RESPONSE_LEN + MAXNAMELEN) + +static struct chap_server_state { + int flags; + int id; + char *name; + struct chap_digest_type *digest; + int challenge_xmits; + int challenge_pktlen; + unsigned char challenge[CHAL_MAX_PKTLEN]; + char message[256]; +} server; + +/* Values for flags in chap_client_state and chap_server_state */ +#define LOWERUP 1 +#define AUTH_STARTED 2 +#define AUTH_DONE 4 +#define AUTH_FAILED 8 +#define TIMEOUT_PENDING 0x10 +#define CHALLENGE_VALID 0x20 -extern double drand48 __P((void)); -extern void srand48 __P((long)); +/* + * Prototypes. + */ +static void chap_init(int unit); +static void chap_lowerup(int unit); +static void chap_lowerdown(int unit); +static void chap_server_timeout(void *arg); +static void chap_client_timeout(void *arg); +static void chap_generate_challenge(struct chap_server_state *ss); +static void chap_handle_response(struct chap_server_state *ss, int code, + unsigned char *pkt, int len); +static chap_verify_hook_fn chap_verify_response; +static void chap_respond(struct chap_client_state *cs, int id, + unsigned char *pkt, int len); +static void chap_handle_status(struct chap_client_state *cs, int code, int id, + unsigned char *pkt, int len); +static void chap_protrej(int unit); +static void chap_input(int unit, unsigned char *pkt, int pktlen); +static int chap_print_pkt(unsigned char *p, int plen, + void (*printer)(void *, char *, ...), void *arg); + +/* List of digest types that we know about */ +static struct chap_digest_type *chap_digests; /* - * ChapInit - Initialize a CHAP unit. + * chap_init - reset to initial state. */ static void -ChapInit(unit) - int unit; +chap_init(int unit) { - chap_state *cstate = &chap[unit]; - - BZERO(cstate, sizeof(*cstate)); - cstate->unit = unit; - cstate->clientstate = CHAPCS_INITIAL; - cstate->serverstate = CHAPSS_INITIAL; - cstate->timeouttime = CHAP_DEFTIMEOUT; - cstate->max_transmits = CHAP_DEFTRANSMITS; - /* random number generator is initialized in magic_init */ -} + memset(&client, 0, sizeof(client)); + memset(&server, 0, sizeof(server)); + chap_md5_init(); +#ifdef PPP_WITH_CHAPMS + chapms_init(); +#endif +} /* - * ChapAuthWithPeer - Authenticate us with our peer (start client). - * + * Add a new digest type to the list. */ void -ChapAuthWithPeer(unit, our_name, digest) - int unit; - char *our_name; - int digest; +chap_register_digest(struct chap_digest_type *dp) { - chap_state *cstate = &chap[unit]; - - cstate->resp_name = our_name; - cstate->resp_type = digest; - - if (cstate->clientstate == CHAPCS_INITIAL || - cstate->clientstate == CHAPCS_PENDING) { - /* lower layer isn't up - wait until later */ - cstate->clientstate = CHAPCS_PENDING; - return; - } - - /* - * We get here as a result of LCP coming up. - * So even if CHAP was open before, we will - * have to re-authenticate ourselves. - */ - cstate->clientstate = CHAPCS_LISTEN; + dp->next = chap_digests; + chap_digests = dp; } - /* - * ChapAuthPeer - Authenticate our peer (start server). + * Lookup a digest type by code */ -void -ChapAuthPeer(unit, our_name, digest) - int unit; - char *our_name; - int digest; -{ - chap_state *cstate = &chap[unit]; - - cstate->chal_name = our_name; - cstate->chal_type = digest; - - if (cstate->serverstate == CHAPSS_INITIAL || - cstate->serverstate == CHAPSS_PENDING) { - /* lower layer isn't up - wait until later */ - cstate->serverstate = CHAPSS_PENDING; - return; - } - - ChapGenChallenge(cstate); - ChapSendChallenge(cstate); /* crank it up dude! */ - cstate->serverstate = CHAPSS_INITIAL_CHAL; +struct chap_digest_type * +chap_find_digest(int digest_code) { + struct chap_digest_type *dp = NULL; + for (dp = chap_digests; dp != NULL; dp = dp->next) + if (dp->code == digest_code) + break; + return dp; } - /* - * ChapChallengeTimeout - Timeout expired on sending challenge. + * chap_lowerup - we can start doing stuff now. */ static void -ChapChallengeTimeout(arg) - void *arg; +chap_lowerup(int unit) { - chap_state *cstate = (chap_state *) arg; - - /* if we aren't sending challenges, don't worry. then again we */ - /* probably shouldn't be here either */ - if (cstate->serverstate != CHAPSS_INITIAL_CHAL && - cstate->serverstate != CHAPSS_RECHALLENGE) - return; - - if (cstate->chal_transmits >= cstate->max_transmits) { - /* give up on peer */ - error("Peer failed to respond to CHAP challenge"); - cstate->serverstate = CHAPSS_BADAUTH; - auth_peer_fail(cstate->unit, PPP_CHAP); - return; - } - - ChapSendChallenge(cstate); /* Re-send challenge */ -} + struct chap_client_state *cs = &client; + struct chap_server_state *ss = &server; + cs->flags |= LOWERUP; + ss->flags |= LOWERUP; + if (ss->flags & AUTH_STARTED) + chap_server_timeout(ss); +} -/* - * ChapResponseTimeout - Timeout expired on sending response. - */ static void -ChapResponseTimeout(arg) - void *arg; +chap_lowerdown(int unit) { - chap_state *cstate = (chap_state *) arg; - - /* if we aren't sending a response, don't worry. */ - if (cstate->clientstate != CHAPCS_RESPONSE) - return; - - ChapSendResponse(cstate); /* re-send response */ + struct chap_client_state *cs = &client; + struct chap_server_state *ss = &server; + + if (cs->flags & TIMEOUT_PENDING) + UNTIMEOUT(chap_client_timeout, cs); + cs->flags = 0; + if (ss->flags & TIMEOUT_PENDING) + UNTIMEOUT(chap_server_timeout, ss); + ss->flags = 0; } - /* - * ChapRechallenge - Time to challenge the peer again. + * chap_auth_peer - Start authenticating the peer. + * If the lower layer is already up, we start sending challenges, + * otherwise we wait for the lower layer to come up. */ -static void -ChapRechallenge(arg) - void *arg; +void +chap_auth_peer(int unit, char *our_name, int digest_code) { - chap_state *cstate = (chap_state *) arg; + struct chap_server_state *ss = &server; + struct chap_digest_type *dp; - /* if we aren't sending a response, don't worry. */ - if (cstate->serverstate != CHAPSS_OPEN) - return; + if (ss->flags & AUTH_STARTED) { + error("CHAP: peer authentication already started!"); + return; + } - ChapGenChallenge(cstate); - ChapSendChallenge(cstate); - cstate->serverstate = CHAPSS_RECHALLENGE; + dp = chap_find_digest(digest_code); + if (dp == NULL) + fatal("CHAP digest 0x%x requested but not available", + digest_code); + + ss->digest = dp; + ss->name = our_name; + /* Start with a random ID value */ + ss->id = (unsigned char)(drand48() * 256); + ss->flags |= AUTH_STARTED; + if (ss->flags & LOWERUP) + chap_server_timeout(ss); } - /* - * ChapLowerUp - The lower layer is up. - * - * Start up if we have pending requests. + * chap_auth_with_peer - Prepare to authenticate ourselves to the peer. + * There isn't much to do until we receive a challenge. */ -static void -ChapLowerUp(unit) - int unit; +void +chap_auth_with_peer(int unit, char *our_name, int digest_code) { - chap_state *cstate = &chap[unit]; - - if (cstate->clientstate == CHAPCS_INITIAL) - cstate->clientstate = CHAPCS_CLOSED; - else if (cstate->clientstate == CHAPCS_PENDING) - cstate->clientstate = CHAPCS_LISTEN; - - if (cstate->serverstate == CHAPSS_INITIAL) - cstate->serverstate = CHAPSS_CLOSED; - else if (cstate->serverstate == CHAPSS_PENDING) { - ChapGenChallenge(cstate); - ChapSendChallenge(cstate); - cstate->serverstate = CHAPSS_INITIAL_CHAL; - } -} + struct chap_client_state *cs = &client; + struct chap_digest_type *dp; - -/* - * ChapLowerDown - The lower layer is down. - * - * Cancel all timeouts. - */ -static void -ChapLowerDown(unit) - int unit; -{ - chap_state *cstate = &chap[unit]; - - /* Timeout(s) pending? Cancel if so. */ - if (cstate->serverstate == CHAPSS_INITIAL_CHAL || - cstate->serverstate == CHAPSS_RECHALLENGE) - UNTIMEOUT(ChapChallengeTimeout, cstate); - else if (cstate->serverstate == CHAPSS_OPEN - && cstate->chal_interval != 0) - UNTIMEOUT(ChapRechallenge, cstate); - if (cstate->clientstate == CHAPCS_RESPONSE) - UNTIMEOUT(ChapResponseTimeout, cstate); - - cstate->clientstate = CHAPCS_INITIAL; - cstate->serverstate = CHAPSS_INITIAL; + if (cs->flags & AUTH_STARTED) { + error("CHAP: authentication with peer already started!"); + return; + } + for (dp = chap_digests; dp != NULL; dp = dp->next) + if (dp->code == digest_code) + break; + if (dp == NULL) + fatal("CHAP digest 0x%x requested but not available", + digest_code); + + cs->digest = dp; + cs->name = our_name; + cs->flags |= AUTH_STARTED | TIMEOUT_PENDING; + TIMEOUT(chap_client_timeout, cs, chap_client_timeout_time); } - /* - * ChapProtocolReject - Peer doesn't grok CHAP. + * chap_server_timeout - It's time to send another challenge to the peer. + * This could be either a retransmission of a previous challenge, + * or a new challenge to start re-authentication. */ static void -ChapProtocolReject(unit) - int unit; +chap_server_timeout(void *arg) { - chap_state *cstate = &chap[unit]; - - if (cstate->serverstate != CHAPSS_INITIAL && - cstate->serverstate != CHAPSS_CLOSED) - auth_peer_fail(unit, PPP_CHAP); - if (cstate->clientstate != CHAPCS_INITIAL && - cstate->clientstate != CHAPCS_CLOSED) - auth_withpeer_fail(unit, PPP_CHAP); - ChapLowerDown(unit); /* shutdown chap */ -} + struct chap_server_state *ss = arg; + + ss->flags &= ~TIMEOUT_PENDING; + if ((ss->flags & CHALLENGE_VALID) == 0) { + ss->challenge_xmits = 0; + chap_generate_challenge(ss); + ss->flags |= CHALLENGE_VALID; + } else if (ss->challenge_xmits >= chap_max_transmits) { + ss->flags &= ~CHALLENGE_VALID; + ss->flags |= AUTH_DONE | AUTH_FAILED; + auth_peer_fail(0, PPP_CHAP); + return; + } + output(0, ss->challenge, ss->challenge_pktlen); + ++ss->challenge_xmits; + ss->flags |= TIMEOUT_PENDING; + TIMEOUT(chap_server_timeout, arg, chap_server_timeout_time); +} -/* - * ChapInput - Input CHAP packet. - */ +/* chap_client_timeout - Authentication with peer timed out. */ static void -ChapInput(unit, inpacket, packet_len) - int unit; - u_char *inpacket; - int packet_len; +chap_client_timeout(void *arg) { - chap_state *cstate = &chap[unit]; - u_char *inp; - u_char code, id; - int len; - - /* - * Parse header (code, id and length). - * If packet too short, drop it. - */ - inp = inpacket; - if (packet_len < CHAP_HEADERLEN) { - CHAPDEBUG(("ChapInput: rcvd short header.")); - return; - } - GETCHAR(code, inp); - GETCHAR(id, inp); - GETSHORT(len, inp); - if (len < CHAP_HEADERLEN) { - CHAPDEBUG(("ChapInput: rcvd illegal length.")); - return; - } - if (len > packet_len) { - CHAPDEBUG(("ChapInput: rcvd short packet.")); - return; - } - len -= CHAP_HEADERLEN; - - /* - * Action depends on code (as in fact it usually does :-). - */ - switch (code) { - case CHAP_CHALLENGE: - ChapReceiveChallenge(cstate, inp, id, len); - break; - - case CHAP_RESPONSE: - ChapReceiveResponse(cstate, inp, id, len); - break; - - case CHAP_FAILURE: - ChapReceiveFailure(cstate, inp, id, len); - break; - - case CHAP_SUCCESS: - ChapReceiveSuccess(cstate, inp, id, len); - break; - - default: /* Need code reject? */ - warn("Unknown CHAP code (%d) received.", code); - break; - } -} + struct chap_client_state *cs = arg; + cs->flags &= ~TIMEOUT_PENDING; + cs->flags |= AUTH_DONE | AUTH_FAILED; + error("CHAP authentication timed out"); + auth_withpeer_fail(0, PPP_CHAP); +} /* - * ChapReceiveChallenge - Receive Challenge and send Response. + * chap_generate_challenge - generate a challenge string and format + * the challenge packet in ss->challenge_pkt. */ static void -ChapReceiveChallenge(cstate, inp, id, len) - chap_state *cstate; - u_char *inp; - int id; - int len; +chap_generate_challenge(struct chap_server_state *ss) { - int rchallenge_len; - u_char *rchallenge; - int secret_len; - char secret[MAXSECRETLEN]; - char rhostname[256]; - MD5_CTX mdContext; - u_char hash[MD5_SIGNATURE_SIZE]; - - if (cstate->clientstate == CHAPCS_CLOSED || - cstate->clientstate == CHAPCS_PENDING) { - CHAPDEBUG(("ChapReceiveChallenge: in state %d", cstate->clientstate)); - return; - } - - if (len < 2) { - CHAPDEBUG(("ChapReceiveChallenge: rcvd short packet.")); - return; - } - - GETCHAR(rchallenge_len, inp); - len -= sizeof (u_char) + rchallenge_len; /* now name field length */ - if (len < 0) { - CHAPDEBUG(("ChapReceiveChallenge: rcvd short packet.")); - return; - } - rchallenge = inp; - INCPTR(rchallenge_len, inp); - - if (len >= sizeof(rhostname)) - len = sizeof(rhostname) - 1; - BCOPY(inp, rhostname, len); - rhostname[len] = '\000'; - - /* Microsoft doesn't send their name back in the PPP packet */ - if (explicit_remote || (remote_name[0] != 0 && rhostname[0] == 0)) { - strlcpy(rhostname, remote_name, sizeof(rhostname)); - CHAPDEBUG(("ChapReceiveChallenge: using '%q' as remote name", - rhostname)); - } - - /* get secret for authenticating ourselves with the specified host */ - if (!get_secret(cstate->unit, cstate->resp_name, rhostname, - secret, &secret_len, 0)) { - secret_len = 0; /* assume null secret if can't find one */ - warn("No CHAP secret found for authenticating us to %q", rhostname); - } - - /* cancel response send timeout if necessary */ - if (cstate->clientstate == CHAPCS_RESPONSE) - UNTIMEOUT(ChapResponseTimeout, cstate); - - cstate->resp_id = id; - cstate->resp_transmits = 0; - - /* generate MD based on negotiated type */ - switch (cstate->resp_type) { - - case CHAP_DIGEST_MD5: - MD5Init(&mdContext); - MD5Update(&mdContext, &cstate->resp_id, 1); - MD5Update(&mdContext, secret, secret_len); - MD5Update(&mdContext, rchallenge, rchallenge_len); - MD5Final(hash, &mdContext); - BCOPY(hash, cstate->response, MD5_SIGNATURE_SIZE); - cstate->resp_length = MD5_SIGNATURE_SIZE; - break; - -#ifdef CHAPMS - case CHAP_MICROSOFT: - ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len); - break; -#endif - - default: - CHAPDEBUG(("unknown digest type %d", cstate->resp_type)); - return; - } - - BZERO(secret, sizeof(secret)); - ChapSendResponse(cstate); + int clen = 1, nlen, len; + unsigned char *p; + + p = ss->challenge; + MAKEHEADER(p, PPP_CHAP); + p += CHAP_HDRLEN; + ss->digest->generate_challenge(p); + clen = *p; + nlen = strlen(ss->name); + memcpy(p + 1 + clen, ss->name, nlen); + + len = CHAP_HDRLEN + 1 + clen + nlen; + ss->challenge_pktlen = PPP_HDRLEN + len; + + p = ss->challenge + PPP_HDRLEN; + p[0] = CHAP_CHALLENGE; + p[1] = ++ss->id; + p[2] = len >> 8; + p[3] = len; } - /* - * ChapReceiveResponse - Receive and process response. + * chap_handle_response - check the response to our challenge. */ static void -ChapReceiveResponse(cstate, inp, id, len) - chap_state *cstate; - u_char *inp; - int id; - int len; +chap_handle_response(struct chap_server_state *ss, int id, + unsigned char *pkt, int len) { - u_char *remmd, remmd_len; - int secret_len, old_state; - int code; - char rhostname[256]; - MD5_CTX mdContext; - char secret[MAXSECRETLEN]; - u_char hash[MD5_SIGNATURE_SIZE]; - - if (cstate->serverstate == CHAPSS_CLOSED || - cstate->serverstate == CHAPSS_PENDING) { - CHAPDEBUG(("ChapReceiveResponse: in state %d", cstate->serverstate)); - return; - } - - if (id != cstate->chal_id) - return; /* doesn't match ID of last challenge */ - - /* - * If we have received a duplicate or bogus Response, - * we have to send the same answer (Success/Failure) - * as we did for the first Response we saw. - */ - if (cstate->serverstate == CHAPSS_OPEN) { - ChapSendStatus(cstate, CHAP_SUCCESS); - return; - } - if (cstate->serverstate == CHAPSS_BADAUTH) { - ChapSendStatus(cstate, CHAP_FAILURE); - return; - } - - if (len < 2) { - CHAPDEBUG(("ChapReceiveResponse: rcvd short packet.")); - return; - } - GETCHAR(remmd_len, inp); /* get length of MD */ - remmd = inp; /* get pointer to MD */ - INCPTR(remmd_len, inp); - - len -= sizeof (u_char) + remmd_len; - if (len < 0) { - CHAPDEBUG(("ChapReceiveResponse: rcvd short packet.")); - return; - } - - UNTIMEOUT(ChapChallengeTimeout, cstate); - - if (len >= sizeof(rhostname)) - len = sizeof(rhostname) - 1; - BCOPY(inp, rhostname, len); - rhostname[len] = '\000'; - - /* - * Get secret for authenticating them with us, - * do the hash ourselves, and compare the result. - */ - code = CHAP_FAILURE; - if (!get_secret(cstate->unit, (explicit_remote? remote_name: rhostname), - cstate->chal_name, secret, &secret_len, 1)) { - warn("No CHAP secret found for authenticating %q", rhostname); - } else { - - /* generate MD based on negotiated type */ - switch (cstate->chal_type) { - - case CHAP_DIGEST_MD5: /* only MD5 is defined for now */ - if (remmd_len != MD5_SIGNATURE_SIZE) - break; /* it's not even the right length */ - MD5Init(&mdContext); - MD5Update(&mdContext, &cstate->chal_id, 1); - MD5Update(&mdContext, secret, secret_len); - MD5Update(&mdContext, cstate->challenge, cstate->chal_len); - MD5Final(hash, &mdContext); - - /* compare local and remote MDs and send the appropriate status */ - if (memcmp (hash, remmd, MD5_SIGNATURE_SIZE) == 0) - code = CHAP_SUCCESS; /* they are the same! */ - break; - - default: - CHAPDEBUG(("unknown digest type %d", cstate->chal_type)); - } - } - - BZERO(secret, sizeof(secret)); - ChapSendStatus(cstate, code); - - if (code == CHAP_SUCCESS) { - old_state = cstate->serverstate; - cstate->serverstate = CHAPSS_OPEN; - if (old_state == CHAPSS_INITIAL_CHAL) { - auth_peer_success(cstate->unit, PPP_CHAP, rhostname, len); + int response_len, ok, mlen; + unsigned char *response, *p; + char *name = NULL; + chap_verify_hook_fn *verifier; + char rname[MAXNAMELEN+1]; + + if ((ss->flags & LOWERUP) == 0) + return; + if (id != ss->challenge[PPP_HDRLEN+1] || len < 2) + return; + if (ss->flags & CHALLENGE_VALID) { + response = pkt; + GETCHAR(response_len, pkt); + len -= response_len + 1; /* length of name */ + name = (char *)pkt + response_len; + if (len < 0) + return; + + if (ss->flags & TIMEOUT_PENDING) { + ss->flags &= ~TIMEOUT_PENDING; + UNTIMEOUT(chap_server_timeout, ss); + } + + if (explicit_remote) { + name = remote_name; + } else { + /* Null terminate and clean remote name. */ + slprintf(rname, sizeof(rname), "%.*v", len, name); + name = rname; + + /* strip the MS domain name */ + if (chapms_strip_domain && strrchr(rname, '\\')) { + char tmp[MAXNAMELEN+1]; + + strcpy(tmp, strrchr(rname, '\\') + 1); + strcpy(rname, tmp); + } + } + + if (chap_verify_hook) + verifier = chap_verify_hook; + else + verifier = chap_verify_response; + ok = (*verifier)(name, ss->name, id, ss->digest, + ss->challenge + PPP_HDRLEN + CHAP_HDRLEN, + response, ss->message, sizeof(ss->message)); + if (!ok || !auth_number()) { + ss->flags |= AUTH_FAILED; + warn("Peer %q failed CHAP authentication", name); + } + } else if ((ss->flags & AUTH_DONE) == 0) + return; + + /* send the response */ + p = outpacket_buf; + MAKEHEADER(p, PPP_CHAP); + mlen = strlen(ss->message); + len = CHAP_HDRLEN + mlen; + p[0] = (ss->flags & AUTH_FAILED)? CHAP_FAILURE: CHAP_SUCCESS; + p[1] = id; + p[2] = len >> 8; + p[3] = len; + if (mlen > 0) + memcpy(p + CHAP_HDRLEN, ss->message, mlen); + output(0, outpacket_buf, PPP_HDRLEN + len); + + if (ss->flags & CHALLENGE_VALID) { + ss->flags &= ~CHALLENGE_VALID; + if (!(ss->flags & AUTH_DONE) && !(ss->flags & AUTH_FAILED)) { + /* + * Auth is OK, so now we need to check session restrictions + * to ensure everything is OK, but only if we used a + * plugin, and only if we're configured to check. This + * allows us to do PAM checks on PPP servers that + * authenticate against ActiveDirectory, and use AD for + * account info (like when using Winbind integrated with + * PAM). + */ + if (session_mgmt && + session_check(name, NULL, devnam, NULL) == 0) { + ss->flags |= AUTH_FAILED; + warn("Peer %q failed CHAP Session verification", name); + } + } + if (ss->flags & AUTH_FAILED) { + auth_peer_fail(0, PPP_CHAP); + } else { + if ((ss->flags & AUTH_DONE) == 0) + auth_peer_success(0, PPP_CHAP, + ss->digest->code, + name, strlen(name)); + if (chap_rechallenge_time) { + ss->flags |= TIMEOUT_PENDING; + TIMEOUT(chap_server_timeout, ss, + chap_rechallenge_time); + } + } + ss->flags |= AUTH_DONE; } - 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); - cstate->serverstate = CHAPSS_BADAUTH; - auth_peer_fail(cstate->unit, PPP_CHAP); - } } /* - * ChapReceiveSuccess - Receive Success + * chap_verify_response - check whether the peer's response matches + * what we think it should be. Returns 1 if it does (authentication + * succeeded), or 0 if it doesn't. */ -static void -ChapReceiveSuccess(cstate, inp, id, len) - chap_state *cstate; - u_char *inp; - u_char id; - int len; +static int +chap_verify_response(char *name, char *ourname, int id, + struct chap_digest_type *digest, + unsigned char *challenge, unsigned char *response, + char *message, int message_space) { + int ok; + unsigned char secret[MAXSECRETLEN]; + int secret_len; + + /* Get the secret that the peer is supposed to know */ + if (!get_secret(0, name, ourname, (char *)secret, &secret_len, 1)) { + error("No CHAP secret found for authenticating %q", name); + return 0; + } - if (cstate->clientstate == CHAPCS_OPEN) - /* presumably an answer to a duplicate response */ - return; - - if (cstate->clientstate != CHAPCS_RESPONSE) { - /* don't know what this is */ - CHAPDEBUG(("ChapReceiveSuccess: in state %d\n", cstate->clientstate)); - return; - } - - UNTIMEOUT(ChapResponseTimeout, cstate); - - /* - * Print message. - */ - if (len > 0) - PRINTMSG(inp, len); - - cstate->clientstate = CHAPCS_OPEN; + ok = digest->verify_response(id, name, secret, secret_len, challenge, + response, message, message_space); + memset(secret, 0, sizeof(secret)); - auth_withpeer_success(cstate->unit, PPP_CHAP); + return ok; } - /* - * ChapReceiveFailure - Receive failure. + * chap_respond - Generate and send a response to a challenge. */ static void -ChapReceiveFailure(cstate, inp, id, len) - chap_state *cstate; - u_char *inp; - u_char id; - int len; +chap_respond(struct chap_client_state *cs, int id, + unsigned char *pkt, int len) { - if (cstate->clientstate != CHAPCS_RESPONSE) { - /* don't know what this is */ - CHAPDEBUG(("ChapReceiveFailure: in state %d\n", cstate->clientstate)); - return; - } - - UNTIMEOUT(ChapResponseTimeout, cstate); - - /* - * Print message. - */ - if (len > 0) - PRINTMSG(inp, len); - - error("CHAP authentication failed"); - auth_withpeer_fail(cstate->unit, PPP_CHAP); -} - - -/* - * ChapSendChallenge - Send an Authenticate challenge. - */ -static void -ChapSendChallenge(cstate) - chap_state *cstate; -{ - u_char *outp; - int chal_len, name_len; - int outlen; - - chal_len = cstate->chal_len; - name_len = strlen(cstate->chal_name); - outlen = CHAP_HEADERLEN + sizeof (u_char) + chal_len + name_len; - outp = outpacket_buf; + int clen, nlen; + int secret_len; + unsigned char *p; + unsigned char response[RESP_MAX_PKTLEN]; + char rname[MAXNAMELEN+1]; + char secret[MAXSECRETLEN+1]; + + if ((cs->flags & (LOWERUP | AUTH_STARTED)) != (LOWERUP | AUTH_STARTED)) + return; /* not ready */ + if (len < 2 || len < pkt[0] + 1) + return; /* too short */ + clen = pkt[0]; + nlen = len - (clen + 1); + + /* Null terminate and clean remote name. */ + slprintf(rname, sizeof(rname), "%.*v", nlen, pkt + clen + 1); + + /* Microsoft doesn't send their name back in the PPP packet */ + if (explicit_remote || (remote_name[0] != 0 && rname[0] == 0)) + strlcpy(rname, remote_name, sizeof(rname)); + + /* get secret for authenticating ourselves with the specified host */ + if (!get_secret(0, cs->name, rname, secret, &secret_len, 0)) { + secret_len = 0; /* assume null secret if can't find one */ + warn("No CHAP secret found for authenticating us to %q", rname); + } - MAKEHEADER(outp, PPP_CHAP); /* paste in a CHAP header */ + p = response; + MAKEHEADER(p, PPP_CHAP); + p += CHAP_HDRLEN; - PUTCHAR(CHAP_CHALLENGE, outp); - PUTCHAR(cstate->chal_id, outp); - PUTSHORT(outlen, outp); + cs->digest->make_response(p, id, cs->name, pkt, + secret, secret_len, cs->priv); + memset(secret, 0, secret_len); - PUTCHAR(chal_len, outp); /* put length of challenge */ - BCOPY(cstate->challenge, outp, chal_len); - INCPTR(chal_len, outp); + clen = *p; + nlen = strlen(cs->name); + memcpy(p + clen + 1, cs->name, nlen); - BCOPY(cstate->chal_name, outp, name_len); /* append hostname */ + p = response + PPP_HDRLEN; + len = CHAP_HDRLEN + clen + 1 + nlen; + p[0] = CHAP_RESPONSE; + p[1] = id; + p[2] = len >> 8; + p[3] = len; - output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN); - - TIMEOUT(ChapChallengeTimeout, cstate, cstate->timeouttime); - ++cstate->chal_transmits; + output(0, response, PPP_HDRLEN + len); } - -/* - * ChapSendStatus - Send a status response (ack or nak). - */ static void -ChapSendStatus(cstate, code) - chap_state *cstate; - int code; +chap_handle_status(struct chap_client_state *cs, int code, int id, + unsigned char *pkt, int len) { - u_char *outp; - int outlen, msglen; - char msg[256]; - - if (code == CHAP_SUCCESS) - slprintf(msg, sizeof(msg), "Welcome to %s.", hostname); - else - slprintf(msg, sizeof(msg), "I don't like you. Go 'way."); - msglen = strlen(msg); - - outlen = CHAP_HEADERLEN + msglen; - outp = outpacket_buf; - - MAKEHEADER(outp, PPP_CHAP); /* paste in a header */ - - PUTCHAR(code, outp); - PUTCHAR(cstate->chal_id, outp); - PUTSHORT(outlen, outp); - BCOPY(msg, outp, msglen); - output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN); + const char *msg = NULL; + + if ((cs->flags & (AUTH_DONE|AUTH_STARTED|LOWERUP)) + != (AUTH_STARTED|LOWERUP)) + return; + cs->flags |= AUTH_DONE; + + UNTIMEOUT(chap_client_timeout, cs); + cs->flags &= ~TIMEOUT_PENDING; + + if (code == CHAP_SUCCESS) { + /* used for MS-CHAP v2 mutual auth, yuck */ + if (cs->digest->check_success != NULL) { + if (!(*cs->digest->check_success)(id, pkt, len)) + code = CHAP_FAILURE; + } else + msg = "CHAP authentication succeeded"; + } else { + if (cs->digest->handle_failure != NULL) + (*cs->digest->handle_failure)(pkt, len); + else + msg = "CHAP authentication failed"; + } + if (msg) { + if (len > 0) + info("%s: %.*v", msg, len, pkt); + else + info("%s", msg); + } + if (code == CHAP_SUCCESS) + auth_withpeer_success(0, PPP_CHAP, cs->digest->code); + else { + cs->flags |= AUTH_FAILED; + error("CHAP authentication failed"); + auth_withpeer_fail(0, PPP_CHAP); + } } -/* - * ChapGenChallenge is used to generate a pseudo-random challenge string of - * a pseudo-random length between min_len and max_len. The challenge - * string and its length are stored in *cstate, and various other fields of - * *cstate are initialized. - */ - static void -ChapGenChallenge(cstate) - chap_state *cstate; +chap_input(int unit, unsigned char *pkt, int pktlen) { - int chal_len; - u_char *ptr = cstate->challenge; - unsigned int i; - - /* pick a random challenge length between MIN_CHALLENGE_LENGTH and - MAX_CHALLENGE_LENGTH */ - chal_len = (unsigned) ((drand48() * - (MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH)) + - MIN_CHALLENGE_LENGTH); - cstate->chal_len = chal_len; - cstate->chal_id = ++cstate->id; - cstate->chal_transmits = 0; - - /* generate a random string */ - for (i = 0; i < chal_len; i++ ) - *ptr++ = (char) (drand48() * 0xff); + struct chap_client_state *cs = &client; + struct chap_server_state *ss = &server; + unsigned char code, id; + int len; + + if (pktlen < CHAP_HDRLEN) + return; + GETCHAR(code, pkt); + GETCHAR(id, pkt); + GETSHORT(len, pkt); + if (len < CHAP_HDRLEN || len > pktlen) + return; + len -= CHAP_HDRLEN; + + switch (code) { + case CHAP_CHALLENGE: + chap_respond(cs, id, pkt, len); + break; + case CHAP_RESPONSE: + chap_handle_response(ss, id, pkt, len); + break; + case CHAP_FAILURE: + case CHAP_SUCCESS: + chap_handle_status(cs, code, id, pkt, len); + break; + } } -/* - * ChapSendResponse - send a response packet with values as specified - * in *cstate. - */ -/* ARGSUSED */ static void -ChapSendResponse(cstate) - chap_state *cstate; +chap_protrej(int unit) { - u_char *outp; - int outlen, md_len, name_len; - - md_len = cstate->resp_length; - name_len = strlen(cstate->resp_name); - outlen = CHAP_HEADERLEN + sizeof (u_char) + md_len + name_len; - outp = outpacket_buf; + struct chap_client_state *cs = &client; + struct chap_server_state *ss = &server; - MAKEHEADER(outp, PPP_CHAP); - - PUTCHAR(CHAP_RESPONSE, outp); /* we are a response */ - PUTCHAR(cstate->resp_id, outp); /* copy id from challenge packet */ - PUTSHORT(outlen, outp); /* packet length */ - - PUTCHAR(md_len, outp); /* length of MD */ - BCOPY(cstate->response, outp, md_len); /* copy MD to buffer */ - INCPTR(md_len, outp); - - BCOPY(cstate->resp_name, outp, name_len); /* append our name */ - - /* send the packet */ - output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN); - - cstate->clientstate = CHAPCS_RESPONSE; - TIMEOUT(ChapResponseTimeout, cstate, cstate->timeouttime); - ++cstate->resp_transmits; + if (ss->flags & TIMEOUT_PENDING) { + ss->flags &= ~TIMEOUT_PENDING; + UNTIMEOUT(chap_server_timeout, ss); + } + if (ss->flags & AUTH_STARTED) { + ss->flags = 0; + auth_peer_fail(0, PPP_CHAP); + } + if ((cs->flags & (AUTH_STARTED|AUTH_DONE)) == AUTH_STARTED) { + cs->flags &= ~AUTH_STARTED; + error("CHAP authentication failed due to protocol-reject"); + auth_withpeer_fail(0, PPP_CHAP); + } } /* - * ChapPrintPkt - print the contents of a CHAP packet. + * chap_print_pkt - print the contents of a CHAP packet. */ -static char *ChapCodenames[] = { - "Challenge", "Response", "Success", "Failure" +static char *chap_code_names[] = { + "Challenge", "Response", "Success", "Failure" }; static int -ChapPrintPkt(p, plen, printer, arg) - u_char *p; - int plen; - void (*printer) __P((void *, char *, ...)); - void *arg; +chap_print_pkt(unsigned char *p, int plen, + void (*printer)(void *, char *, ...), void *arg) { - int code, id, len; - int clen, nlen; - u_char x; - - if (plen < CHAP_HEADERLEN) - return 0; - GETCHAR(code, p); - GETCHAR(id, p); - GETSHORT(len, p); - if (len < CHAP_HEADERLEN || len > plen) - return 0; - - if (code >= 1 && code <= sizeof(ChapCodenames) / sizeof(char *)) - printer(arg, " %s", ChapCodenames[code-1]); - else - printer(arg, " code=0x%x", code); - printer(arg, " id=0x%x", id); - len -= CHAP_HEADERLEN; - switch (code) { - case CHAP_CHALLENGE: - case CHAP_RESPONSE: - if (len < 1) - break; - clen = p[0]; - if (len < clen + 1) - break; - ++p; - nlen = len - clen - 1; - printer(arg, " <"); - for (; clen > 0; --clen) { - GETCHAR(x, p); - printer(arg, "%.2x", x); - } - printer(arg, ">, name = "); - print_string((char *)p, nlen, printer, arg); - break; - case CHAP_FAILURE: - case CHAP_SUCCESS: - printer(arg, " "); - print_string((char *)p, len, printer, arg); - break; - default: - for (clen = len; clen > 0; --clen) { - GETCHAR(x, p); - printer(arg, " %.2x", x); + int code, id, len; + int clen, nlen; + unsigned char x; + + if (plen < CHAP_HDRLEN) + return 0; + GETCHAR(code, p); + GETCHAR(id, p); + GETSHORT(len, p); + if (len < CHAP_HDRLEN || len > plen) + return 0; + + if (code >= 1 && code <= sizeof(chap_code_names) / sizeof(char *)) + printer(arg, " %s", chap_code_names[code-1]); + else + printer(arg, " code=0x%x", code); + printer(arg, " id=0x%x", id); + len -= CHAP_HDRLEN; + switch (code) { + case CHAP_CHALLENGE: + case CHAP_RESPONSE: + if (len < 1) + break; + clen = p[0]; + if (len < clen + 1) + break; + ++p; + nlen = len - clen - 1; + printer(arg, " <"); + for (; clen > 0; --clen) { + GETCHAR(x, p); + printer(arg, "%.2x", x); + } + printer(arg, ">, name = "); + print_string((char *)p, nlen, printer, arg); + break; + case CHAP_FAILURE: + case CHAP_SUCCESS: + printer(arg, " "); + print_string((char *)p, len, printer, arg); + break; + default: + for (clen = len; clen > 0; --clen) { + GETCHAR(x, p); + printer(arg, " %.2x", x); + } } - } - return len + CHAP_HEADERLEN; + return len + CHAP_HDRLEN; } + +struct protent chap_protent = { + PPP_CHAP, + chap_init, + chap_input, + chap_protrej, + chap_lowerup, + chap_lowerdown, + NULL, /* open */ + NULL, /* close */ + chap_print_pkt, + NULL, /* datainput */ + 1, /* enabled_flag */ + "CHAP", /* name */ + NULL, /* data_name */ + chap_option_list, + NULL, /* check_options */ +};