X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;f=pppd%2Fchap.c;h=2411b95c842a08ff34da66e6eeb977588043d61e;hb=dd3932623ee32a9f679aa087a9939e92556948e4;hp=b7a95cc2b04517d3a3f0792407ed8df162c339f8;hpb=6a34bc9a3edf435bc67e81755b65acb6786c98b3;p=ppp.git diff --git a/pppd/chap.c b/pppd/chap.c index b7a95cc..2411b95 100644 --- a/pppd/chap.c +++ b/pppd/chap.c @@ -1,823 +1,698 @@ /* - * chap.c - Crytographic Handshake Authentication Protocol. + * chap-new.c - New CHAP implementation. * - * Copyright (c) 1991 Gregory M. Christy. - * 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 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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * 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. + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 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. + * + * 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. */ -#ifndef lint -static char rcsid[] = "$Id: chap.c,v 1.5 1994/10/22 11:54:04 paulus Exp $"; -#endif +#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 - -#include "pppd.h" +#include "pppd-private.h" +#include "options.h" +#include "session.h" #include "chap.h" -#include "md5.h" +#include "chap-md5.h" -chap_state chap[N_PPP]; /* CHAP state; one for each unit */ +#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 void ChapChallengeTimeout __P((caddr_t)); -static void ChapResponseTimeout __P((caddr_t)); -static void ChapReceiveChallenge __P((chap_state *, u_char *, int, int)); -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 *)); +int chap_mdtype_all = MDTYPE_ALL; -extern double drand48 __P((void)); -extern void srand48 __P((long)); +/* Hook for a plugin to validate CHAP challenge */ +chap_verify_hook_fn *chap_verify_hook = NULL; /* - * ChapInit - Initialize a CHAP unit. + * Option variables. */ -void -ChapInit(unit) - 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; - srand48((long) time(NULL)); /* joggle random number generator */ -} - +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; /* - * ChapAuthWithPeer - Authenticate us with our peer (start client). - * + * Command-line options. */ -void -ChapAuthWithPeer(unit, our_name, digest) - int unit; - char *our_name; - int digest; -{ - 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; -} - +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 } +}; /* - * ChapAuthPeer - Authenticate our peer (start server). + * Internal state. */ -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; -} - +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; /* - * ChapChallengeTimeout - Timeout expired on sending challenge. + * These limits apply to challenge and response packets we send. + * The +4 is the +1 that we actually need rounded up. */ -static void -ChapChallengeTimeout(arg) - caddr_t arg; -{ - 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 */ - syslog(LOG_ERR, "Peer failed to respond to CHAP challenge"); - cstate->serverstate = CHAPSS_BADAUTH; - auth_peer_fail(cstate->unit, PPP_CHAP); - return; - } - - ChapSendChallenge(cstate); /* Re-send challenge */ -} - +#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 /* - * ChapResponseTimeout - Timeout expired on sending response. + * Prototypes. */ -static void -ChapResponseTimeout(arg) - caddr_t arg; -{ - 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 */ -} - +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; /* - * ChapRechallenge - Time to challenge the peer again. + * chap_init - reset to initial state. */ static void -ChapRechallenge(arg) - caddr_t arg; +chap_init(int unit) { - chap_state *cstate = (chap_state *) arg; + memset(&client, 0, sizeof(client)); + memset(&server, 0, sizeof(server)); - /* if we aren't sending a response, don't worry. */ - if (cstate->serverstate != CHAPSS_OPEN) - return; - - ChapGenChallenge(cstate); - ChapSendChallenge(cstate); - cstate->serverstate = CHAPSS_RECHALLENGE; - - if (cstate->chal_interval != 0) - TIMEOUT(ChapRechallenge, (caddr_t) cstate, cstate->chal_interval); + chap_md5_init(); +#ifdef PPP_WITH_CHAPMS + chapms_init(); +#endif } - /* - * ChapLowerUp - The lower layer is up. - * - * Start up if we have pending requests. + * Add a new digest type to the list. */ void -ChapLowerUp(unit) - int unit; +chap_register_digest(struct chap_digest_type *dp) { - 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; - } + dp->next = chap_digests; + chap_digests = dp; } - /* - * ChapLowerDown - The lower layer is down. - * - * Cancel all timeouts. + * Lookup a digest type by code */ -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, (caddr_t) cstate); - else if (cstate->serverstate == CHAPSS_OPEN - && cstate->chal_interval != 0) - UNTIMEOUT(ChapRechallenge, (caddr_t) cstate); - if (cstate->clientstate == CHAPCS_RESPONSE) - UNTIMEOUT(ChapResponseTimeout, (caddr_t) cstate); - - cstate->clientstate = CHAPCS_INITIAL; - cstate->serverstate = CHAPSS_INITIAL; +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; } - /* - * ChapProtocolReject - Peer doesn't grok CHAP. + * chap_lowerup - we can start doing stuff now. */ -void -ChapProtocolReject(unit) - int unit; +static void +chap_lowerup(int unit) { - 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_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); } +static void +chap_lowerdown(int unit) +{ + 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; +} /* - * ChapInput - Input CHAP packet. + * 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. */ void -ChapInput(unit, inpacket, packet_len) - int unit; - u_char *inpacket; - int packet_len; +chap_auth_peer(int unit, char *our_name, int digest_code) { - 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((LOG_INFO, "ChapInput: rcvd short header.")); - return; - } - GETCHAR(code, inp); - GETCHAR(id, inp); - GETSHORT(len, inp); - if (len < CHAP_HEADERLEN) { - CHAPDEBUG((LOG_INFO, "ChapInput: rcvd illegal length.")); - return; - } - if (len > packet_len) { - CHAPDEBUG((LOG_INFO, "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? */ - syslog(LOG_WARNING, "Unknown CHAP code (%d) received.", code); - break; - } -} + struct chap_server_state *ss = &server; + struct chap_digest_type *dp; + if (ss->flags & AUTH_STARTED) { + error("CHAP: peer authentication already started!"); + return; + } -/* - * ChapReceiveChallenge - Receive Challenge and send Response. - */ -static void -ChapReceiveChallenge(cstate, inp, id, len) - chap_state *cstate; - u_char *inp; - int id; - int len; -{ - int rchallenge_len; - u_char *rchallenge; - int secret_len; - char secret[MAXSECRETLEN]; - char rhostname[256]; - MD5_CTX mdContext; - - CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: Rcvd id %d.", id)); - if (cstate->clientstate == CHAPCS_CLOSED || - cstate->clientstate == CHAPCS_PENDING) { - CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: in state %d", - cstate->clientstate)); - return; - } - - if (len < 2) { - CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: rcvd short packet.")); - return; - } - - GETCHAR(rchallenge_len, inp); - len -= sizeof (u_char) + rchallenge_len; /* now name field length */ - if (len < 0) { - CHAPDEBUG((LOG_INFO, "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'; - - CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: received name field: %s", - 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 */ - syslog(LOG_WARNING, "No CHAP secret found for authenticating us to %s", - rhostname); - } - - /* cancel response send timeout if necessary */ - if (cstate->clientstate == CHAPCS_RESPONSE) - UNTIMEOUT(ChapResponseTimeout, (caddr_t) cstate); - - cstate->resp_id = id; - cstate->resp_transmits = 0; - - /* generate MD based on negotiated type */ - switch (cstate->resp_type) { - - case CHAP_DIGEST_MD5: /* only MD5 is defined for now */ - MD5Init(&mdContext); - MD5Update(&mdContext, &cstate->resp_id, 1); - MD5Update(&mdContext, secret, secret_len); - MD5Update(&mdContext, rchallenge, rchallenge_len); - MD5Final(&mdContext); - BCOPY(mdContext.digest, cstate->response, MD5_SIGNATURE_SIZE); - cstate->resp_length = MD5_SIGNATURE_SIZE; - break; - - default: - CHAPDEBUG((LOG_INFO, "unknown digest type %d", cstate->resp_type)); - return; - } - - ChapSendResponse(cstate); + 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); } - /* - * ChapReceiveResponse - Receive and process response. + * chap_auth_with_peer - Prepare to authenticate ourselves to the peer. + * There isn't much to do until we receive a challenge. */ -static void -ChapReceiveResponse(cstate, inp, id, len) - chap_state *cstate; - u_char *inp; - int id; - int len; +void +chap_auth_with_peer(int unit, char *our_name, int digest_code) { - u_char *remmd, remmd_len; - int secret_len, old_state; - int code; - char rhostname[256]; - u_char buf[256]; - MD5_CTX mdContext; - u_char msg[256]; - char secret[MAXSECRETLEN]; - - CHAPDEBUG((LOG_INFO, "ChapReceiveResponse: Rcvd id %d.", id)); - - if (cstate->serverstate == CHAPSS_CLOSED || - cstate->serverstate == CHAPSS_PENDING) { - CHAPDEBUG((LOG_INFO, "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((LOG_INFO, "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((LOG_INFO, "ChapReceiveResponse: rcvd short packet.")); - return; - } - - UNTIMEOUT(ChapChallengeTimeout, (caddr_t) cstate); - - if (len >= sizeof(rhostname)) - len = sizeof(rhostname) - 1; - BCOPY(inp, rhostname, len); - rhostname[len] = '\000'; - - CHAPDEBUG((LOG_INFO, "ChapReceiveResponse: received name field: %s", - rhostname)); - - /* - * Get secret for authenticating them with us, - * do the hash ourselves, and compare the result. - */ - code = CHAP_FAILURE; - if (!get_secret(cstate->unit, rhostname, cstate->chal_name, - secret, &secret_len, 1)) { - syslog(LOG_WARNING, "No CHAP secret found for authenticating %s", - 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(&mdContext); - - /* compare local and remote MDs and send the appropriate status */ - if (bcmp (mdContext.digest, remmd, MD5_SIGNATURE_SIZE) == 0) - code = CHAP_SUCCESS; /* they are the same! */ - break; + struct chap_client_state *cs = &client; + struct chap_digest_type *dp; - default: - CHAPDEBUG((LOG_INFO, "unknown digest type %d", cstate->chal_type)); - } - } - - 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); + if (cs->flags & AUTH_STARTED) { + error("CHAP: authentication with peer already started!"); + return; } - if (cstate->chal_interval != 0) - TIMEOUT(ChapRechallenge, (caddr_t) cstate, cstate->chal_interval); - - } else { - syslog(LOG_ERR, "CHAP peer authentication failed"); - cstate->serverstate = CHAPSS_BADAUTH; - auth_peer_fail(cstate->unit, PPP_CHAP); - } + 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); } /* - * ChapReceiveSuccess - Receive Success + * 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 -ChapReceiveSuccess(cstate, inp, id, len) - chap_state *cstate; - u_char *inp; - u_char id; - int len; +chap_server_timeout(void *arg) { + 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; + } - CHAPDEBUG((LOG_INFO, "ChapReceiveSuccess: Rcvd id %d.", id)); - - 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((LOG_INFO, "ChapReceiveSuccess: in state %d\n", - cstate->clientstate)); - return; - } - - UNTIMEOUT(ChapResponseTimeout, (caddr_t) cstate); - - /* - * Print message. - */ - if (len > 0) - PRINTMSG(inp, len); - - cstate->clientstate = CHAPCS_OPEN; - - auth_withpeer_success(cstate->unit, PPP_CHAP); + output(0, ss->challenge, ss->challenge_pktlen); + ++ss->challenge_xmits; + ss->flags |= TIMEOUT_PENDING; + TIMEOUT(chap_server_timeout, arg, chap_server_timeout_time); } - -/* - * ChapReceiveFailure - Receive failure. - */ +/* chap_client_timeout - Authentication with peer timed out. */ static void -ChapReceiveFailure(cstate, inp, id, len) - chap_state *cstate; - u_char *inp; - u_char id; - int len; +chap_client_timeout(void *arg) { - u_char msglen; - u_char *msg; - - CHAPDEBUG((LOG_INFO, "ChapReceiveFailure: Rcvd id %d.", id)); - - if (cstate->clientstate != CHAPCS_RESPONSE) { - /* don't know what this is */ - CHAPDEBUG((LOG_INFO, "ChapReceiveFailure: in state %d\n", - cstate->clientstate)); - return; - } - - UNTIMEOUT(ChapResponseTimeout, (caddr_t) cstate); - - /* - * Print message. - */ - if (len > 0) - PRINTMSG(inp, len); - - syslog(LOG_ERR, "CHAP authentication failed"); - auth_withpeer_fail(cstate->unit, PPP_CHAP); -} + 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); +} /* - * ChapSendChallenge - Send an Authenticate challenge. + * chap_generate_challenge - generate a challenge string and format + * the challenge packet in ss->challenge_pkt. */ static void -ChapSendChallenge(cstate) - chap_state *cstate; +chap_generate_challenge(struct chap_server_state *ss) { - 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; - - MAKEHEADER(outp, PPP_CHAP); /* paste in a CHAP header */ - - PUTCHAR(CHAP_CHALLENGE, outp); - PUTCHAR(cstate->chal_id, outp); - PUTSHORT(outlen, outp); - - PUTCHAR(chal_len, outp); /* put length of challenge */ - BCOPY(cstate->challenge, outp, chal_len); - INCPTR(chal_len, outp); - - BCOPY(cstate->chal_name, outp, name_len); /* append hostname */ - - output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN); - - CHAPDEBUG((LOG_INFO, "ChapSendChallenge: Sent id %d.", cstate->chal_id)); - - TIMEOUT(ChapChallengeTimeout, (caddr_t) cstate, cstate->timeouttime); - ++cstate->chal_transmits; + 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; } - /* - * ChapSendStatus - Send a status response (ack or nak). + * chap_handle_response - check the response to our challenge. */ static void -ChapSendStatus(cstate, code) - chap_state *cstate; - int code; +chap_handle_response(struct chap_server_state *ss, int id, + unsigned char *pkt, int len) { - u_char *outp; - int outlen, msglen; - char msg[256]; - - if (code == CHAP_SUCCESS) - sprintf(msg, "Welcome to %s.", hostname); - else - sprintf(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); - - CHAPDEBUG((LOG_INFO, "ChapSendStatus: Sent code %d, id %d.", code, - cstate->chal_id)); + 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; + } } /* - * 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. + * 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 -ChapGenChallenge(cstate) - chap_state *cstate; +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 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); + 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; + } + + ok = digest->verify_response(id, name, secret, secret_len, challenge, + response, message, message_space); + memset(secret, 0, sizeof(secret)); + + return ok; } /* - * ChapSendResponse - send a response packet with values as specified - * in *cstate. + * chap_respond - Generate and send a response to a challenge. */ -/* ARGSUSED */ static void -ChapSendResponse(cstate) - chap_state *cstate; +chap_respond(struct chap_client_state *cs, int id, + unsigned char *pkt, int len) { - 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; - - MAKEHEADER(outp, PPP_CHAP); + 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); + } - PUTCHAR(CHAP_RESPONSE, outp); /* we are a response */ - PUTCHAR(cstate->resp_id, outp); /* copy id from challenge packet */ - PUTSHORT(outlen, outp); /* packet length */ + p = response; + MAKEHEADER(p, PPP_CHAP); + p += CHAP_HDRLEN; - PUTCHAR(md_len, outp); /* length of MD */ - BCOPY(cstate->response, outp, md_len); /* copy MD to buffer */ - INCPTR(md_len, outp); + cs->digest->make_response(p, id, cs->name, pkt, + secret, secret_len, cs->priv); + memset(secret, 0, secret_len); - BCOPY(cstate->resp_name, outp, name_len); /* append our name */ + clen = *p; + nlen = strlen(cs->name); + memcpy(p + clen + 1, cs->name, nlen); - /* send the packet */ - output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN); + p = response + PPP_HDRLEN; + len = CHAP_HDRLEN + clen + 1 + nlen; + p[0] = CHAP_RESPONSE; + p[1] = id; + p[2] = len >> 8; + p[3] = len; - cstate->clientstate = CHAPCS_RESPONSE; - TIMEOUT(ChapResponseTimeout, (caddr_t) cstate, cstate->timeouttime); - ++cstate->resp_transmits; + output(0, response, PPP_HDRLEN + len); } -/* - * ChapPrintPkt - print the contents of a CHAP packet. - */ -char *ChapCodenames[] = { - "Challenge", "Response", "Success", "Failure" -}; - -int -ChapPrintPkt(p, plen, printer, arg) - u_char *p; - int plen; - void (*printer) __P((void *, char *, ...)); - void *arg; +static void +chap_handle_status(struct chap_client_state *cs, int code, int id, + unsigned char *pkt, int len) { - 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); + 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"; } - 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); + 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); } - } - - return len + CHAP_HEADERLEN; } -#ifdef NO_DRAND48 +static void +chap_input(int unit, unsigned char *pkt, int pktlen) +{ + 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; + } +} -double drand48() +static void +chap_protrej(int unit) { - return (double)random() / (double)0x7fffffffL; /* 2**31-1 */ + struct chap_client_state *cs = &client; + struct chap_server_state *ss = &server; + + 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); + } } -void srand48(seedval) -long seedval; +/* + * chap_print_pkt - print the contents of a CHAP packet. + */ +static char *chap_code_names[] = { + "Challenge", "Response", "Success", "Failure" +}; + +static int +chap_print_pkt(unsigned char *p, int plen, + void (*printer)(void *, char *, ...), void *arg) { - srand((int)seedval); + 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_HDRLEN; } -#endif +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 */ +};