/*      $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.
*/

#include <sys/cdefs.h>
__RCSID("$NetBSD: eap.c,v 1.7 2025/01/08 19:59:39 christos Exp $");

/*
* Modification by Beniamino Galvani, Mar 2005
* Implemented EAP-TLS authentication
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <md5.h>

#include "pppd-private.h"
#include "options.h"
#include "pathnames.h"
#include "crypto.h"
#include "crypto_ms.h"
#include "eap.h"
#ifdef PPP_WITH_PEAP
#include "peap.h"
#endif /* PPP_WITH_PEAP */

#ifdef PPP_WITH_SRP
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include <t_pwd.h>
#include <t_server.h>
#include <t_client.h>
#endif /* PPP_WITH_SRP */

#ifdef PPP_WITH_EAPTLS
#include "eap-tls.h"
#endif /* PPP_WITH_EAPTLS */

#ifdef PPP_WITH_CHAPMS
#include "chap.h"
#include "chap_ms.h"

extern int chapms_strip_domain;
#endif /* PPP_WITH_CHAPMS */

eap_state eap_states[NUM_PPP];          /* EAP state; one for each unit */
#ifdef PPP_WITH_SRP
static char *pn_secret = NULL;          /* Pseudonym generating secret */
#endif

/*
* Command-line options.
*/
static struct option 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 PPP_WITH_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 (int unit);
static void eap_input (int unit, u_char *inp, int inlen);
static void eap_protrej (int unit);
static void eap_lowerup (int unit);
static void eap_lowerdown (int unit);
static int  eap_printpkt (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 */
};

#ifdef PPP_WITH_SRP
/*
* 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
};
#endif /* PPP_WITH_SRP */

/* 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];

       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;
#ifdef PPP_WITH_EAPTLS
       esp->es_client.ea_using_eaptls = 0;
#endif /* PPP_WITH_EAPTLS */
#ifdef PPP_WITH_CHAPMS
       esp->es_client.digest = chap_find_digest(CHAP_MICROSOFT_V2);
       esp->es_server.digest = chap_find_digest(CHAP_MICROSOFT_V2);
#endif
}

/*
* 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;

       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(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 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;

       if (pn_secret == NULL)
               return (0);
       reftime = time(NULL) + timeoffs;
       tp = localtime(&reftime);

       ctxt = PPP_MD_CTX_new();
       if (ctxt) {

           strftime(tbuf, sizeof (tbuf), "%Y%m%d", tp);

           PPP_DigestInit(ctxt, PPP_sha1());
           PPP_DigestUpdate(ctxt, pn_secret, strlen(pn_secret));
           PPP_DigestUpdate(ctxt, tbuf, strlen(tbuf));
           PPP_DigestFinal(ctxt, key, &keylen);

           PPP_MD_CTX_free(ctxt);
           return 1;
       }

       return (0);
}

static char base64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

struct b64state {
       u_int32_t bs_bits;
       int bs_offs;
};

static int
b64enc(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(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(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 /* PPP_WITH_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(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 */

       esp->es_server.ea_timeout = esp->es_savedtime;
#ifdef PPP_WITH_EAPTLS
       esp->es_server.ea_prev_state = esp->es_server.ea_state;
