From d741a3b912f17d84dc8dc87474e0b989c775de50 Mon Sep 17 00:00:00 2001 From: James Carlson Date: Sat, 2 Nov 2002 19:48:13 +0000 Subject: Added EAP support with MD5-Challenge and SRP-SHA1 methods. Tested on Linux (with both methods) and on Solaris (just MD5-Challenge). Fixed several Makefiles that were missing references to required modules such as tty.o. --- pppd/eap.c | 2428 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2428 insertions(+) create mode 100644 pppd/eap.c (limited to 'pppd/eap.c') diff --git a/pppd/eap.c b/pppd/eap.c new file mode 100644 index 0000000..cc3bc73 --- /dev/null +++ b/pppd/eap.c @@ -0,0 +1,2428 @@ +/* + * 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. + */ + +#define RCSID "$Id: eap.c,v 1.1 2002/11/02 19:48:12 carlsonj Exp $" + +/* + * TODO: + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pppd.h" +#include "pathnames.h" +#include "md5.h" +/* For MD5_SIGNATURE_SIZE and MIN/MAX_CHALLENGE_LENGTH; should fix. */ +#include "chap.h" +#include "eap.h" + +#ifdef USE_SRP +#include +#include +#include +#include "pppcrypt.h" +#endif /* USE_SRP */ + +#ifndef SHA_DIGESTSIZE +#define SHA_DIGESTSIZE 20 +#endif + +static const char rcsid[] = RCSID; + +eap_state eap_states[NUM_PPP]; /* EAP state; one for each unit */ +static char *pn_secret = NULL; /* Pseudonym generating secret */ + +/* + * Command-line options. + */ +static option_t eap_option_list[] = { + { "eap-restart", o_int, &eap_states[0].es_server.ea_timeout, + "Set retransmit timeout for EAP Requests (server)" }, + { "eap-max-sreq", o_int, &eap_states[0].es_server.ea_maxrequests, + "Set max number of EAP Requests sent (server)" }, + { "eap-timeout", o_int, &eap_states[0].es_client.ea_timeout, + "Set time limit for peer EAP authentication" }, + { "eap-max-rreq", o_int, &eap_states[0].es_client.ea_maxrequests, + "Set max number of EAP Requests allows (client)" }, + { "eap-interval", o_int, &eap_states[0].es_rechallenge, + "Set interval for EAP rechallenge" }, +#ifdef USE_SRP + { "srp-interval", o_int, &eap_states[0].es_lwrechallenge, + "Set interval for SRP lightweight rechallenge" }, + { "srp-pn-secret", o_string, &pn_secret, + "Long term pseudonym generation secret" }, + { "srp-use-pseudonym", o_bool, &eap_states[0].es_usepseudo, + "Use pseudonym if offered one by server", 1 }, +#endif + { NULL } +}; + +/* + * Protocol entry points. + */ +static void eap_init __P((int unit)); +static void eap_input __P((int unit, u_char *inp, int inlen)); +static void eap_protrej __P((int unit)); +static void eap_lowerup __P((int unit)); +static void eap_lowerdown __P((int unit)); +static int eap_printpkt __P((u_char *inp, int inlen, + void (*)(void *arg, char *fmt, ...), void *arg)); + +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 */ +}; + +/* + * A well-known 2048 bit modulus. + */ +static const u_char wkmodulus[] = { + 0xAC, 0x6B, 0xDB, 0x41, 0x32, 0x4A, 0x9A, 0x9B, + 0xF1, 0x66, 0xDE, 0x5E, 0x13, 0x89, 0x58, 0x2F, + 0xAF, 0x72, 0xB6, 0x65, 0x19, 0x87, 0xEE, 0x07, + 0xFC, 0x31, 0x92, 0x94, 0x3D, 0xB5, 0x60, 0x50, + 0xA3, 0x73, 0x29, 0xCB, 0xB4, 0xA0, 0x99, 0xED, + 0x81, 0x93, 0xE0, 0x75, 0x77, 0x67, 0xA1, 0x3D, + 0xD5, 0x23, 0x12, 0xAB, 0x4B, 0x03, 0x31, 0x0D, + 0xCD, 0x7F, 0x48, 0xA9, 0xDA, 0x04, 0xFD, 0x50, + 0xE8, 0x08, 0x39, 0x69, 0xED, 0xB7, 0x67, 0xB0, + 0xCF, 0x60, 0x95, 0x17, 0x9A, 0x16, 0x3A, 0xB3, + 0x66, 0x1A, 0x05, 0xFB, 0xD5, 0xFA, 0xAA, 0xE8, + 0x29, 0x18, 0xA9, 0x96, 0x2F, 0x0B, 0x93, 0xB8, + 0x55, 0xF9, 0x79, 0x93, 0xEC, 0x97, 0x5E, 0xEA, + 0xA8, 0x0D, 0x74, 0x0A, 0xDB, 0xF4, 0xFF, 0x74, + 0x73, 0x59, 0xD0, 0x41, 0xD5, 0xC3, 0x3E, 0xA7, + 0x1D, 0x28, 0x1E, 0x44, 0x6B, 0x14, 0x77, 0x3B, + 0xCA, 0x97, 0xB4, 0x3A, 0x23, 0xFB, 0x80, 0x16, + 0x76, 0xBD, 0x20, 0x7A, 0x43, 0x6C, 0x64, 0x81, + 0xF1, 0xD2, 0xB9, 0x07, 0x87, 0x17, 0x46, 0x1A, + 0x5B, 0x9D, 0x32, 0xE6, 0x88, 0xF8, 0x77, 0x48, + 0x54, 0x45, 0x23, 0xB5, 0x24, 0xB0, 0xD5, 0x7D, + 0x5E, 0xA7, 0x7A, 0x27, 0x75, 0xD2, 0xEC, 0xFA, + 0x03, 0x2C, 0xFB, 0xDB, 0xF5, 0x2F, 0xB3, 0x78, + 0x61, 0x60, 0x27, 0x90, 0x04, 0xE5, 0x7A, 0xE6, + 0xAF, 0x87, 0x4E, 0x73, 0x03, 0xCE, 0x53, 0x29, + 0x9C, 0xCC, 0x04, 0x1C, 0x7B, 0xC3, 0x08, 0xD8, + 0x2A, 0x56, 0x98, 0xF3, 0xA8, 0xD0, 0xC3, 0x82, + 0x71, 0xAE, 0x35, 0xF8, 0xE9, 0xDB, 0xFB, 0xB6, + 0x94, 0xB5, 0xC8, 0x03, 0xD8, 0x9F, 0x7A, 0xE4, + 0x35, 0xDE, 0x23, 0x6D, 0x52, 0x5F, 0x54, 0x75, + 0x9B, 0x65, 0xE3, 0x72, 0xFC, 0xD6, 0x8E, 0xF2, + 0x0F, 0xA7, 0x11, 0x1F, 0x9E, 0x4A, 0xFF, 0x73 +}; + +/* Local forward declarations. */ +static void eap_server_timeout __P((void *arg)); + +/* + * Convert EAP state code to printable string for debug. + */ +static const char * +eap_state_name(esc) +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(unit) +int unit; +{ + eap_state *esp = &eap_states[unit]; + + BZERO(esp, sizeof (*esp)); + esp->es_unit = unit; + esp->es_server.ea_timeout = EAP_DEFTIMEOUT; + esp->es_server.ea_maxrequests = EAP_DEFTRANSMITS; + esp->es_server.ea_id = (u_char)(drand48() * 0x100); + esp->es_client.ea_timeout = EAP_DEFREQTIME; + esp->es_client.ea_maxrequests = EAP_DEFALLOWREQ; +} + +/* + * eap_client_timeout - Give up waiting for the peer to send any + * Request messages. + */ +static void +eap_client_timeout(arg) +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(unit, localname) +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(esp) +eap_state *esp; +{ + u_char *outp; + + outp = outpacket_buf; + + MAKEHEADER(outp, PPP_EAP); + + PUTCHAR(EAP_FAILURE, outp); + esp->es_server.ea_id++; + PUTCHAR(esp->es_server.ea_id, outp); + PUTSHORT(EAP_HEADERLEN, outp); + + output(esp->es_unit, outpacket_buf, EAP_HEADERLEN + PPP_HDRLEN); + + esp->es_server.ea_state = eapBadAuth; + auth_peer_fail(esp->es_unit, PPP_EAP); +} + +/* + * Format a standard EAP Success message and send it to the peer. + * (Server operation) + */ +static void +eap_send_success(esp) +eap_state *esp; +{ + u_char *outp; + + outp = outpacket_buf; + + MAKEHEADER(outp, PPP_EAP); + + PUTCHAR(EAP_SUCCESS, outp); + esp->es_server.ea_id++; + PUTCHAR(esp->es_server.ea_id, outp); + PUTSHORT(EAP_HEADERLEN, outp); + + output(esp->es_unit, outpacket_buf, PPP_HDRLEN + EAP_HEADERLEN); + + auth_peer_success(esp->es_unit, PPP_EAP, 0, + esp->es_server.ea_peer, esp->es_server.ea_peerlen); +} + +#ifdef USE_SRP +/* + * Set DES key according to pseudonym-generating secret and current + * date. + */ +static bool +pncrypt_setkey(int timeoffs) +{ + struct tm *tp; + char tbuf[9]; + SHA1_CTX ctxt; + u_char dig[SHA_DIGESTSIZE]; + time_t reftime; + + if (pn_secret == NULL) + return (0); + reftime = time(NULL) + timeoffs; + tp = localtime(&reftime); + SHA1Init(&ctxt); + SHA1Update(&ctxt, pn_secret, strlen(pn_secret)); + strftime(tbuf, sizeof (tbuf), "%Y%m%d", tp); + SHA1Update(&ctxt, tbuf, strlen(tbuf)); + SHA1Final(dig, &ctxt); + return (DesSetkey(dig)); +} + +static char base64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +struct b64state { + u_int32_t bs_bits; + int bs_offs; +}; + +static int +b64enc(bs, inp, inlen, outp) +struct b64state *bs; +u_char *inp; +int inlen; +u_char *outp; +{ + int outlen = 0; + + while (inlen > 0) { + bs->bs_bits = (bs->bs_bits << 8) | *inp++; + inlen--; + bs->bs_offs += 8; + if (bs->bs_offs >= 24) { + *outp++ = base64[(bs->bs_bits >> 18) & 0x3F]; + *outp++ = base64[(bs->bs_bits >> 12) & 0x3F]; + *outp++ = base64[(bs->bs_bits >> 6) & 0x3F]; + *outp++ = base64[bs->bs_bits & 0x3F]; + outlen += 4; + bs->bs_offs = 0; + bs->bs_bits = 0; + } + } + return (outlen); +} + +static int +b64flush(bs, outp) +struct b64state *bs; +u_char *outp; +{ + int outlen = 0; + + if (bs->bs_offs == 8) { + *outp++ = base64[(bs->bs_bits >> 2) & 0x3F]; + *outp++ = base64[(bs->bs_bits << 4) & 0x3F]; + outlen = 2; + } else if (bs->bs_offs == 16) { + *outp++ = base64[(bs->bs_bits >> 10) & 0x3F]; + *outp++ = base64[(bs->bs_bits >> 4) & 0x3F]; + *outp++ = base64[(bs->bs_bits << 2) & 0x3F]; + outlen = 3; + } + bs->bs_offs = 0; + bs->bs_bits = 0; + return (outlen); +} + +static int +b64dec(bs, inp, inlen, outp) +struct b64state *bs; +u_char *inp; +int inlen; +u_char *outp; +{ + int outlen = 0; + char *cp; + + while (inlen > 0) { + if ((cp = strchr(base64, *inp++)) == NULL) + break; + bs->bs_bits = (bs->bs_bits << 6) | (cp - base64); + inlen--; + bs->bs_offs += 6; + if (bs->bs_offs >= 8) { + *outp++ = bs->bs_bits >> (bs->bs_offs - 8); + outlen++; + bs->bs_offs -= 8; + } + } + return (outlen); +} +#endif /* USE_SRP */ + +/* + * 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(esp, status) +eap_state *esp; +int status; +{ +#ifdef USE_SRP + unsigned char secbuf[MAXWORDLEN], clear[8], *sp, *dp; + struct t_pw tpw; + struct t_confent *tce, mytce; + char *cp, *cp2; + struct t_server *ts; + int id, i, plen, toffs; + u_char vals[2]; + struct b64state bs; +#endif /* USE_SRP */ + + esp->es_server.ea_timeout = esp->es_savedtime; + switch (esp->es_server.ea_state) { + case eapBadAuth: + return; + + case eapIdentify: +#ifdef USE_SRP + /* Discard any previous session. */ + ts = (struct t_server *)esp->es_server.ea_session; + if (ts != NULL) { + t_serverclose(ts); + esp->es_server.ea_session = NULL; + esp->es_server.ea_skey = NULL; + } +#endif /* USE_SRP */ + if (status != 0) { + esp->es_server.ea_state = eapBadAuth; + break; + } +#ifdef USE_SRP + /* If we've got a pseudonym, try to decode to real name. */ + if (esp->es_server.ea_peerlen > SRP_PSEUDO_LEN && + strncmp(esp->es_server.ea_peer, SRP_PSEUDO_ID, + SRP_PSEUDO_LEN) == 0 && + (esp->es_server.ea_peerlen - SRP_PSEUDO_LEN) * 3 / 4 < + sizeof (secbuf)) { + BZERO(&bs, sizeof (bs)); + plen = b64dec(&bs, + esp->es_server.ea_peer + SRP_PSEUDO_LEN, + esp->es_server.ea_peerlen - SRP_PSEUDO_LEN, + secbuf); + toffs = 0; + for (i = 0; i < 5; i++) { + pncrypt_setkey(toffs); + toffs -= 86400; + if (!DesDecrypt(secbuf, clear)) { + dbglog("no DES here; cannot decode " + "pseudonym"); + return; + } + id = *(unsigned char *)clear; + if (id + 1 <= plen && id + 9 > plen) + break; + } + if (plen % 8 == 0 && i < 5) { + /* + * Note that this is always shorter than the + * original stored string, so there's no need + * to realloc. + */ + if ((i = plen = *(unsigned char *)clear) > 7) + i = 7; + esp->es_server.ea_peerlen = plen; + dp = (unsigned char *)esp->es_server.ea_peer; + BCOPY(clear + 1, dp, i); + plen -= i; + dp += i; + sp = secbuf + 8; + while (plen > 0) { + (void) DesDecrypt(sp, dp); + sp += 8; + dp += 8; + plen -= 8; + } + esp->es_server.ea_peer[ + esp->es_server.ea_peerlen] = '\0'; + dbglog("decoded pseudonym to \"%.*q\"", + esp->es_server.ea_peerlen, + esp->es_server.ea_peer); + } else { + dbglog("failed to decode real name"); + /* Stay in eapIdentfy state; requery */ + break; + } + } + /* Look up user in secrets database. */ + if (get_srp_secret(esp->es_unit, esp->es_server.ea_peer, + esp->es_server.ea_name, (char *)secbuf, 1) != 0) { + /* Set up default in case SRP entry is bad */ + esp->es_server.ea_state = eapMD5Chall; + /* Get t_confent based on index in srp-secrets */ + id = strtol((char *)secbuf, &cp, 10); + if (*cp++ != ':' || id < 0) + break; + if (id == 0) { + mytce.index = 0; + mytce.modulus.data = (u_char *)wkmodulus; + mytce.modulus.len = sizeof (wkmodulus); + mytce.generator.data = (u_char *)"\002"; + mytce.generator.len = 1; + tce = &mytce; + } else if ((tce = gettcid(id)) != NULL) { + /* + * Client will have to verify this modulus/ + * generator combination, and that will take + * a while. Lengthen the timeout here. + */ + if (esp->es_server.ea_timeout > 0 && + esp->es_server.ea_timeout < 30) + esp->es_server.ea_timeout = 30; + } else { + break; + } + if ((cp2 = strchr(cp, ':')) == NULL) + break; + *cp2++ = '\0'; + tpw.pebuf.name = esp->es_server.ea_peer; + tpw.pebuf.password.len = t_fromb64((char *)tpw.pwbuf, + cp); + tpw.pebuf.password.data = tpw.pwbuf; + tpw.pebuf.salt.len = t_fromb64((char *)tpw.saltbuf, + cp2); + tpw.pebuf.salt.data = tpw.saltbuf; + if ((ts = t_serveropenraw(&tpw.pebuf, tce)) == NULL) + break; + esp->es_server.ea_session = (void *)ts; + esp->es_server.ea_state = eapSRP1; + vals[0] = esp->es_server.ea_id + 1; + vals[1] = EAPT_SRP; + t_serveraddexdata(ts, vals, 2); + /* Generate B; must call before t_servergetkey() */ + t_servergenexp(ts); + break; + } +#endif /* USE_SRP */ + esp->es_server.ea_state = eapMD5Chall; + break; + + case eapSRP1: +#ifdef USE_SRP + ts = (struct t_server *)esp->es_server.ea_session; + if (ts != NULL && status != 0) { + t_serverclose(ts); + esp->es_server.ea_session = NULL; + esp->es_server.ea_skey = NULL; + } +#endif /* USE_SRP */ + if (status == 1) { + esp->es_server.ea_state = eapMD5Chall; + } else if (status != 0 || esp->es_server.ea_session == NULL) { + esp->es_server.ea_state = eapBadAuth; + } else { + esp->es_server.ea_state = eapSRP2; + } + break; + + case eapSRP2: +#ifdef USE_SRP + ts = (struct t_server *)esp->es_server.ea_session; + if (ts != NULL && status != 0) { + t_serverclose(ts); + esp->es_server.ea_session = NULL; + esp->es_server.ea_skey = NULL; + } +#endif /* USE_SRP */ + if (status != 0 || esp->es_server.ea_session == NULL) { + esp->es_server.ea_state = eapBadAuth; + } else { + esp->es_server.ea_state = eapSRP3; + } + break; + + case eapSRP3: + case eapSRP4: +#ifdef USE_SRP + ts = (struct t_server *)esp->es_server.ea_session; + if (ts != NULL && status != 0) { + t_serverclose(ts); + esp->es_server.ea_session = NULL; + esp->es_server.ea_skey = NULL; + } +#endif /* USE_SRP */ + if (status != 0 || esp->es_server.ea_session == NULL) { + esp->es_server.ea_state = eapBadAuth; + } else { + esp->es_server.ea_state = eapOpen; + } + break; + + case eapMD5Chall: + if (status != 0) { + esp->es_server.ea_state = eapBadAuth; + } else { + esp->es_server.ea_state = eapOpen; + } + break; + + default: + esp->es_server.ea_state = eapBadAuth; + break; + } + if (esp->es_server.ea_state == eapBadAuth) + eap_send_failure(esp); +} + +/* + * Format an EAP Request message and send it to the peer. Message + * type depends on current state. (Server operation) + */ +static void +eap_send_request(esp) +eap_state *esp; +{ + u_char *outp; + u_char *lenloc; + u_char *ptr; + int outlen; + int challen; + char *str; +#ifdef USE_SRP + struct t_server *ts; + u_char clear[8], cipher[8], dig[SHA_DIGESTSIZE], *optr, *cp; + int i, j; + struct b64state b64; + SHA1_CTX ctxt; +#endif /* USE_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; + } + + outp = outpacket_buf; + + MAKEHEADER(outp, PPP_EAP); + + PUTCHAR(EAP_REQUEST, outp); + PUTCHAR(esp->es_server.ea_id, outp); + lenloc = outp; + INCPTR(2, outp); + + switch (esp->es_server.ea_state) { + case eapIdentify: + PUTCHAR(EAPT_IDENTITY, outp); + str = "Name"; + challen = strlen(str); + BCOPY(str, outp, challen); + INCPTR(challen, outp); + break; + + case eapMD5Chall: + PUTCHAR(EAPT_MD5CHAP, outp); + /* + * pick a random challenge length between + * MIN_CHALLENGE_LENGTH and MAX_CHALLENGE_LENGTH + */ + challen = (drand48() * + (MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH)) + + MIN_CHALLENGE_LENGTH; + PUTCHAR(challen, outp); + esp->es_challen = challen; + ptr = esp->es_challenge; + while (--challen >= 0) + *ptr++ = (u_char) (drand48() * 0x100); + BCOPY(esp->es_challenge, outp, esp->es_challen); + INCPTR(esp->es_challen, outp); + BCOPY(esp->es_server.ea_name, outp, esp->es_server.ea_namelen); + INCPTR(esp->es_server.ea_namelen, outp); + break; + +#ifdef USE_SRP + case eapSRP1: + PUTCHAR(EAPT_SRP, outp); + PUTCHAR(EAPSRP_CHALLENGE, outp); + + PUTCHAR(esp->es_server.ea_namelen, outp); + BCOPY(esp->es_server.ea_name, outp, esp->es_server.ea_namelen); + INCPTR(esp->es_server.ea_namelen, outp); + + ts = (struct t_server *)esp->es_server.ea_session; + assert(ts != NULL); + PUTCHAR(ts->s.len, outp); + BCOPY(ts->s.data, outp, ts->s.len); + INCPTR(ts->s.len, outp); + + if (ts->g.len == 1 && ts->g.data[0] == 2) { + PUTCHAR(0, outp); + } else { + PUTCHAR(ts->g.len, outp); + BCOPY(ts->g.data, outp, ts->g.len); + INCPTR(ts->g.len, outp); + } + + if (ts->n.len != sizeof (wkmodulus) || + BCMP(ts->n.data, wkmodulus, sizeof (wkmodulus)) != 0) { + BCOPY(ts->n.data, outp, ts->n.len); + INCPTR(ts->n.len, outp); + } + break; + + case eapSRP2: + PUTCHAR(EAPT_SRP, outp); + PUTCHAR(EAPSRP_SKEY, outp); + + ts = (struct t_server *)esp->es_server.ea_session; + assert(ts != NULL); + BCOPY(ts->B.data, outp, ts->B.len); + INCPTR(ts->B.len, outp); + break; + + case eapSRP3: + PUTCHAR(EAPT_SRP, outp); + PUTCHAR(EAPSRP_SVALIDATOR, outp); + PUTLONG(SRPVAL_EBIT, outp); + ts = (struct t_server *)esp->es_server.ea_session; + assert(ts != NULL); + BCOPY(t_serverresponse(ts), outp, SHA_DIGESTSIZE); + INCPTR(SHA_DIGESTSIZE, outp); + + if (pncrypt_setkey(0)) { + /* Generate pseudonym */ + optr = outp; + cp = (unsigned char *)esp->es_server.ea_peer; + if ((j = i = esp->es_server.ea_peerlen) > 7) + j = 7; + clear[0] = i; + BCOPY(cp, clear + 1, j); + i -= j; + cp += j; + if (!DesEncrypt(clear, cipher)) { + dbglog("no DES here; not generating pseudonym"); + break; + } + BZERO(&b64, sizeof (b64)); + outp++; /* space for pseudonym length */ + outp += b64enc(&b64, cipher, 8, outp); + while (i >= 8) { + (void) DesEncrypt(cp, cipher); + outp += b64enc(&b64, cipher, 8, outp); + cp += 8; + i -= 8; + } + if (i > 0) { + BCOPY(cp, clear, i); + cp += i; + while (i < 8) { + *cp++ = drand48() * 0x100; + i++; + } + (void) DesEncrypt(clear, cipher); + outp += b64enc(&b64, cipher, 8, outp); + } + outp += b64flush(&b64, outp); + + /* Set length and pad out to next 20 octet boundary */ + i = outp - optr - 1; + *optr = i; + i %= SHA_DIGESTSIZE; + if (i != 0) { + while (i < SHA_DIGESTSIZE) { + *outp++ = drand48() * 0x100; + i++; + } + } + + /* Obscure the pseudonym with SHA1 hash */ + SHA1Init(&ctxt); + SHA1Update(&ctxt, &esp->es_server.ea_id, 1); + SHA1Update(&ctxt, esp->es_server.ea_skey, + SESSION_KEY_LEN); + SHA1Update(&ctxt, esp->es_server.ea_peer, + esp->es_server.ea_peerlen); + while (optr < outp) { + SHA1Final(dig, &ctxt); + cp = dig; + while (cp < dig + SHA_DIGESTSIZE) + *optr++ ^= *cp++; + SHA1Init(&ctxt); + SHA1Update(&ctxt, &esp->es_server.ea_id, 1); + SHA1Update(&ctxt, esp->es_server.ea_skey, + SESSION_KEY_LEN); + SHA1Update(&ctxt, optr - SHA_DIGESTSIZE, + SHA_DIGESTSIZE); + } + } + break; + + case eapSRP4: + PUTCHAR(EAPT_SRP, outp); + PUTCHAR(EAPSRP_LWRECHALLENGE, outp); + challen = MIN_CHALLENGE_LENGTH + + ((MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH) * drand48()); + esp->es_challen = challen; + ptr = esp->es_challenge; + while (--challen >= 0) + *ptr++ = drand48() * 0x100; + BCOPY(esp->es_challenge, outp, esp->es_challen); + INCPTR(esp->es_challen, outp); + break; +#endif /* USE_SRP */ + + default: + return; + } + + outlen = (outp - outpacket_buf) - PPP_HDRLEN; + PUTSHORT(outlen, lenloc); + + output(esp->es_unit, outpacket_buf, outlen + PPP_HDRLEN); + + esp->es_server.ea_requests++; + + 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(unit, localname) +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); +} + +/* + * eap_server_timeout - Retransmission timer for sending Requests + * expired. + */ +static void +eap_server_timeout(arg) +void *arg; +{ + eap_state *esp = (eap_state *) arg; + + if (!eap_server_active(esp)) + return; + + /* 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(arg) +void *arg; +{ + eap_state *esp = (eap_state *)arg; + + if (esp->es_server.ea_state != eapOpen && + esp->es_server.ea_state != eapSRP4) + return; + + esp->es_server.ea_requests = 0; + esp->es_server.ea_state = eapIdentify; + eap_figure_next_state(esp, 0); + esp->es_server.ea_id++; + eap_send_request(esp); +} + +static void +srp_lwrechallenge(arg) +void *arg; +{ + eap_state *esp = (eap_state *)arg; + + if (esp->es_server.ea_state != eapOpen || + esp->es_server.ea_type != EAPT_SRP) + return; + + esp->es_server.ea_requests = 0; + esp->es_server.ea_state = eapSRP4; + esp->es_server.ea_id++; + eap_send_request(esp); +} + +/* + * 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(unit) +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; + + esp->es_client.ea_state = eapClosed; + esp->es_server.ea_state = eapClosed; +} + +/* + * eap_lowerdown - The lower layer is now down. + * + * Cancel all timeouts and return to initial state. + */ +static void +eap_lowerdown(unit) +int unit; +{ + eap_state *esp = &eap_states[unit]; + + if (eap_client_active(esp) && esp->es_client.ea_timeout > 0) { + UNTIMEOUT(eap_client_timeout, (void *)esp); + } + if (eap_server_active(esp)) { + if (esp->es_server.ea_timeout > 0) { + UNTIMEOUT(eap_server_timeout, (void *)esp); + } + } else { + if ((esp->es_server.ea_state == eapOpen || + esp->es_server.ea_state == eapSRP4) && + esp->es_rechallenge > 0) { + UNTIMEOUT(eap_rechallenge, (void *)esp); + } + if (esp->es_server.ea_state == eapOpen && + esp->es_lwrechallenge > 0) { + UNTIMEOUT(srp_lwrechallenge, (void *)esp); + } + } + + esp->es_client.ea_state = esp->es_server.ea_state = eapInitial; + esp->es_client.ea_requests = esp->es_server.ea_requests = 0; +} + +/* + * eap_protrej - Peer doesn't speak this protocol. + * + * This shouldn't happen. If it does, it represents authentication + * failure. + */ +static void +eap_protrej(unit) +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(esp, id, typenum, str, lenstr) +eap_state *esp; +u_char id; +u_char typenum; +u_char *str; +int lenstr; +{ + u_char *outp; + int msglen; + + outp = outpacket_buf; + + MAKEHEADER(outp, PPP_EAP); + + PUTCHAR(EAP_RESPONSE, outp); + PUTCHAR(id, outp); + esp->es_client.ea_id = id; + msglen = EAP_HEADERLEN + sizeof (u_char) + lenstr; + PUTSHORT(msglen, outp); + PUTCHAR(typenum, outp); + if (lenstr > 0) { + BCOPY(str, outp, lenstr); + } + + output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); +} + +/* + * Format and send an MD5-Challenge EAP Response message. + */ +static void +eap_chap_response(esp, id, hash, name, namelen) +eap_state *esp; +u_char id; +u_char *hash; +char *name; +int namelen; +{ + u_char *outp; + int msglen; + + outp = outpacket_buf; + + MAKEHEADER(outp, PPP_EAP); + + PUTCHAR(EAP_RESPONSE, outp); + PUTCHAR(id, outp); + esp->es_client.ea_id = id; + msglen = EAP_HEADERLEN + 2 * sizeof (u_char) + MD5_SIGNATURE_SIZE + + namelen; + PUTSHORT(msglen, outp); + PUTCHAR(EAPT_MD5CHAP, outp); + PUTCHAR(MD5_SIGNATURE_SIZE, outp); + BCOPY(hash, outp, MD5_SIGNATURE_SIZE); + INCPTR(MD5_SIGNATURE_SIZE, outp); + if (namelen > 0) { + BCOPY(name, outp, namelen); + } + + output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); +} + +#ifdef USE_SRP +/* + * Format and send a SRP EAP Response message. + */ +static void +eap_srp_response(esp, id, subtypenum, str, lenstr) +eap_state *esp; +u_char id; +u_char subtypenum; +u_char *str; +int lenstr; +{ + u_char *outp; + int msglen; + + outp = outpacket_buf; + + MAKEHEADER(outp, PPP_EAP); + + PUTCHAR(EAP_RESPONSE, outp); + PUTCHAR(id, outp); + esp->es_client.ea_id = id; + msglen = EAP_HEADERLEN + 2 * sizeof (u_char) + lenstr; + PUTSHORT(msglen, outp); + PUTCHAR(EAPT_SRP, outp); + PUTCHAR(subtypenum, outp); + if (lenstr > 0) { + BCOPY(str, outp, lenstr); + } + + output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); +} + +/* + * Format and send a SRP EAP Client Validator Response message. + */ +static void +eap_srpval_response(esp, id, flags, str) +eap_state *esp; +u_char id; +u_int32_t flags; +u_char *str; +{ + u_char *outp; + int msglen; + + outp = outpacket_buf; + + MAKEHEADER(outp, PPP_EAP); + + PUTCHAR(EAP_RESPONSE, outp); + PUTCHAR(id, outp); + esp->es_client.ea_id = id; + msglen = EAP_HEADERLEN + 2 * sizeof (u_char) + sizeof (u_int32_t) + + SHA_DIGESTSIZE; + PUTSHORT(msglen, outp); + PUTCHAR(EAPT_SRP, outp); + PUTCHAR(EAPSRP_CVALIDATOR, outp); + PUTLONG(flags, outp); + BCOPY(str, outp, SHA_DIGESTSIZE); + + output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); +} +#endif /* USE_SRP */ + +static void +eap_send_nak(esp, id, type) +eap_state *esp; +u_char id; +u_char type; +{ + u_char *outp; + int msglen; + + outp = outpacket_buf; + + MAKEHEADER(outp, PPP_EAP); + + PUTCHAR(EAP_RESPONSE, outp); + PUTCHAR(id, outp); + esp->es_client.ea_id = id; + msglen = EAP_HEADERLEN + 2 * sizeof (u_char); + PUTSHORT(msglen, outp); + PUTCHAR(EAPT_NAK, outp); + PUTCHAR(type, outp); + + output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); +} + +#ifdef USE_SRP +static char * +name_of_pn_file() +{ + char *user, *path, *file; + struct passwd *pw; + size_t pl; + static bool pnlogged = 0; + + pw = getpwuid(getuid()); + if (pw == NULL || (user = pw->pw_dir) == NULL || user[0] == 0) { + errno = EINVAL; + return (NULL); + } + file = _PATH_PSEUDONYM; + pl = strlen(user) + strlen(file) + 2; + path = malloc(pl); + if (path == NULL) + return (NULL); + (void) slprintf(path, pl, "%s/%s", user, file); + if (!pnlogged) { + dbglog("pseudonym file: %s", path); + pnlogged = 1; + } + return (path); +} + +static int +open_pn_file(modebits) +mode_t modebits; +{ + char *path; + int fd, err; + + if ((path = name_of_pn_file()) == NULL) + return (-1); + fd = open(path, modebits, S_IRUSR | S_IWUSR); + err = errno; + free(path); + errno = err; + return (fd); +} + +static void +remove_pn_file() +{ + char *path; + + if ((path = name_of_pn_file()) != NULL) { + (void) unlink(path); + (void) free(path); + } +} + +static void +write_pseudonym(esp, inp, len, id) +eap_state *esp; +u_char *inp; +int len, id; +{ + u_char val; + u_char *datp, *digp; + SHA1_CTX ctxt; + u_char dig[SHA_DIGESTSIZE]; + int dsize, fd, olen = len; + + /* + * 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_DIGESTSIZE) == 0) + dsize = SHA_DIGESTSIZE; + len -= dsize; + datp = inp + len; + SHA1Init(&ctxt); + SHA1Update(&ctxt, &val, 1); + SHA1Update(&ctxt, esp->es_client.ea_skey, SESSION_KEY_LEN); + if (len > 0) { + SHA1Update(&ctxt, datp, SHA_DIGESTSIZE); + } else { + SHA1Update(&ctxt, esp->es_client.ea_name, + esp->es_client.ea_namelen); + } + SHA1Final(dig, &ctxt); + for (digp = dig; digp < dig + SHA_DIGESTSIZE; digp++) + *datp++ ^= *digp; + } + + /* Now check that the result is sane */ + if (olen <= 0 || *inp + 1 > olen) { + dbglog("EAP: decoded pseudonym is unusable <%.*B>", olen, inp); + return; + } + + /* Save it away */ + fd = open_pn_file(O_WRONLY | O_CREAT | O_TRUNC); + if (fd < 0) { + dbglog("EAP: error saving pseudonym: %m"); + return; + } + len = write(fd, inp + 1, *inp); + if (close(fd) != -1 && len == *inp) { + dbglog("EAP: saved pseudonym"); + esp->es_usedpseudo = 0; + } else { + dbglog("EAP: failed to save pseudonym"); + remove_pn_file(); + } +} +#endif /* USE_SRP */ + +/* + * eap_request - Receive EAP Request message (client mode). + */ +static void +eap_request(esp, inp, id, len) +eap_state *esp; +u_char *inp; +int id; +int len; +{ + u_char typenum; + u_char vallen; + int secret_len; + char secret[MAXWORDLEN]; + char rhostname[256]; + MD5_CTX mdContext; + u_char hash[MD5_SIGNATURE_SIZE]; +#ifdef USE_SRP + struct t_client *tc; + struct t_num sval, gval, Nval, *Ap, Bval; + u_char vals[2]; + SHA1_CTX ctxt; + u_char dig[SHA_DIGESTSIZE]; + int fd; +#endif /* USE_SRP */ + + /* + * 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; + } + + if (len <= 0) { + error("EAP: empty Request message discarded"); + return; + } + + GETCHAR(typenum, inp); + len--; + + switch (typenum) { + case EAPT_IDENTITY: + if (len > 0) + info("EAP: Identity prompt \"%.*q\"", len, inp); +#ifdef USE_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 /* USE_SRP */ + eap_send_response(esp, id, typenum, 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 (vallen >= len + 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; + } + MD5Init(&mdContext); + typenum = id; + MD5Update(&mdContext, &typenum, 1); + MD5Update(&mdContext, secret, secret_len); + BZERO(secret, sizeof (secret)); + MD5Update(&mdContext, inp, vallen); + MD5Final(hash, &mdContext); + eap_chap_response(esp, id, hash, esp->es_client.ea_name, + esp->es_client.ea_namelen); + break; + +#ifdef USE_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)); + } + + if (esp->es_client.ea_peer != NULL) + free(esp->es_client.ea_peer); + esp->es_client.ea_peer = strdup(rhostname); + esp->es_client.ea_peerlen = strlen(rhostname); + + GETCHAR(vallen, inp); + len--; + if (vallen >= len) { + error("EAP: badly-formed SRP Challenge" + " (s)"); + /* Ignore badly-formed messages */ + return; + } + sval.data = inp; + sval.len = vallen; + INCPTR(vallen, inp); + len -= vallen; + + 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_DIGESTSIZE; + 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_DIGESTSIZE, 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; + } + SHA1Init(&ctxt); + vals[0] = id; + SHA1Update(&ctxt, vals, 1); + SHA1Update(&ctxt, esp->es_client.ea_skey, + SESSION_KEY_LEN); + SHA1Update(&ctxt, inp, len); + SHA1Update(&ctxt, esp->es_client.ea_name, + esp->es_client.ea_namelen); + SHA1Final(dig, &ctxt); + eap_srp_response(esp, id, EAPSRP_LWRECHALLENGE, dig, + SHA_DIGESTSIZE); + break; + + default: + error("EAP: unknown SRP Subtype %d", vallen); + eap_send_nak(esp, id, EAPT_MD5CHAP); + break; + } + break; +#endif /* USE_SRP */ + + default: + info("EAP: unknown authentication type %d; Naking", typenum); + eap_send_nak(esp, id, EAPT_SRP); + break; + } + + if (esp->es_client.ea_timeout > 0) { + UNTIMEOUT(eap_client_timeout, (void *)esp); + TIMEOUT(eap_client_timeout, (void *)esp, + esp->es_client.ea_timeout); + } + return; + +#ifdef USE_SRP +client_failure: + esp->es_client.ea_state = eapBadAuth; + if (esp->es_client.ea_timeout > 0) { + UNTIMEOUT(eap_client_timeout, (void *)esp); + } + esp->es_client.ea_session = NULL; + t_clientclose(tc); + auth_withpeer_fail(esp->es_unit, PPP_EAP); +#endif /* USE_SRP */ +} + +/* + * eap_response - Receive EAP Response message (server mode). + */ +static void +eap_response(esp, inp, id, len) +eap_state *esp; +u_char *inp; +int id; +int len; +{ + u_char typenum; + u_char vallen; + int secret_len; + char secret[MAXSECRETLEN]; + char rhostname[256]; + MD5_CTX mdContext; + u_char hash[MD5_SIGNATURE_SIZE]; +#ifdef USE_SRP + struct t_server *ts; + struct t_num A; + SHA1_CTX ctxt; + u_char dig[SHA_DIGESTSIZE]; +#endif /* USE_SRP */ + + if (esp->es_server.ea_id != id) { + dbglog("EAP: discarding Response %d; expected ID %d", id, + esp->es_server.ea_id); + return; + } + + esp->es_server.ea_responses++; + + if (len <= 0) { + error("EAP: empty Response message discarded"); + return; + } + + GETCHAR(typenum, inp); + len--; + + switch (typenum) { + case EAPT_IDENTITY: + if (esp->es_server.ea_state != eapIdentify) { + dbglog("EAP discarding unwanted Identify \"%.q\"", len, + inp); + break; + } + info("EAP: unauthenticated peer name \"%.*q\"", len, inp); + 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 = malloc(len + 1); + if (esp->es_server.ea_peer == NULL) { + esp->es_server.ea_peerlen = 0; + eap_figure_next_state(esp, 1); + break; + } + BCOPY(inp, esp->es_server.ea_peer, len); + esp->es_server.ea_peer[len] = '\0'; + esp->es_server.ea_peerlen = len; + eap_figure_next_state(esp, 0); + break; + + case EAPT_NOTIFICATION: + dbglog("EAP unexpected Notification; response discarded"); + break; + + case EAPT_NAK: + if (len < 1) { + info("EAP: Nak Response with no suggested protocol"); + eap_figure_next_state(esp, 1); + break; + } + + GETCHAR(vallen, inp); + len--; + + if (!explicit_remote && esp->es_server.ea_state == eapIdentify){ + /* Peer cannot Nak Identify Request */ + eap_figure_next_state(esp, 1); + break; + } + + switch (vallen) { + case EAPT_SRP: + /* Run through SRP validator selection again. */ + esp->es_server.ea_state = eapIdentify; + eap_figure_next_state(esp, 0); + break; + + case EAPT_MD5CHAP: + esp->es_server.ea_state = eapMD5Chall; + break; + + 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 (vallen >= len + 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; + } + MD5Init(&mdContext); + MD5Update(&mdContext, &esp->es_server.ea_id, 1); + MD5Update(&mdContext, secret, secret_len); + BZERO(secret, sizeof (secret)); + MD5Update(&mdContext, esp->es_challenge, esp->es_challen); + MD5Final(hash, &mdContext); + if (BCMP(hash, inp, MD5_SIGNATURE_SIZE) != 0) { + eap_send_failure(esp); + break; + } + esp->es_server.ea_type = EAPT_MD5CHAP; + eap_send_success(esp); + eap_figure_next_state(esp, 0); + if (esp->es_rechallenge != 0) + TIMEOUT(eap_rechallenge, esp, esp->es_rechallenge); + break; + +#ifdef USE_SRP + case EAPT_SRP: + if (len < 1) { + error("EAP: empty SRP Response"); + eap_figure_next_state(esp, 1); + break; + } + GETCHAR(typenum, inp); + len--; + switch (typenum) { + case EAPSRP_CKEY: + if (esp->es_server.ea_state != eapSRP1) { + error("EAP: unexpected SRP Subtype 1 Response"); + eap_figure_next_state(esp, 1); + break; + } + A.data = inp; + A.len = len; + ts = (struct t_server *)esp->es_server.ea_session; + assert(ts != NULL); + esp->es_server.ea_skey = t_servergetkey(ts, &A); + if (esp->es_server.ea_skey == NULL) { + /* Client's A value is bogus; terminate now */ + error("EAP: bogus A value from client"); + eap_send_failure(esp); + } else { + eap_figure_next_state(esp, 0); + } + break; + + case EAPSRP_CVALIDATOR: + if (esp->es_server.ea_state != eapSRP2) { + error("EAP: unexpected SRP Subtype 2 Response"); + eap_figure_next_state(esp, 1); + break; + } + if (len < sizeof (u_int32_t) + SHA_DIGESTSIZE) { + error("EAP: M1 length %d < %d", len, + sizeof (u_int32_t) + SHA_DIGESTSIZE); + eap_figure_next_state(esp, 1); + break; + } + GETLONG(esp->es_server.ea_keyflags, inp); + ts = (struct t_server *)esp->es_server.ea_session; + assert(ts != NULL); + if (t_serververify(ts, inp)) { + info("EAP: unable to validate client identity"); + eap_send_failure(esp); + break; + } + eap_figure_next_state(esp, 0); + break; + + case EAPSRP_ACK: + if (esp->es_server.ea_state != eapSRP3) { + error("EAP: unexpected SRP Subtype 3 Response"); + eap_send_failure(esp); + break; + } + esp->es_server.ea_type = EAPT_SRP; + eap_send_success(esp); + eap_figure_next_state(esp, 0); + if (esp->es_rechallenge != 0) + TIMEOUT(eap_rechallenge, esp, + esp->es_rechallenge); + if (esp->es_lwrechallenge != 0) + TIMEOUT(srp_lwrechallenge, esp, + esp->es_lwrechallenge); + break; + + case EAPSRP_LWRECHALLENGE: + if (esp->es_server.ea_state != eapSRP4) { + info("EAP: unexpected SRP Subtype 4 Response"); + return; + } + if (len != SHA_DIGESTSIZE) { + error("EAP: bad Lightweight rechallenge " + "response"); + return; + } + SHA1Init(&ctxt); + vallen = id; + SHA1Update(&ctxt, &vallen, 1); + SHA1Update(&ctxt, esp->es_server.ea_skey, + SESSION_KEY_LEN); + SHA1Update(&ctxt, esp->es_challenge, esp->es_challen); + SHA1Update(&ctxt, esp->es_server.ea_peer, + esp->es_server.ea_peerlen); + SHA1Final(dig, &ctxt); + if (BCMP(dig, inp, SHA_DIGESTSIZE) != 0) { + error("EAP: failed Lightweight rechallenge"); + eap_send_failure(esp); + break; + } + esp->es_server.ea_state = eapOpen; + if (esp->es_lwrechallenge != 0) + TIMEOUT(srp_lwrechallenge, esp, + esp->es_lwrechallenge); + break; + } + break; +#endif /* USE_SRP */ + + default: + /* This can't happen. */ + error("EAP: unknown Response type %d; ignored", typenum); + return; + } + + if (esp->es_server.ea_timeout > 0) { + UNTIMEOUT(eap_server_timeout, (void *)esp); + } + + if (esp->es_server.ea_state != eapBadAuth && + esp->es_server.ea_state != eapOpen) { + esp->es_server.ea_id++; + eap_send_request(esp); + } +} + +/* + * eap_success - Receive EAP Success message (client mode). + */ +static void +eap_success(esp, inp, id, len) +eap_state *esp; +u_char *inp; +int id; +int len; +{ + if (esp->es_client.ea_state != eapOpen && !eap_client_active(esp)) { + dbglog("EAP unexpected success message in state %s (%d)", + eap_state_name(esp->es_client.ea_state), + esp->es_client.ea_state); + return; + } + + 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); + } + + esp->es_client.ea_state = eapOpen; + auth_withpeer_success(esp->es_unit, PPP_EAP, 0); +} + +/* + * eap_failure - Receive EAP Failure message (client mode). + */ +static void +eap_failure(esp, inp, id, len) +eap_state *esp; +u_char *inp; +int id; +int len; +{ + 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); + } + + esp->es_client.ea_state = eapBadAuth; + + error("EAP: peer reports authentication failure"); + auth_withpeer_fail(esp->es_unit, PPP_EAP); +} + +/* + * eap_input - Handle received EAP message. + */ +static void +eap_input(unit, inp, inlen) +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" +}; + +static char *eap_typenames[] = { + "Identity", "Notification", "Nak", "MD5-Challenge", + "OTP", "Generic-Token", NULL, NULL, + "RSA", "DSS", "KEA", "KEA-Validate", + "TLS", "Defender", "Windows 2000", "Arcot", + "Cisco", "Nokia", "SRP" +}; + +static int +eap_printpkt(inp, inlen, printer, arg) +u_char *inp; +int inlen; +void (*printer) __P((void *, char *, ...)); +void *arg; +{ + int code, id, len, rtype, vallen; + u_char *pstart; + u_int32_t uval; + + if (inlen < EAP_HEADERLEN) + return (0); + pstart = inp; + GETCHAR(code, inp); + GETCHAR(id, inp); + GETSHORT(len, inp); + if (len < EAP_HEADERLEN || len > inlen) + return (0); + + if (code >= 1 && code <= sizeof(eap_codenames) / sizeof(char *)) + printer(arg, " %s", eap_codenames[code-1]); + else + printer(arg, " code=0x%x", code); + printer(arg, " id=0x%x", id); + len -= EAP_HEADERLEN; + switch (code) { + case EAP_REQUEST: + if (len < 1) { + printer(arg, " "); + break; + } + GETCHAR(rtype, inp); + len--; + if (rtype >= 1 && + rtype <= sizeof (eap_typenames) / sizeof (char *)) + printer(arg, " %s", eap_typenames[rtype-1]); + else + printer(arg, " type=0x%x", rtype); + switch (rtype) { + case EAPT_IDENTITY: + case EAPT_NOTIFICATION: + if (len > 0) { + printer(arg, " "); + INCPTR(len, inp); + len = 0; + } else { + printer(arg, " "); + } + break; + + case EAPT_MD5CHAP: + if (len <= 0) + break; + GETCHAR(vallen, inp); + len--; + if (vallen > len) + goto truncated; + printer(arg, " ", vallen, inp); + INCPTR(vallen, inp); + len -= vallen; + if (len > 0) { + printer(arg, " "); + INCPTR(len, inp); + len = 0; + } else { + printer(arg, " "); + } + break; + + case EAPT_SRP: + if (len < 3) + goto truncated; + GETCHAR(vallen, inp); + len--; + printer(arg, "-%d", vallen); + switch (vallen) { + case EAPSRP_CHALLENGE: + GETCHAR(vallen, inp); + len--; + if (vallen >= len) + goto truncated; + if (vallen > 0) { + printer(arg, " "); + } else { + printer(arg, " "); + } + INCPTR(vallen, inp); + len -= vallen; + GETCHAR(vallen, inp); + len--; + if (vallen >= len) + goto truncated; + printer(arg, " ", vallen, inp); + INCPTR(vallen, inp); + len -= vallen; + GETCHAR(vallen, inp); + len--; + if (vallen > len) + goto truncated; + if (vallen == 0) { + printer(arg, " "); + } else { + printer(arg, " ", vallen, inp); + } + INCPTR(vallen, inp); + len -= vallen; + if (len == 0) { + printer(arg, " "); + } else { + printer(arg, " ", len, inp); + INCPTR(len, inp); + len = 0; + } + break; + + case EAPSRP_SKEY: + printer(arg, " ", len, inp); + INCPTR(len, inp); + len = 0; + break; + + case EAPSRP_SVALIDATOR: + if (len < sizeof (u_int32_t)) + break; + GETLONG(uval, inp); + len -= sizeof (u_int32_t); + if (uval & SRPVAL_EBIT) { + printer(arg, " E"); + uval &= ~SRPVAL_EBIT; + } + if (uval != 0) { + printer(arg, " f<%X>", uval); + } + if ((vallen = len) > SHA_DIGESTSIZE) + vallen = SHA_DIGESTSIZE; + printer(arg, " ", len, inp, + len < SHA_DIGESTSIZE ? "?" : ""); + INCPTR(vallen, inp); + len -= vallen; + if (len > 0) { + printer(arg, " ", len, inp); + INCPTR(len, inp); + len = 0; + } + break; + + case EAPSRP_LWRECHALLENGE: + printer(arg, " ", len, inp); + INCPTR(len, inp); + len = 0; + break; + } + break; + } + break; + + case EAP_RESPONSE: + if (len < 1) + break; + GETCHAR(rtype, inp); + len--; + if (rtype >= 1 && + rtype <= sizeof (eap_typenames) / sizeof (char *)) + printer(arg, " %s", eap_typenames[rtype-1]); + else + printer(arg, " type=0x%x", rtype); + switch (rtype) { + case EAPT_IDENTITY: + if (len > 0) { + printer(arg, " "); + INCPTR(len, inp); + len = 0; + } + break; + + case EAPT_NAK: + if (len <= 0) { + printer(arg, " "); + break; + } + GETCHAR(rtype, inp); + len--; + printer(arg, " = 1 && + rtype < sizeof (eap_typenames) / sizeof (char *)) + printer(arg, " (%s)", eap_typenames[rtype-1]); + printer(arg, ">"); + break; + + case EAPT_MD5CHAP: + if (len <= 0) { + printer(arg, " "); + break; + } + GETCHAR(vallen, inp); + len--; + if (vallen > len) + goto truncated; + printer(arg, " ", vallen, inp); + INCPTR(vallen, inp); + len -= vallen; + if (len > 0) { + printer(arg, " "); + INCPTR(len, inp); + len = 0; + } else { + printer(arg, " "); + } + break; + + case EAPT_SRP: + if (len < 1) + goto truncated; + GETCHAR(vallen, inp); + len--; + printer(arg, "-%d", vallen); + switch (vallen) { + case EAPSRP_CKEY: + printer(arg, " ", len, inp); + INCPTR(len, inp); + len = 0; + break; + + case EAPSRP_CVALIDATOR: + if (len < sizeof (u_int32_t)) + break; + GETLONG(uval, inp); + len -= sizeof (u_int32_t); + if (uval & SRPVAL_EBIT) { + printer(arg, " E"); + uval &= ~SRPVAL_EBIT; + } + if (uval != 0) { + printer(arg, " f<%X>", uval); + } + printer(arg, " ", len, inp, + len == SHA_DIGESTSIZE ? "" : "?"); + INCPTR(len, inp); + len = 0; + break; + + case EAPSRP_ACK: + break; + + case EAPSRP_LWRECHALLENGE: + printer(arg, " ", len, inp, + len == SHA_DIGESTSIZE ? "" : "?"); + if ((vallen = len) > SHA_DIGESTSIZE) + vallen = SHA_DIGESTSIZE; + INCPTR(vallen, inp); + len -= vallen; + break; + } + break; + } + break; + + case EAP_SUCCESS: /* No payload expected for these! */ + case EAP_FAILURE: + break; + + truncated: + printer(arg, " "); + break; + } + + if (len > 8) + printer(arg, "%8B...", inp); + else if (len > 0) + printer(arg, "%.*B", len, inp); + INCPTR(len, inp); + + return (inp - pstart); +} -- cgit v1.2.1