/* $NetBSD: eap.c,v 1.7 2025/01/08 19:59:39 christos Exp $ */
/*
* eap.c - Extensible Authentication Protocol for PPP (RFC 2284)
*
* Copyright (c) 2001 by Sun Microsystems, Inc.
* All rights reserved.
*
* Non-exclusive rights to redistribute, modify, translate, and use
* this software in source and binary forms, in whole or in part, is
* hereby granted, provided that the above copyright notice is
* duplicated in any source form, and that neither the name of the
* copyright holder nor the author is used to endorse or promote
* products derived from this software.
*
* 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.
*
* Original version by James Carlson
*
* This implementation of EAP supports MD5-Challenge and SRP-SHA1
* authentication styles. Note that support of MD5-Challenge is a
* requirement of RFC 2284, and that it's essentially just a
* reimplementation of regular RFC 1994 CHAP using EAP messages.
*
* As an authenticator ("server"), there are multiple phases for each
* style. In the first phase of each style, the unauthenticated peer
* name is queried using the EAP Identity request type. If the
* "remotename" option is used, then this phase is skipped, because
* the peer's name is presumed to be known.
*
* For MD5-Challenge, there are two phases, and the second phase
* consists of sending the challenge itself and handling the
* associated response.
*
* For SRP-SHA1, there are four phases. The second sends 's', 'N',
* and 'g'. The reply contains 'A'. The third sends 'B', and the
* reply contains 'M1'. The forth sends the 'M2' value.
*
* As an authenticatee ("client"), there's just a single phase --
* responding to the queries generated by the peer. EAP is an
* authenticator-driven protocol.
*
* Based on draft-ietf-pppext-eap-srp-03.txt.
*/
struct protent eap_protent = {
PPP_EAP, /* protocol number */
eap_init, /* initialization procedure */
eap_input, /* process a received packet */
eap_protrej, /* process a received protocol-reject */
eap_lowerup, /* lower layer has gone up */
eap_lowerdown, /* lower layer has gone down */
NULL, /* open the protocol */
NULL, /* close the protocol */
eap_printpkt, /* print a packet in readable form */
NULL, /* process a received data packet */
1, /* protocol enabled */
"EAP", /* text name of protocol */
NULL, /* text name of corresponding data protocol */
eap_option_list, /* list of command-line options */
NULL, /* check requested options; assign defaults */
NULL, /* configure interface for demand-dial */
NULL /* say whether to bring up link for this pkt */
};
/* Local forward declarations. */
static void eap_server_timeout (void *arg);
/*
* Convert EAP state code to printable string for debug.
*/
static const char *
eap_state_name(enum eap_state_code esc)
{
static const char *state_names[] = { EAP_STATES };
return (state_names[(int)esc]);
}
/*
* eap_init - Initialize state for an EAP user. This is currently
* called once by main() during start-up.
*/
static void
eap_init(int unit)
{
eap_state *esp = &eap_states[unit];
/*
* eap_client_timeout - Give up waiting for the peer to send any
* Request messages.
*/
static void
eap_client_timeout(void *arg)
{
eap_state *esp = (eap_state *) arg;
if (!eap_client_active(esp))
return;
error("EAP: timeout waiting for Request from peer");
auth_withpeer_fail(esp->es_unit, PPP_EAP);
esp->es_client.ea_state = eapBadAuth;
}
/*
* eap_authwithpeer - Authenticate to our peer (behave as client).
*
* Start client state and wait for requests. This is called only
* after eap_lowerup.
*/
void
eap_authwithpeer(int unit, char *localname)
{
eap_state *esp = &eap_states[unit];
/* Save the peer name we're given */
esp->es_client.ea_name = localname;
esp->es_client.ea_namelen = strlen(localname);
esp->es_client.ea_state = eapListen;
/*
* Start a timer so that if the other end just goes
* silent, we don't sit here waiting forever.
*/
if (esp->es_client.ea_timeout > 0)
TIMEOUT(eap_client_timeout, (void *)esp,
esp->es_client.ea_timeout);
}
/*
* Format a standard EAP Failure message and send it to the peer.
* (Server operation)
*/
static void
eap_send_failure(eap_state *esp)
{
u_char *outp;
/*
* Format a standard EAP Success message and send it to the peer.
* (Server operation)
*/
static void
eap_send_success(eap_state *esp)
{
u_char *outp;
#ifdef PPP_WITH_SRP
/*
* Set DES key according to pseudonym-generating secret and current
* date.
*/
static bool
pncrypt_getkey(int timeoffs, unsigned char *key, int keylen)
{
struct tm *tp;
char tbuf[9];
PPP_MD_CTX *ctxt;
time_t reftime;
/*
* Assume that current waiting server state is complete and figure
* next state to use based on available authentication data. 'status'
* indicates if there was an error in handling the last query. It is
* 0 for success and non-zero for failure.
*/
static void
eap_figure_next_state(eap_state *esp, int status)
{
#ifdef PPP_WITH_SRP
unsigned char secbuf[MAXWORDLEN], clear[8], *sp, *dp, key[SHA_DIGEST_LENGTH];
struct t_pw tpw;
struct t_confent *tce, mytce;
char *cp, *cp2;
struct t_server *ts;
int id, i, plen, clen, toffs, keylen;
u_char vals[2];
struct b64state bs;
#endif /* PPP_WITH_SRP */
#ifdef PPP_WITH_EAPTLS
struct eaptls_session *ets;
int secret_len;
char secret[MAXWORDLEN];
#endif /* PPP_WITH_EAPTLS */
#if PPP_WITH_CHAPMS
/*
* eap_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 int
eap_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;
}
/*
* Format an EAP Request message and send it to the peer. Message
* type depends on current state. (Server operation)
*/
static void
eap_send_request(eap_state *esp)
{
u_char *outp;
u_char *lenloc;
u_char *ptr;
int outlen;
int challen;
char *str;
#ifdef PPP_WITH_SRP
struct t_server *ts;
u_char clear[8], cipher[8], dig[SHA_DIGEST_LENGTH], *optr, *cp, key[SHA_DIGEST_LENGTH];
int i, j, diglen, clen, keylen = sizeof(key);
struct b64state b64;
PPP_MD_CTX *ctxt;
#endif /* PPP_WITH_SRP */
/* Handle both initial auth and restart */
if (esp->es_server.ea_state < eapIdentify &&
esp->es_server.ea_state != eapInitial) {
esp->es_server.ea_state = eapIdentify;
if (explicit_remote) {
/*
* If we already know the peer's
* unauthenticated name, then there's no
* reason to ask. Go to next state instead.
*/
esp->es_server.ea_peer = remote_name;
esp->es_server.ea_peerlen = strlen(remote_name);
eap_figure_next_state(esp, 0);
}
}
if (esp->es_server.ea_maxrequests > 0 &&
esp->es_server.ea_requests >= esp->es_server.ea_maxrequests) {
if (esp->es_server.ea_responses > 0)
error("EAP: too many Requests sent");
else
error("EAP: no response to Requests");
eap_send_failure(esp);
return;
}
/* Set length and pad out to next 20 octet boundary */
i = outp - optr - 1;
*optr = i;
i %= SHA_DIGEST_LENGTH;
if (i != 0) {
while (i < SHA_DIGEST_LENGTH) {
*outp++ = drand48() * 0x100;
i++;
}
}
/* Obscure the pseudonym with SHA1 hash */
ctxt = PPP_MD_CTX_new();
if (ctxt) {
if (esp->es_server.ea_timeout > 0)
TIMEOUT(eap_server_timeout, esp, esp->es_server.ea_timeout);
}
/*
* eap_authpeer - Authenticate our peer (behave as server).
*
* Start server state and send first request. This is called only
* after eap_lowerup.
*/
void
eap_authpeer(int unit, char *localname)
{
eap_state *esp = &eap_states[unit];
/* Save the name we're given. */
esp->es_server.ea_name = localname;
esp->es_server.ea_namelen = strlen(localname);
esp->es_savedtime = esp->es_server.ea_timeout;
/* Lower layer up yet? */
if (esp->es_server.ea_state == eapInitial ||
esp->es_server.ea_state == eapPending) {
esp->es_server.ea_state = eapPending;
return;
}
esp->es_server.ea_state = eapPending;
/* ID number not updated here intentionally; hashed into M1 */
eap_send_request(esp);
}
/*
* In eap-tls the state changes after a request, so we return to
* previous state ...
*/
case(eapTlsStart):
case(eapTlsSendAck):
esp->es_server.ea_state = esp->es_server.ea_prev_state;
break;
/*
* ... or resend the stored data
*/
case(eapTlsSend):
case(eapTlsSendAlert):
outp = outpacket_buf;
MAKEHEADER(outp, PPP_EAP);
PUTCHAR(EAP_REQUEST, outp);
PUTCHAR(esp->es_server.ea_id, outp);
lenloc = outp;
INCPTR(2, outp);
/* EAP ID number must not change on timeout. */
eap_send_request(esp);
}
/*
* When it's time to send rechallenge the peer, this timeout is
* called. Once the rechallenge is successful, the response handler
* will restart the timer. If it fails, then the link is dropped.
*/
static void
eap_rechallenge(void *arg)
{
eap_state *esp = (eap_state *)arg;
if (esp->es_server.ea_state != eapOpen &&
esp->es_server.ea_state != eapSRP4)
return;
/*
* eap_lowerup - The lower layer is now up.
*
* This is called before either eap_authpeer or eap_authwithpeer. See
* link_established() in auth.c. All that's necessary here is to
* return to closed state so that those two routines will do the right
* thing.
*/
static void
eap_lowerup(int unit)
{
eap_state *esp = &eap_states[unit];
/* Discard any (possibly authenticated) peer name. */
if (esp->es_server.ea_peer != NULL &&
esp->es_server.ea_peer != remote_name)
free(esp->es_server.ea_peer);
esp->es_server.ea_peer = NULL;
if (esp->es_client.ea_peer != NULL)
free(esp->es_client.ea_peer);
esp->es_client.ea_peer = NULL;
/*
* eap_lowerdown - The lower layer is now down.
*
* Cancel all timeouts and return to initial state.
*/
static void
eap_lowerdown(int unit)
{
eap_state *esp = &eap_states[unit];
/*
* eap_protrej - Peer doesn't speak this protocol.
*
* This shouldn't happen. If it does, it represents authentication
* failure.
*/
static void
eap_protrej(int unit)
{
eap_state *esp = &eap_states[unit];
if (eap_client_active(esp)) {
error("EAP authentication failed due to Protocol-Reject");
auth_withpeer_fail(unit, PPP_EAP);
}
if (eap_server_active(esp)) {
error("EAP authentication of peer failed on Protocol-Reject");
auth_peer_fail(unit, PPP_EAP);
}
eap_lowerdown(unit);
}
/*
* Format and send a regular EAP Response message.
*/
static void
eap_send_response(eap_state *esp, u_char id, u_char typenum,
u_char *str, int lenstr)
{
u_char *outp;
int msglen;
#ifdef PPP_WITH_EAPTLS
/*
* Send an EAP-TLS response message with tls data
*/
static void
eap_tls_response(eap_state *esp, u_char id)
{
u_char *outp;
int outlen;
u_char *lenloc;
outp = outpacket_buf;
MAKEHEADER(outp, PPP_EAP);
PUTCHAR(EAP_RESPONSE, outp);
PUTCHAR(id, outp);
lenloc = outp;
INCPTR(2, outp);
/*
If the id in the request is unchanged, we must retransmit
the old data
*/
if(id == esp->es_client.ea_id)
eaptls_retransmit(esp->es_client.ea_session, &outp);
else
eaptls_send(esp->es_client.ea_session, &outp);
static void
write_pseudonym(eap_state *esp, u_char *inp, int len, int id)
{
u_char val;
u_char *datp, *digp;
PPP_MD_CTX *ctxt;
u_char dig[SHA_DIGEST_LENGTH];
int dsize, fd, olen = len, diglen = sizeof(dig);
/*
* Do the decoding by working backwards. This eliminates the need
* to save the decoded output in a separate buffer.
*/
val = id;
while (len > 0) {
if ((dsize = len % SHA_DIGEST_LENGTH) == 0)
dsize = SHA_DIGEST_LENGTH;
len -= dsize;
datp = inp + len;
ctxt = PPP_MD_CTX_new();
if (ctxt) {
/*
* Ignore requests if we're not open
*/
if (esp->es_client.ea_state <= eapClosed)
return;
/*
* Note: we update es_client.ea_id *only if* a Response
* message is being generated. Otherwise, we leave it the
* same for duplicate detection purposes.
*/
esp->es_client.ea_requests++;
if (esp->es_client.ea_maxrequests != 0 &&
esp->es_client.ea_requests > esp->es_client.ea_maxrequests) {
info("EAP: received too many Request messages");
if (esp->es_client.ea_timeout > 0) {
UNTIMEOUT(eap_client_timeout, (void *)esp);
}
auth_withpeer_fail(esp->es_unit, PPP_EAP);
return;
}
switch (typenum) {
case EAPT_IDENTITY:
if (len > 0)
info("EAP: Identity prompt \"%.*q\"", len, inp);
#ifdef PPP_WITH_SRP
if (esp->es_usepseudo &&
(esp->es_usedpseudo == 0 ||
(esp->es_usedpseudo == 1 &&
id == esp->es_client.ea_id))) {
esp->es_usedpseudo = 1;
/* Try to get a pseudonym */
if ((fd = open_pn_file(O_RDONLY)) >= 0) {
strcpy(rhostname, SRP_PSEUDO_ID);
len = read(fd, rhostname + SRP_PSEUDO_LEN,
sizeof (rhostname) - SRP_PSEUDO_LEN);
/* XXX NAI unsupported */
if (len > 0) {
eap_send_response(esp, id, typenum,
rhostname, len + SRP_PSEUDO_LEN);
}
(void) close(fd);
if (len > 0)
break;
}
}
/* Stop using pseudonym now. */
if (esp->es_usepseudo && esp->es_usedpseudo != 2) {
remove_pn_file();
esp->es_usedpseudo = 2;
}
#endif /* PPP_WITH_SRP */
eap_send_response(esp, id, typenum, (u_char *)esp->es_client.ea_name,
esp->es_client.ea_namelen);
break;
case EAPT_NOTIFICATION:
if (len > 0)
info("EAP: Notification \"%.*q\"", len, inp);
eap_send_response(esp, id, typenum, NULL, 0);
break;
case EAPT_NAK:
/*
* Avoid the temptation to send Response Nak in reply
* to Request Nak here. It can only lead to trouble.
*/
warn("EAP: unexpected Nak in Request; ignored");
/* Return because we're waiting for something real. */
return;
case EAPT_MD5CHAP:
if (len < 1) {
error("EAP: received MD5-Challenge with no data");
/* Bogus request; wait for something real. */
return;
}
GETCHAR(vallen, inp);
len--;
if (vallen < 8 || vallen > len) {
error("EAP: MD5-Challenge with bad length %d (8..%d)",
vallen, len);
/* Try something better. */
eap_send_nak(esp, id, EAPT_SRP);
break;
}
/* Not so likely to happen. */
if (len - vallen >= sizeof (rhostname)) {
dbglog("EAP: trimming really long peer name down");
BCOPY(inp + vallen, rhostname, sizeof (rhostname) - 1);
rhostname[sizeof (rhostname) - 1] = '\0';
} else {
BCOPY(inp + vallen, rhostname, len - vallen);
rhostname[len - vallen] = '\0';
}
/* In case the remote doesn't give us his name. */
if (explicit_remote ||
(remote_name[0] != '\0' && vallen == len))
strlcpy(rhostname, remote_name, sizeof (rhostname));
/*
* Get the secret for authenticating ourselves with
* the specified host.
*/
if (!get_secret(esp->es_unit, esp->es_client.ea_name,
rhostname, secret, &secret_len, 0)) {
dbglog("EAP: no MD5 secret for auth to %q", rhostname);
eap_send_nak(esp, id, EAPT_SRP);
break;
}
mdctx = PPP_MD_CTX_new();
if (mdctx != NULL) {
if (PPP_DigestInit(mdctx, PPP_md5())) {
typenum = id;
if (PPP_DigestUpdate(mdctx, &typenum, 1)) {
if (PPP_DigestUpdate(mdctx, secret, secret_len)) {
BZERO(secret, sizeof(secret));
if (PPP_DigestUpdate(mdctx, inp, vallen)) {
if (PPP_DigestFinal(mdctx, hash, &hashlen)) {
eap_chap_response(esp, id, hash, esp->es_client.ea_name,
esp->es_client.ea_namelen);
PPP_MD_CTX_free(mdctx);
break;
}
}
}
}
}
PPP_MD_CTX_free(mdctx);
}
dbglog("EAP: Invalid MD5 checksum");
eap_send_nak(esp, id, EAPT_SRP);
break;
#ifdef PPP_WITH_EAPTLS
case EAPT_TLS:
switch(esp->es_client.ea_state) {
case eapListen:
if (len < 1) {
error("EAP: received EAP-TLS Listen packet with no data");
/* Bogus request; wait for something real. */
return;
}
GETCHAR(flags, inp);
if(flags & EAP_TLS_FLAGS_START){
#ifdef PPP_WITH_SRP
case EAPT_SRP:
if (len < 1) {
error("EAP: received empty SRP Request");
/* Bogus request; wait for something real. */
return;
}
/* Get subtype */
GETCHAR(vallen, inp);
len--;
switch (vallen) {
case EAPSRP_CHALLENGE:
tc = NULL;
if (esp->es_client.ea_session != NULL) {
tc = (struct t_client *)esp->es_client.
ea_session;
/*
* If this is a new challenge, then start
* over with a new client session context.
* Otherwise, just resend last response.
*/
if (id != esp->es_client.ea_id) {
t_clientclose(tc);
esp->es_client.ea_session = NULL;
tc = NULL;
}
}
/* No session key just yet */
esp->es_client.ea_skey = NULL;
if (tc == NULL) {
GETCHAR(vallen, inp);
len--;
if (vallen >= len) {
error("EAP: badly-formed SRP Challenge"
" (name)");
/* Ignore badly-formed messages */
return;
}
BCOPY(inp, rhostname, vallen);
rhostname[vallen] = '\0';
INCPTR(vallen, inp);
len -= vallen;
/*
* In case the remote doesn't give us his name,
* use configured name.
*/
if (explicit_remote ||
(remote_name[0] != '\0' && vallen == 0)) {
strlcpy(rhostname, remote_name,
sizeof (rhostname));
}
GETCHAR(vallen, inp);
len--;
if (vallen > len) {
error("EAP: badly-formed SRP Challenge"
" (g)");
/* Ignore badly-formed messages */
return;
}
/* If no generator present, then use value 2 */
if (vallen == 0) {
gval.data = (u_char *)"\002";
gval.len = 1;
} else {
gval.data = inp;
gval.len = vallen;
}
INCPTR(vallen, inp);
len -= vallen;
/*
* If no modulus present, then use well-known
* value.
*/
if (len == 0) {
Nval.data = (u_char *)wkmodulus;
Nval.len = sizeof (wkmodulus);
} else {
Nval.data = inp;
Nval.len = len;
}
tc = t_clientopen(esp->es_client.ea_name,
&Nval, &gval, &sval);
if (tc == NULL) {
eap_send_nak(esp, id, EAPT_MD5CHAP);
break;
}
esp->es_client.ea_session = (void *)tc;
/* Add Challenge ID & type to verifier */
vals[0] = id;
vals[1] = EAPT_SRP;
t_clientaddexdata(tc, vals, 2);
}
Ap = t_clientgenexp(tc);
eap_srp_response(esp, id, EAPSRP_CKEY, Ap->data,
Ap->len);
break;
case EAPSRP_SKEY:
tc = (struct t_client *)esp->es_client.ea_session;
if (tc == NULL) {
warn("EAP: peer sent Subtype 2 without 1");
eap_send_nak(esp, id, EAPT_MD5CHAP);
break;
}
if (esp->es_client.ea_skey != NULL) {
/*
* ID number should not change here. Warn
* if it does (but otherwise ignore).
*/
if (id != esp->es_client.ea_id) {
warn("EAP: ID changed from %d to %d "
"in SRP Subtype 2 rexmit",
esp->es_client.ea_id, id);
}
} else {
if (get_srp_secret(esp->es_unit,
esp->es_client.ea_name,
esp->es_client.ea_peer, secret, 0) == 0) {
/*
* Can't work with this peer because
* the secret is missing. Just give
* up.
*/
eap_send_nak(esp, id, EAPT_MD5CHAP);
break;
}
Bval.data = inp;
Bval.len = len;
t_clientpasswd(tc, secret);
BZERO(secret, sizeof (secret));
esp->es_client.ea_skey =
t_clientgetkey(tc, &Bval);
if (esp->es_client.ea_skey == NULL) {
/* Server is rogue; stop now */
error("EAP: SRP server is rogue");
goto client_failure;
}
}
eap_srpval_response(esp, id, SRPVAL_EBIT,
t_clientresponse(tc));
break;
case EAPSRP_SVALIDATOR:
tc = (struct t_client *)esp->es_client.ea_session;
if (tc == NULL || esp->es_client.ea_skey == NULL) {
warn("EAP: peer sent Subtype 3 without 1/2");
eap_send_nak(esp, id, EAPT_MD5CHAP);
break;
}
/*
* If we're already open, then this ought to be a
* duplicate. Otherwise, check that the server is
* who we think it is.
*/
if (esp->es_client.ea_state == eapOpen) {
if (id != esp->es_client.ea_id) {
warn("EAP: ID changed from %d to %d "
"in SRP Subtype 3 rexmit",
esp->es_client.ea_id, id);
}
} else {
len -= sizeof (u_int32_t) + SHA_DIGEST_LENGTH;
if (len < 0 || t_clientverify(tc, inp +
sizeof (u_int32_t)) != 0) {
error("EAP: SRP server verification "
"failed");
goto client_failure;
}
GETLONG(esp->es_client.ea_keyflags, inp);
/* Save pseudonym if user wants it. */
if (len > 0 && esp->es_usepseudo) {
INCPTR(SHA_DIGEST_LENGTH, inp);
write_pseudonym(esp, inp, len, id);
}
}
/*
* We've verified our peer. We're now mostly done,
* except for waiting on the regular EAP Success
* message.
*/
eap_srp_response(esp, id, EAPSRP_ACK, NULL, 0);
break;
case EAPSRP_LWRECHALLENGE:
if (len < 4) {
warn("EAP: malformed Lightweight rechallenge");
return;
}
ctxt = PPP_MD_CTX_new();
if (ctxt) {
#ifdef PPP_WITH_CHAPMS
case EAPT_MSCHAPV2:
if (len < 4) {
error("EAP: received invalid MSCHAPv2 packet, too short");
return;
}
unsigned char opcode;
GETCHAR(opcode, inp);
unsigned char chapid; /* Chapv2-ID */
GETCHAR(chapid, inp);
short mssize;
GETSHORT(mssize, inp);
/* Validate the mssize field */
if (len != mssize) {
error("EAP: received invalid MSCHAPv2 packet, invalid length");
return;
}
len -= 4;
/* If MSCHAPv2 digest was not found, NAK the packet */
if (!esp->es_client.digest) {
error("EAP MSCHAPv2 not supported");
eap_send_nak(esp, id, EAPT_SRP);
return;
}
unsigned char vsize;
GETCHAR(vsize, inp);
len -= 1;
/* Validate the VALUE field */
if (vsize != MS_CHAP2_PEER_CHAL_LEN || len < MS_CHAP2_PEER_CHAL_LEN) {
error("EAP: received invalid MSCHAPv2 packet, invalid value-length: %d", vsize);
return;
}
/* Increment past the VALUE field */
INCPTR(MS_CHAP2_PEER_CHAL_LEN, inp);
len -= MS_CHAP2_PEER_CHAL_LEN;
/* Extract the hostname */
rhostname[0] = '\0';
if (len > 0) {
if (len >= sizeof (rhostname)) {
dbglog("EAP: trimming really long peer name down");
len = sizeof(rhostname) - 1;
}
BCOPY(inp, rhostname, len);
rhostname[len] = '\0';
}
/* In case the remote doesn't give us his name. */
if (explicit_remote || (remote_name[0] != '\0' && len == 0))
strlcpy(rhostname, remote_name, sizeof(rhostname));
/* Get the secret for authenticating ourselves with the specified host. */
if (!get_secret(esp->es_unit, esp->es_client.ea_name,
rhostname, secret, &secret_len, 0)) {
dbglog("EAP: no CHAP secret for auth to %q", rhostname);
eap_send_nak(esp, id, EAPT_SRP);
break;
}
esp->es_client.ea_namelen = strlen(esp->es_client.ea_name);
/* Create the MSCHAPv2 response (and add to cache) */
unsigned char response[MS_CHAP2_RESPONSE_LEN+1]; // VLEN + VALUE
esp->es_client.digest->make_response(response, chapid, esp->es_client.ea_name,
challenge, secret, secret_len, NULL);
#ifdef PPP_WITH_CHAPMS
case EAPT_MSCHAPV2:
info("EAP: peer proposes MSCHAPv2");
/* If MSCHAPv2 digest was not found, NAK the packet */
if (!esp->es_server.digest) {
error("EAP MSCHAPv2 not supported");
eap_send_nak(esp, id, EAPT_SRP);
break;
}
esp->es_server.ea_state = eapMSCHAPv2Chall;
break;
#endif /* PPP_WITH_CHAPMS */
default:
dbglog("EAP: peer requesting unknown Type %d", vallen);
switch (esp->es_server.ea_state) {
case eapSRP1:
case eapSRP2:
case eapSRP3:
esp->es_server.ea_state = eapMD5Chall;
break;
case eapMD5Chall:
case eapSRP4:
esp->es_server.ea_state = eapIdentify;
eap_figure_next_state(esp, 0);
break;
default:
break;
}
break;
}
break;
case EAPT_MD5CHAP:
if (esp->es_server.ea_state != eapMD5Chall) {
error("EAP: unexpected MD5-Response");
eap_figure_next_state(esp, 1);
break;
}
if (len < 1) {
error("EAP: received MD5-Response with no data");
eap_figure_next_state(esp, 1);
break;
}
GETCHAR(vallen, inp);
len--;
if (vallen != 16 || vallen > len) {
error("EAP: MD5-Response with bad length %d", vallen);
eap_figure_next_state(esp, 1);
break;
}
/* Not so likely to happen. */
if (len - vallen >= sizeof (rhostname)) {
dbglog("EAP: trimming really long peer name down");
BCOPY(inp + vallen, rhostname, sizeof (rhostname) - 1);
rhostname[sizeof (rhostname) - 1] = '\0';
} else {
BCOPY(inp + vallen, rhostname, len - vallen);
rhostname[len - vallen] = '\0';
}
/* In case the remote doesn't give us his name. */
if (explicit_remote ||
(remote_name[0] != '\0' && vallen == len))
strlcpy(rhostname, remote_name, sizeof (rhostname));
/*
* Get the secret for authenticating the specified
* host.
*/
if (!get_secret(esp->es_unit, rhostname,
esp->es_server.ea_name, secret, &secret_len, 1)) {
dbglog("EAP: no MD5 secret for auth of %q", rhostname);
eap_send_failure(esp);
break;
}
mdctx = PPP_MD_CTX_new();
if (mdctx != NULL) {
if (PPP_DigestInit(mdctx, PPP_md5())) {
if (PPP_DigestUpdate(mdctx, &esp->es_server.ea_id, 1)) {
if (PPP_DigestUpdate(mdctx, &secret, secret_len)) {
BZERO(secret, sizeof(secret));
if (PPP_DigestUpdate(mdctx, esp->es_challenge, esp->es_challen)) {
#ifdef PPP_WITH_CHAPMS
case EAPT_MSCHAPV2:
if (len < 1) {
error("EAP: received MSCHAPv2 with no data");
eap_figure_next_state(esp, 1);
break;
}
GETCHAR(opcode, inp);
len--;
switch (opcode) {
case CHAP_RESPONSE:
if (esp->es_server.ea_state != eapMSCHAPv2Chall) {
error("EAP: unexpected MSCHAPv2-Response");
eap_figure_next_state(esp, 1);
break;
}
/* skip MS ID + len */
INCPTR(3, inp);
GETCHAR(vallen, inp);
len -= 4;
/* Not so likely to happen. */
if (len - vallen >= sizeof (rhostname)) {
dbglog("EAP: trimming really long peer name down");
BCOPY(inp + vallen, rhostname, sizeof (rhostname) - 1);
rhostname[sizeof (rhostname) - 1] = '\0';
} else {
BCOPY(inp + vallen, rhostname, len - vallen);
rhostname[len - vallen] = '\0';
}
/* In case the remote doesn't give us his name. */
if (explicit_remote ||
(remote_name[0] != '\0' && vallen == len))
strlcpy(rhostname, remote_name, sizeof (rhostname));
/* strip the MS domain name */
if (chapms_strip_domain && strrchr(rhostname, '\\')) {
char tmp[MAXNAMELEN+1];
/*
* eap_failure - Receive EAP Failure message (client mode).
*/
static void
eap_failure(eap_state *esp, u_char *inp, int id, int len)
{
/*
* Ignore failure messages if we're not open
*/
if (esp->es_client.ea_state <= eapClosed)
return;
if (!eap_client_active(esp)) {
dbglog("EAP unexpected failure message in state %s (%d)",
eap_state_name(esp->es_client.ea_state),
esp->es_client.ea_state);
}
if (esp->es_client.ea_timeout > 0) {
UNTIMEOUT(eap_client_timeout, (void *)esp);
}
if (len > 0) {
/* This is odd. The spec doesn't allow for this. */
PRINTMSG(inp, len);
}
/*
* eap_input - Handle received EAP message.
*/
static void
eap_input(int unit, u_char *inp, int inlen)
{
eap_state *esp = &eap_states[unit];
u_char code, id;
int len;
/*
* Parse header (code, id and length). If packet too short,
* drop it.
*/
if (inlen < EAP_HEADERLEN) {
error("EAP: packet too short: %d < %d", inlen, EAP_HEADERLEN);
return;
}
GETCHAR(code, inp);
GETCHAR(id, inp);
GETSHORT(len, inp);
if (len < EAP_HEADERLEN || len > inlen) {
error("EAP: packet has illegal length field %d (%d..%d)", len,
EAP_HEADERLEN, inlen);
return;
}
len -= EAP_HEADERLEN;
/* Dispatch based on message code */
switch (code) {
case EAP_REQUEST:
eap_request(esp, inp, id, len);
break;
case EAP_RESPONSE:
eap_response(esp, inp, id, len);
break;
case EAP_SUCCESS:
eap_success(esp, inp, id, len);
break;
case EAP_FAILURE:
eap_failure(esp, inp, id, len);
break;
default: /* XXX Need code reject */
/* Note: it's not legal to send EAP Nak here. */
warn("EAP: unknown code %d received", code);
break;
}
}
/*
* eap_printpkt - print the contents of an EAP packet.
*/
static char *eap_codenames[] = {
"Request", "Response", "Success", "Failure"
};