#endif /* PPP_WITH_EAPTLS */
       switch (esp->es_server.ea_state) {
       case eapBadAuth:
               return;

       case eapIdentify:
#ifdef PPP_WITH_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 /* PPP_WITH_SRP */
               if (status != 0) {
                       esp->es_server.ea_state = eapBadAuth;
                       break;
               }
#ifdef PPP_WITH_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_getkey(toffs, key, keylen);
                               toffs -= 86400;

                               if (!DesDecrypt(secbuf, key, 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) {
                                       DesDecrypt(sp, key, 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 = (char*) 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 /* PPP_WITH_SRP */
#ifdef PPP_WITH_EAPTLS
               if (!get_secret(esp->es_unit, esp->es_server.ea_peer,
                   esp->es_server.ea_name, secret, &secret_len, 1)) {

                       esp->es_server.ea_state = eapTlsStart;
                       break;
               }
#endif /* PPP_WITH_EAPTLS */

               esp->es_server.ea_state = eapMD5Chall;
               break;

#ifdef PPP_WITH_EAPTLS
       case eapTlsStart:
               /* Initialize ssl session */
               if(!eaptls_init_ssl_server(esp)) {
                       esp->es_server.ea_state = eapBadAuth;
                       break;
               }

               esp->es_server.ea_state = eapTlsRecv;
               break;

       case eapTlsRecv:
               ets = (struct eaptls_session *) esp->es_server.ea_session;

               if(ets->alert_sent) {
                       esp->es_server.ea_state = eapTlsSendAlert;
                       break;
               }

               if (status) {
                       esp->es_server.ea_state = eapBadAuth;
                       break;
               }
               ets = (struct eaptls_session *) esp->es_server.ea_session;

               if(ets->frag)
                       esp->es_server.ea_state = eapTlsSendAck;
               else
                       esp->es_server.ea_state = eapTlsSend;
               break;

       case eapTlsSend:
               ets = (struct eaptls_session *) esp->es_server.ea_session;

               if(ets->frag)
                       esp->es_server.ea_state = eapTlsRecvAck;
               else
                       if(SSL_is_init_finished(ets->ssl))
                               esp->es_server.ea_state = eapTlsRecvClient;
                       else
                               /* JJK Add "TLS empty record" message here ??? */
                               esp->es_server.ea_state = eapTlsRecv;
               break;

       case eapTlsSendAck:
               esp->es_server.ea_state = eapTlsRecv;
               break;

       case eapTlsRecvAck:
               if (status)
               {
                       esp->es_server.ea_state = eapBadAuth;
                       break;
               }

               esp->es_server.ea_state = eapTlsSend;
               break;

       case eapTlsSendAlert:
               esp->es_server.ea_state = eapTlsRecvAlertAck;
               break;
#endif /* PPP_WITH_EAPTLS */

       case eapSRP1:
#ifdef PPP_WITH_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 /* PPP_WITH_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 PPP_WITH_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 /* PPP_WITH_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 PPP_WITH_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 /* PPP_WITH_SRP */
               if (status != 0 || esp->es_server.ea_session == NULL) {
                       esp->es_server.ea_state = eapBadAuth;
               } else {
                       esp->es_server.ea_state = eapOpen;
               }
               break;

#ifdef PPP_WITH_CHAPMS
       case eapMSCHAPv2Chall:
#endif
       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);

#ifdef PPP_WITH_EAPTLS
       dbglog("EAP id=0x%2x '%s' -> '%s'", esp->es_server.ea_id, eap_state_name(esp->es_server.ea_prev_state), eap_state_name(esp->es_server.ea_state));
#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;
       }

       ok = digest->verify_response(id, name, secret, secret_len, challenge,
                                    response, message, message_space);
       memset(secret, 0, sizeof(secret));

       return ok;
}

/*
* Format and send an CHAPV2-Success/Failure EAP Request message.
*/
static void
eap_chapms2_send_request(eap_state *esp, u_char id,
                        u_char opcode, u_char chapid,
                        char *message, int message_len)
{
       u_char *outp;
       int msglen;

       outp = outpacket_buf;

       MAKEHEADER(outp, PPP_EAP);

       msglen = EAP_HEADERLEN + 5 * sizeof (u_char);
       msglen += message_len;

       PUTCHAR(EAP_REQUEST, outp);
       PUTCHAR(id, outp);
       PUTSHORT(msglen, outp);
       PUTCHAR(EAPT_MSCHAPV2, outp);
       PUTCHAR(opcode, outp);
       PUTCHAR(chapid, outp);
       /* MS len */
       PUTSHORT(msglen - 5, outp);
       BCOPY(message, outp, message_len);

       output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen);

       if (opcode == CHAP_SUCCESS) {
               auth_peer_success(esp->es_unit, PPP_EAP, 0,
                               esp->es_server.ea_peer, esp->es_server.ea_peerlen);
       }
       else {
               esp->es_server.ea_state = eapBadAuth;
               auth_peer_fail(esp->es_unit, PPP_EAP);
       }
}
#endif /* PPP_WITH_CHAPMS */

/*
* 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;
       }

       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 PPP_WITH_CHAPMS
       case eapMSCHAPv2Chall:
               esp->es_server.digest->generate_challenge(esp->es_challenge);
               challen = esp->es_challenge[0];
               esp->es_challen = challen;

               PUTCHAR(EAPT_MSCHAPV2, outp);
               PUTCHAR(CHAP_CHALLENGE, outp);
               PUTCHAR(esp->es_server.ea_id, outp);
               /* MS len */
               PUTSHORT(5 + challen +
                               esp->es_server.ea_namelen,
                               outp);
               /* challen + challenge */
               BCOPY(esp->es_challenge, outp, challen+1);
               INCPTR(challen+1, outp);
               BCOPY(esp->es_server.ea_name,
                               outp,
                               esp->es_server.ea_namelen);
               INCPTR(esp->es_server.ea_namelen, outp);
               break;
#endif /* PPP_WITH_CHAPMS */

#ifdef PPP_WITH_EAPTLS
       case eapTlsStart:
               PUTCHAR(EAPT_TLS, outp);
               PUTCHAR(EAP_TLS_FLAGS_START, outp);
               eap_figure_next_state(esp, 0);
               break;

       case eapTlsSend:
               eaptls_send(esp->es_server.ea_session, &outp);
               eap_figure_next_state(esp, 0);
               break;

       case eapTlsSendAck:
               PUTCHAR(EAPT_TLS, outp);
               PUTCHAR(0, outp);
               eap_figure_next_state(esp, 0);
               break;

       case eapTlsSendAlert:
               eaptls_send(esp->es_server.ea_session, &outp);
               eap_figure_next_state(esp, 0);
               break;
#endif /* PPP_WITH_EAPTLS */

#ifdef PPP_WITH_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_DIGEST_LENGTH);
               INCPTR(SHA_DIGEST_LENGTH, outp);

               if (pncrypt_getkey(0, key, keylen)) {
                       /* 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, key, 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) {
                               DesEncrypt(cp, key, 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++;
                               }

                               DesEncrypt(clear, key, 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_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) {

                               PPP_DigestInit(ctxt, PPP_sha1());
                               PPP_DigestUpdate(ctxt, &esp->es_server.ea_id, 1);
                               PPP_DigestUpdate(ctxt, &esp->es_server.ea_skey,
                                       SESSION_KEY_LEN);
                               PPP_DigestUpdate(ctxt,  esp->es_server.ea_peer,
                                       esp->es_server.ea_peerlen);
                               while (optr < outp) {
                                       diglen = SHA_DIGEST_LENGTH;
                                       PPP_DigestFinal(ctxt, dig, &diglen);
                                       cp = dig;
                                       while (cp < dig + SHA_DIGEST_LENGTH)
                                               *optr++ ^= *cp++;

                                       PPP_DigestInit(ctxt, PPP_sha1());
                                       PPP_DigestUpdate(ctxt, &esp->es_server.ea_id, 1);
                                       PPP_DigestUpdate(ctxt, esp->es_server.ea_skey,
                                               SESSION_KEY_LEN);
                                       PPP_DigestUpdate(ctxt, optr - SHA_DIGEST_LENGTH,
                                               SHA_DIGEST_LENGTH);
                               }

                               PPP_MD_CTX_free(ctxt);
                       }
               }
               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 /* PPP_WITH_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(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(void *arg)
{
#ifdef PPP_WITH_EAPTLS
       u_char *outp;
       u_char *lenloc;
       int outlen;
#endif /* PPP_WITH_EAPTLS */

       eap_state *esp = (eap_state *) arg;

       if (!eap_server_active(esp))
               return;

#ifdef PPP_WITH_EAPTLS
       switch(esp->es_server.ea_prev_state) {

       /*
        *  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);

               eaptls_retransmit(esp->es_server.ea_session, &outp);

               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);

               return;
       default:
               break;
       }
#endif /* PPP_WITH_EAPTLS */

       /* 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;

       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(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(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(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(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;

       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(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_DIGEST_LENGTH +
           namelen;
       PUTSHORT(msglen, outp);
       PUTCHAR(EAPT_MD5CHAP, outp);
       PUTCHAR(MD5_DIGEST_LENGTH, outp);
       BCOPY(hash, outp, MD5_DIGEST_LENGTH);
       INCPTR(MD5_DIGEST_LENGTH, outp);
       if (namelen > 0) {
               BCOPY(name, outp, namelen);
       }

       output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen);
}

#ifdef PPP_WITH_SRP
/*
* Format and send a SRP EAP Response message.
*/
static void
eap_srp_response(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(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_DIGEST_LENGTH;
       PUTSHORT(msglen, outp);
       PUTCHAR(EAPT_SRP, outp);
       PUTCHAR(EAPSRP_CVALIDATOR, outp);
       PUTLONG(flags, outp);
       BCOPY(str, outp, SHA_DIGEST_LENGTH);

       output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen);
}
#endif /* PPP_WITH_SRP */

#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);

       outlen = (outp - outpacket_buf) - PPP_HDRLEN;
       PUTSHORT(outlen, lenloc);

       output(esp->es_unit, outpacket_buf, PPP_HDRLEN + outlen);

       esp->es_client.ea_id = id;
}

/*
* Send an EAP-TLS ack
*/
static void
eap_tls_sendack(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);
       esp->es_client.ea_id = id;

       lenloc = outp;
       INCPTR(2, outp);

       PUTCHAR(EAPT_TLS, outp);
       PUTCHAR(0, outp);

       outlen = (outp - outpacket_buf) - PPP_HDRLEN;
       PUTSHORT(outlen, lenloc);

       output(esp->es_unit, outpacket_buf, PPP_HDRLEN + outlen);
}
#endif /* PPP_WITH_EAPTLS */

static void
eap_send_nak(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 PPP_WITH_SRP
static char *
name_of_pn_file(void)
{
       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 = PPP_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(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(void)
{
       char *path;

       if ((path = name_of_pn_file()) != NULL) {
               (void) unlink(path);
               (void) free(path);
       }
}

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) {

                       PPP_DigestInit(ctxt, PPP_sha1());
                       PPP_DigestUpdate(ctxt, &val, 1);
                       PPP_DigestUpdate(ctxt, esp->es_client.ea_skey,
                                       SESSION_KEY_LEN);
                       if (len > 0) {
                               PPP_DigestUpdate(ctxt, datp, SHA_DIGEST_LENGTH);
                       } else {
                               PPP_DigestUpdate(ctxt, esp->es_client.ea_name,
                                       esp->es_client.ea_namelen);
                       }
                       PPP_DigestFinal(ctxt, dig, &diglen);

                       for (digp = dig; digp < dig + SHA_DIGEST_LENGTH; digp++)
                               *datp++ ^= *digp;

                       PPP_MD_CTX_free(ctxt);
               }
       }

       /* 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 /* PPP_WITH_SRP */

#if PPP_WITH_CHAPMS
/*
* Format and send an CHAPV2-Challenge EAP Response message.
*/
static void
eap_chapv2_response(eap_state *esp, u_char id, u_char chapid, u_char *response, char *user, int user_len)
{
   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 + 6 * sizeof (u_char) + MS_CHAP2_RESPONSE_LEN + user_len;
   PUTSHORT(msglen, outp);
   PUTCHAR(EAPT_MSCHAPV2, outp);
   PUTCHAR(CHAP_RESPONSE, outp);
   PUTCHAR(chapid, outp);
   PUTCHAR(0, outp);
   /* len */
   PUTCHAR(5 + user_len + MS_CHAP2_RESPONSE_LEN, outp);
   BCOPY(response, outp, MS_CHAP2_RESPONSE_LEN+1); // VLEN + VALUE
   INCPTR(MS_CHAP2_RESPONSE_LEN+1, outp);
   BCOPY(user, outp, user_len);

   output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen);
}
#endif

/*
* eap_request - Receive EAP Request message (client mode).
*/
static void
eap_request(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];
       PPP_MD_CTX *mdctx;
       u_char hash[MD5_DIGEST_LENGTH];
       int hashlen = MD5_DIGEST_LENGTH;
#ifdef PPP_WITH_EAPTLS
       u_char flags;
       struct eaptls_session *ets = esp->es_client.ea_session;
#endif /* PPP_WITH_EAPTLS */

#ifdef PPP_WITH_SRP
       struct t_client *tc;
       struct t_num sval, gval, Nval, *Ap, Bval;
       u_char vals[2];
       PPP_MD_CTX *ctxt;
       u_char dig[SHA_DIGEST_LENGTH];
       int diglen = sizeof(dig);
       int fd;
#endif /* PPP_WITH_SRP */

       /*
        * 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;
       }

       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 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){

                               esp->es_client.ea_using_eaptls = 1;

                               if (explicit_remote){
                                       esp->es_client.ea_peer = strdup(remote_name);
                                       esp->es_client.ea_peerlen = strlen(remote_name);
                               } else
                                       esp->es_client.ea_peer = NULL;

                               /* Init ssl session */
                               if(!eaptls_init_ssl_client(esp)) {
                                       dbglog("cannot init ssl");
                                       eap_send_nak(esp, id, EAPT_MSCHAPV2);
                                       esp->es_client.ea_using_eaptls = 0;
                                       break;
                               }

                               ets = esp->es_client.ea_session;
                               eap_tls_response(esp, id);
                               esp->es_client.ea_state = (ets->frag ? eapTlsRecvAck : eapTlsRecv);
                               break;
                       }

                       /* The server has sent a bad start packet. */
                       eap_send_nak(esp, id, EAPT_MSCHAPV2);
                       break;

               case eapTlsRecvAck:
                       eap_tls_response(esp, id);
                       esp->es_client.ea_state = (ets->frag ? eapTlsRecvAck : eapTlsRecv);
                       break;

               case eapTlsRecv:
                       if (len < 1) {
                               error("EAP: discarding EAP-TLS Receive packet with no data");
                               /* Bogus request; wait for something real. */
                               return;
                       }
                       eaptls_receive(ets, inp, len);

                       if(ets->frag) {
                               eap_tls_sendack(esp, id);
                               esp->es_client.ea_state = eapTlsRecv;
                               break;
                       }

                       if(ets->alert_recv) {
                               eap_tls_sendack(esp, id);
                               esp->es_client.ea_state = eapTlsRecvFailure;
                               break;
                       }

                       /* Check if TLS handshake is finished */
                       if(eaptls_is_init_finished(ets)) {
#ifdef PPP_WITH_MPPE
                               eaptls_gen_mppe_keys(ets, 1);
#endif
                               eaptls_free_session(ets);
                               eap_tls_sendack(esp, id);
                               esp->es_client.ea_state = eapTlsRecvSuccess;
                               break;
                       }

                       eap_tls_response(esp,id);
                       esp->es_client.ea_state = (ets->frag ? eapTlsRecvAck : eapTlsRecv);
                       break;

               default:
                       eap_send_nak(esp, id, EAPT_MSCHAPV2);
                       esp->es_client.ea_using_eaptls = 0;
                       break;
               }

               break;
#endif /* PPP_WITH_EAPTLS */

#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));
                               }

                               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_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) {

                               vals[0] = id;
                               PPP_DigestInit(ctxt, PPP_sha1());
                               PPP_DigestUpdate(ctxt, vals, 1);
                               PPP_DigestUpdate(ctxt, esp->es_client.ea_skey,
                                       SESSION_KEY_LEN);
                               PPP_DigestUpdate(ctxt, inp, len);
                               PPP_DigestUpdate(ctxt, esp->es_client.ea_name,
                                       esp->es_client.ea_namelen);
                               PPP_DigestFinal(ctxt, dig, &diglen);

                               PPP_MD_CTX_free(ctxt);

                               eap_srp_response(esp, id, EAPSRP_LWRECHALLENGE, dig,
                                       SHA_DIGEST_LENGTH);
                       }
                       break;

               default:
                       error("EAP: unknown SRP Subtype %d", vallen);
                       eap_send_nak(esp, id, EAPT_MD5CHAP);
                       break;
               }
               break;
#endif /* PPP_WITH_SRP */

#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;
           }

           switch (opcode) {
           case CHAP_CHALLENGE: {

               /* make_response() expects: VLEN + VALUE */
               u_char *challenge = inp;

               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);

               eap_chapv2_response(esp, id, chapid, response, esp->es_client.ea_name, esp->es_client.ea_namelen);
               break;
           }
           case CHAP_SUCCESS: {

               /* Check response for mutual authentication */
               u_char status = CHAP_FAILURE;
               if (esp->es_client.digest->check_success(chapid, inp, len) == 1) {
                    info("Chap authentication succeeded! %.*v", len, inp);
                    status = CHAP_SUCCESS;
               }
               eap_send_response(esp, id, EAPT_MSCHAPV2, &status, sizeof(status));
               break;
           }
           case CHAP_FAILURE: {

               /* Process the failure string, and log appropriate information */
               esp->es_client.digest->handle_failure(inp, len);

               u_char status = CHAP_FAILURE;
               eap_send_response(esp, id, EAPT_MSCHAPV2, &status, sizeof(status));
               goto client_failure; /* force termination */
           }
           default:

               error("EAP: received invalid MSCHAPv2 packet, invalid or unsupported opcode: %d", opcode);
               eap_send_nak(esp, id, EAPT_SRP);
           }

           break;
#endif /* PPP_WITH_CHAPMS */
#ifdef PPP_WITH_PEAP
       case EAPT_PEAP:

               /* Initialize the PEAP context (if not already initialized) */
               if (!esp->ea_peap) {
                       rhostname[0] = '\0';
                       if (explicit_remote || (remote_name[0] != '\0')) {
                               strlcpy(rhostname, remote_name, sizeof (rhostname));
                       }
                       if (peap_init(&esp->ea_peap, rhostname)) {
                               eap_send_nak(esp, id, EAPT_TLS);
                               break;
                       }
               }

               /* Process the PEAP packet */
               if (peap_process(esp, id, inp, len)) {
                       eap_send_nak(esp, id, EAPT_TLS);
               }

               break;
#endif // PPP_WITH_PEAP

       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;

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;
#ifdef PPP_WITH_SRP
       t_clientclose(tc);
       auth_withpeer_fail(esp->es_unit, PPP_EAP);
#endif /* PPP_WITH_SRP */
}

/*
* eap_response - Receive EAP Response message (server mode).
*/
static void
eap_response(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];
       PPP_MD_CTX *mdctx;
       u_char hash[MD5_DIGEST_LENGTH];
       int hashlen = MD5_DIGEST_LENGTH;
#ifdef PPP_WITH_SRP
       struct t_server *ts;
       struct t_num A;
       PPP_MD_CTX *ctxt;
       u_char dig[SHA_DIGEST_LENGTH];
       int diglen = sizeof(dig);
#endif /* PPP_WITH_SRP */

#ifdef PPP_WITH_EAPTLS
       struct eaptls_session *ets;
       u_char flags;
#endif /* PPP_WITH_EAPTLS */
#ifdef PPP_WITH_CHAPMS
       u_char opcode;
       chap_verify_hook_fn *chap_verifier;
       char response_message[256];
#endif /* PPP_WITH_CHAPMS */

       /*
        * Ignore responses if we're not open
        */
       if (esp->es_server.ea_state <= eapClosed)
               return;

       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;

#ifdef PPP_WITH_EAPTLS
       case EAPT_TLS:
               switch(esp->es_server.ea_state) {

               case eapTlsRecv:

                       ets = (struct eaptls_session *) esp->es_server.ea_session;

                       eap_figure_next_state(esp,
                               eaptls_receive(esp->es_server.ea_session, inp, len));

                       if(ets->alert_recv) {
                               eap_send_failure(esp);
                               break;
                       }
                       break;

               case eapTlsRecvAck:
                       if(len > 1) {
                               dbglog("EAP-TLS ACK with extra data");
                       }
                       eap_figure_next_state(esp, 0);
                       break;

               case eapTlsRecvClient:
                       /* Receive authentication response from client */
                       if (len > 0) {
                               GETCHAR(flags, inp);

                               if(len == 1 && !flags) {        /* Ack = ok */
#ifdef PPP_WITH_MPPE
                                       eaptls_gen_mppe_keys( esp->es_server.ea_session, 0 );
#endif
                                       eap_send_success(esp);
                               }
                               else {                  /* failure */
                                       warn("Server authentication failed");
                                       eap_send_failure(esp);
                               }
                       }
                       else
                               warn("Bogus EAP-TLS packet received from client");

                       eaptls_free_session(esp->es_server.ea_session);

                       break;

               case eapTlsRecvAlertAck:
                       eap_send_failure(esp);
                       break;

               default:
                       eap_figure_next_state(esp, 1);
                       break;
               }
               break;
#endif /* PPP_WITH_EAPTLS */

       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;

#ifdef PPP_WITH_EAPTLS
                       /* Send EAP-TLS start packet */
               case EAPT_TLS:
                       esp->es_server.ea_state = eapTlsStart;
                       break;
#endif /* PPP_WITH_EAPTLS */

#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)) {

                                                       if (PPP_DigestFinal(mdctx, hash, &hashlen)) {

                                                               if (BCMP(hash, inp, MD5_DIGEST_LENGTH) == 0) {
                                                                       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);
                                                                       }
                                                                       PPP_MD_CTX_free(mdctx);
                                                                       break;
                                                               }
                                                       }
                                               }
                                       }
                               }
                       }

                       PPP_MD_CTX_free(mdctx);
               }

               eap_send_failure(esp);
               break;

#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;

                       if (vallen != MS_CHAP2_RESPONSE_LEN || vallen > len) {
                               error("EAP: Invalid MSCHAPv2-Response "
                                               "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));

                       /* strip the MS domain name */
                       if (chapms_strip_domain && strrchr(rhostname, '\\')) {
                               char tmp[MAXNAMELEN+1];

                               strcpy(tmp, strrchr(rhostname, '\\') + 1);
                               strlcpy(rhostname, tmp, sizeof(rhostname));
                       }

                       if (chap_verify_hook)
                               chap_verifier = chap_verify_hook;
                       else
                               chap_verifier = eap_chap_verify_response;

                       esp->es_server.ea_id += 1;
                       if ((*chap_verifier)(rhostname,
                                               esp->es_server.ea_name,
                                               id,
                                               esp->es_server.digest,
                                               esp->es_challenge,
                                               inp - 1,
                                               response_message,
                                               sizeof(response_message)))
                       {
                               info("EAP: MSCHAPv2 success for peer %q",
                                               rhostname);
                               esp->es_server.ea_type = EAPT_MSCHAPV2;
                               eap_chapms2_send_request(esp,
                                               esp->es_server.ea_id,
                                               CHAP_SUCCESS,
                                               esp->es_server.ea_id,
                                               response_message,
                                               strlen(response_message));
                               eap_figure_next_state(esp, 0);
                               if (esp->es_rechallenge != 0)
                                       TIMEOUT(eap_rechallenge, esp, esp->es_rechallenge);
                       }
                       else {
                               warn("EAP: MSCHAPv2 failure for peer %q",
                                               rhostname);
                               eap_chapms2_send_request(esp,
                                               esp->es_server.ea_id,
                                               CHAP_FAILURE,
                                               esp->es_server.ea_id,
                                               response_message,
                                               strlen(response_message));
                       }
                       break;
               case CHAP_SUCCESS:
                       info("EAP: MSCHAPv2 success confirmed");
                       break;
               case CHAP_FAILURE:
                       info("EAP: MSCHAPv2 failure confirmed");
                       break;
               default:
                       error("EAP: Unhandled MSCHAPv2 opcode %d", opcode);
                       eap_send_nak(esp, id, EAPT_SRP);
               }

               break;
#endif /* PPP_WITH_CHAPMS */

#ifdef PPP_WITH_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_DIGEST_LENGTH) {
                               error("EAP: M1 length %d < %d", len,
                                   sizeof (u_int32_t) + SHA_DIGEST_LENGTH);
                               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_DIGEST_LENGTH) {
                               error("EAP: bad Lightweight rechallenge "
                                   "response");
                               return;
                       }
                       ctxt = PPP_MD_CTX_new();
                       if (ctxt) {
                               vallen = id;

                               PPP_DigestInit(ctxt, PPP_sha1());
                               PPP_DigestUpdate(ctxt, &vallen, 1);
                               PPP_DigestUpdate(ctxt, esp->es_server.ea_skey,
                                       SESSION_KEY_LEN);
                               PPP_DigestUpdate(ctxt, esp->es_challenge, esp->es_challen);
                               PPP_DigestUpdate(ctxt, esp->es_server.ea_peer,
                                       esp->es_server.ea_peerlen);
                               PPP_DigestFinal(ctxt, dig, &diglen);

                               PPP_MD_CTX_free(ctxt);

                               if (BCMP(dig, inp, SHA_DIGEST_LENGTH) != 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 /* PPP_WITH_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(eap_state *esp, u_char *inp, int id, int len)
{
       if (esp->es_client.ea_state != eapOpen && !eap_client_active(esp)
#ifdef PPP_WITH_EAPTLS
               && esp->es_client.ea_state != eapTlsRecvSuccess
#endif /* PPP_WITH_EAPTLS */
               ) {
               dbglog("EAP unexpected success message in state %s (%d)",
                   eap_state_name(esp->es_client.ea_state),
                   esp->es_client.ea_state);
               return;
       }

#ifdef PPP_WITH_EAPTLS
       if(esp->es_client.ea_using_eaptls && esp->es_client.ea_state !=
               eapTlsRecvSuccess) {
               dbglog("EAP-TLS unexpected success message in state %s (%d)",
                   eap_state_name(esp->es_client.ea_state),
                   esp->es_client.ea_state);
               return;
       }
#endif /* PPP_WITH_EAPTLS */

       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);
       }

#ifdef PPP_WITH_PEAP
       peap_finish(&esp->ea_peap);
#endif

       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(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);
       }

       esp->es_client.ea_state = eapBadAuth;

       error("EAP: peer reports authentication failure");

#ifdef PPP_WITH_PEAP
       peap_finish(&esp->ea_peap);
#endif

       auth_withpeer_fail(esp->es_unit, PPP_EAP);
}

/*
* 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"
};

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", NULL,
       "TTLS", "RAS", "AKA", "3COM", "PEAP",
       "MSCHAPv2"
};

static int
eap_printpkt(u_char *inp, int inlen,
            void (*printer) (void *, char *, ...), void *arg)
{
       int code, id, len, rtype, vallen;
       u_char *pstart;
#ifdef PPP_WITH_SRP
       u_int32_t uval;
#endif /* PPP_WITH_SRP */
#ifdef PPP_WITH_EAPTLS
       u_char flags;
#endif /* PPP_WITH_EAPTLS */
#ifdef PPP_WITH_CHAPMS
       u_char opcode;
#endif /* PPP_WITH_CHAPMS */

       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, " <missing type>");
                       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, " <Message ");
                               print_string((char *)inp, len, printer, arg);
                               printer(arg, ">");
                               INCPTR(len, inp);
                               len = 0;
                       } else {
                               printer(arg, " <No message>");
                       }
                       break;

               case EAPT_MD5CHAP:
                       if (len <= 0)
                               break;
                       GETCHAR(vallen, inp);
                       len--;
                       if (vallen > len)
                               goto truncated;
                       printer(arg, " <Value%.*B>", vallen, inp);
                       INCPTR(vallen, inp);
                       len -= vallen;
                       if (len > 0) {
                               printer(arg, " <Name ");
                               print_string((char *)inp, len, printer, arg);
                               printer(arg, ">");
                               INCPTR(len, inp);
                               len = 0;
                       } else {
                               printer(arg, " <No name>");
                       }
                       break;

#ifdef PPP_WITH_CHAPMS
               case EAPT_MSCHAPV2:
                       if (len <= 0)
                               break;
                       GETCHAR(opcode, inp);
                       len--;
                       switch (opcode) {
                       case CHAP_CHALLENGE:
                               INCPTR(3, inp);
                               len -= 3;
                               GETCHAR(vallen, inp);
                               len--;
                               if (vallen > len)
                                       goto truncated;
                               len -= vallen;
                               printer(arg, " Challenge <");
                               for (; vallen > 0; --vallen) {
                                       u_char val;
                                       GETCHAR(val, inp);
                                       printer(arg, "%.2x", val);
                               }
                               printer(arg, ">");
                               if (len > 0) {
                                       printer(arg, ", <Name ");
                                       print_string((char *)inp, len, printer, arg);
                                       printer(arg, ">");
                                       INCPTR(len, inp);
                                       len = 0;
                               } else {
                                       printer(arg, ", <No name>");
                               }
                               break;
                       case CHAP_SUCCESS:
                               INCPTR(3, inp);
                               len -= 3;
                               printer(arg, " Success <Message ");
                               print_string((char *)inp, len, printer, arg);
                               printer(arg, ">");
                               break;
                       case CHAP_FAILURE:
                               INCPTR(3, inp);
                               len -= 3;
                               printer(arg, " Failure <Message ");
                               print_string((char *)inp, len, printer, arg);
                               printer(arg, ">");
                               break;
                       default:
                               INCPTR(3, inp);
                               len -= 3;
                               printer(arg, " opcode=0x%x <%.*B>", opcode, len, inp);
                               break;
                       }
                       break;
#endif /* PPP_WITH_CHAPMS */

#ifdef PPP_WITH_EAPTLS
               case EAPT_TLS:
                       if (len < 1)
                               break;
                       GETCHAR(flags, inp);
                       len--;

                       if(flags == 0 && len == 0){
                               printer(arg, " Ack");
                               break;
                       }

                       printer(arg, flags & EAP_TLS_FLAGS_LI ? " L":" -");
                       printer(arg, flags & EAP_TLS_FLAGS_MF ? "M":"-");
                       printer(arg, flags & EAP_TLS_FLAGS_START ? "S":"- ");
                       break;
#endif /* PPP_WITH_EAPTLS */

#ifdef PPP_WITH_SRP
               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, " <Name ");
                                       print_string((char *)inp, vallen, printer,
                                           arg);
                                       printer(arg, ">");
                               } else {
                                       printer(arg, " <No name>");
                               }
                               INCPTR(vallen, inp);
                               len -= vallen;
                               GETCHAR(vallen, inp);
                               len--;
                               if (vallen >= len)
                                       goto truncated;
                               printer(arg, " <s%.*B>", vallen, inp);
                               INCPTR(vallen, inp);
                               len -= vallen;
                               GETCHAR(vallen, inp);
                               len--;
                               if (vallen > len)
                                       goto truncated;
                               if (vallen == 0) {
                                       printer(arg, " <Default g=2>");
                               } else {
                                       printer(arg, " <g%.*B>", vallen, inp);
                               }
                               INCPTR(vallen, inp);
                               len -= vallen;
                               if (len == 0) {
                                       printer(arg, " <Default N>");
                               } else {
                                       printer(arg, " <N%.*B>", len, inp);
                                       INCPTR(len, inp);
                                       len = 0;
                               }
                               break;

                       case EAPSRP_SKEY:
                               printer(arg, " <B%.*B>", 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_DIGEST_LENGTH)
                                       vallen = SHA_DIGEST_LENGTH;
                               printer(arg, " <M2%.*B%s>", len, inp,
                                   len < SHA_DIGEST_LENGTH ? "?" : "");
                               INCPTR(vallen, inp);
                               len -= vallen;
                               if (len > 0) {
                                       printer(arg, " <PN%.*B>", len, inp);
                                       INCPTR(len, inp);
                                       len = 0;
                               }
                               break;

                       case EAPSRP_LWRECHALLENGE:
                               printer(arg, " <Challenge%.*B>", len, inp);
                               INCPTR(len, inp);
                               len = 0;
                               break;
                       }
                       break;
#endif  /* PPP_WITH_SRP */
               }
               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, " <Name ");
                               print_string((char *)inp, len, printer, arg);
                               printer(arg, ">");
                               INCPTR(len, inp);
                               len = 0;
                       }
                       break;

#ifdef PPP_WITH_EAPTLS
               case EAPT_TLS:
                       if (len < 1)
                               break;
                       GETCHAR(flags, inp);
                       len--;

                       if(flags == 0 && len == 0){
                               printer(arg, " Ack");
                               break;
                       }

                       printer(arg, flags & EAP_TLS_FLAGS_LI ? " L":" -");
                       printer(arg, flags & EAP_TLS_FLAGS_MF ? "M":"-");
                       printer(arg, flags & EAP_TLS_FLAGS_START ? "S":"- ");

                       break;
#endif /* PPP_WITH_EAPTLS */

               case EAPT_NAK:
                       if (len <= 0) {
                               printer(arg, " <missing hint>");
                               break;
                       }
                       GETCHAR(rtype, inp);
                       len--;
                       printer(arg, " <Suggested-type %02X", rtype);
                       if (rtype >= 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, " <missing length>");
                               break;
                       }
                       GETCHAR(vallen, inp);
                       len--;
                       if (vallen > len)
                               goto truncated;
                       printer(arg, " <Value%.*B>", vallen, inp);
                       INCPTR(vallen, inp);
                       len -= vallen;
                       if (len > 0) {
                               printer(arg, " <Name ");
                               print_string((char *)inp, len, printer, arg);
                               printer(arg, ">");
                               INCPTR(len, inp);
                               len = 0;
                       } else {
                               printer(arg, " <No name>");
                       }
                       break;

#ifdef PPP_WITH_CHAPMS
               case EAPT_MSCHAPV2:
                       if (len <= 0)
                               break;
                       GETCHAR(opcode, inp);
                       len--;
                       switch (opcode) {
                       case CHAP_RESPONSE:
                               INCPTR(3, inp);
                               len -= 3;
                               GETCHAR(vallen, inp);
                               len--;
                               if (vallen > len)
                                       goto truncated;
                               len -= vallen;
                               printer(arg, " Response <");
                               for (; vallen > 0; --vallen) {
                                       u_char val;
                                       GETCHAR(val, inp);
                                       printer(arg, "%.2x", val);
                               }
                               printer(arg, ">");
                               if (len > 0) {
                                       printer(arg, ", <Name ");
                                       print_string((char *)inp, len, printer, arg);
                                       printer(arg, ">");
                                       INCPTR(len, inp);
                                       len = 0;
                               } else {
                                       printer(arg, ", <No name>");
                               }
                               break;
                       case CHAP_SUCCESS:
                               printer(arg, " Success");
                               break;
                       case CHAP_FAILURE:
                               printer(arg, " Failure");
                               break;
                       default:
                               printer(arg, " opcode=0x%x <%.*B>", opcode, len, inp);
                               break;
                       }
                       break;
#endif /* PPP_WITH_CHAPMS */

#ifdef PPP_WITH_SRP
               case EAPT_SRP:
                       if (len < 1)
                               goto truncated;
                       GETCHAR(vallen, inp);
                       len--;
                       printer(arg, "-%d", vallen);
                       switch (vallen) {
                       case EAPSRP_CKEY:
                               printer(arg, " <A%.*B>", 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, " <M1%.*B%s>", len, inp,
                                   len == SHA_DIGEST_LENGTH ? "" : "?");
                               INCPTR(len, inp);
                               len = 0;
                               break;

                       case EAPSRP_ACK:
                               break;

                       case EAPSRP_LWRECHALLENGE:
                               printer(arg, " <Response%.*B%s>", len, inp,
                                   len == SHA_DIGEST_LENGTH ? "" : "?");
                               if ((vallen = len) > SHA_DIGEST_LENGTH)
                                       vallen = SHA_DIGEST_LENGTH;
                               INCPTR(vallen, inp);
                               len -= vallen;
                               break;
                       }
                       break;
#endif  /* PPP_WITH_SRP */
               }
               break;

       case EAP_SUCCESS:       /* No payload expected for these! */
       case EAP_FAILURE:
               break;

       truncated:
               printer(arg, " <truncated>");
               break;
       }

       if (len > 8)
               printer(arg, "%8B...", inp);
       else if (len > 0)
               printer(arg, "%.*B", len, inp);
       INCPTR(len, inp);

       return (inp - pstart);
}