/*
 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * eap.c - Extensible Authentication Protocol.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Gregory M. Christy.  The name of the author may not be used to
 * endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * 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.
 */

#define RCSID	"$Id: eap.c,v 1.22.8.1 2005/10/28 22:47:01 lindak Exp $"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <pthread.h>
#include <unistd.h>

#include "pppd.h"
#include "eap.h"
#include "fsm.h"
#include "lcp.h"
#include "chap_ms.h" // for mppe keys

#ifndef lint
static const char rcsid[] = RCSID;
#endif


static int eaploadplugin(char **argv);
int sys_eaploadplugin(char *arg, eap_ext *eap);

/*
 * Command-line options.
 */
static option_t eap_option_list[] = {
    { "eap-restart", o_int, &eap[0].timeouttime,
      "Set timeout for EAP" },
    { "eap-max-challenge", o_int, &eap[0].max_transmits,
      "Set max #xmits for challenge" },
    { "eap-interval", o_int, &eap[0].req_interval,
      "Set interval for rechallenge" },
    { "eapplugin", o_special, (void *)eaploadplugin,
      "Load an eap plug-in module into pppd", OPT_PRIV | OPT_A2LIST },
    { NULL }
};

/*
 * Protocol entry points.
 */
static void EapInit __P((int));
static void EapLowerUp __P((int));
static void EapLowerDown __P((int));
static void EapInput __P((int, u_char *, int));
static void EapProtocolReject __P((int));
static int  EapPrintPkt __P((u_char *, int,
			      void (*) __P((void *, char *, ...)), void *));

struct protent eap_protent = {
    PPP_EAP,
    EapInit,
    EapInput,
    EapProtocolReject,
    EapLowerUp,
    EapLowerDown,
    NULL,
    NULL,
    EapPrintPkt,
    NULL,
    1,
    "EAP",
    NULL,
    eap_option_list,
    NULL,
    NULL,
    NULL,
#ifdef __APPLE__
    NULL,
    NULL,
    NULL
#endif
};

eap_state eap[NUM_PPP];		/* EAP state; one for each unit */

eap_ext *eap_extensions = NULL;	/* eap extensions list */

static void EapChallengeTimeout __P((void *));
static void EapReceiveRequest __P((eap_state *, u_char *, int, u_char *, int, int));
static void EapRechallenge __P((void *));
static void EapReceiveResponse __P((eap_state *, u_char *, int, u_char *, int, int));
static void EapReceiveSuccess __P((eap_state *, u_char *, int, u_char *, int, int));
static void EapReceiveFailure __P((eap_state *, u_char *, int, u_char *, int, int));
static void EapSendIdentityRequest __P((eap_state *));
static eap_ext * EapSupportedType(int type);
static int EAPServerProcess(eap_state *, u_int16_t, u_char *, int);
static int EAPClientProcess(eap_state *, u_int16_t, u_char *, int);
static void EAPClientAction(eap_state *);
static void EAPServerAction(eap_state *);
static void EAPInput_fd(void);


/*
 * EapInit - Initialize a EAP unit.
 */
static void
EapInit(unit)
    int unit;
{
    eap_state *cstate = &eap[unit];

    BZERO(cstate, sizeof(*cstate));
    cstate->unit = unit;
    cstate->clientstate = EAPCS_INITIAL;
    cstate->serverstate = EAPSS_INITIAL;
    cstate->timeouttime = EAP_DEFTIMEOUT;
    cstate->max_transmits = EAP_DEFTRANSMITS;
    cstate->client_ext_ui_fds[0] = -1;
    cstate->client_ext_ui_fds[1] = -1;
}


/*
 * EapAuthWithPeer - Authenticate us with our peer (start client).
 *
 */
void
EapAuthWithPeer(unit, our_name)
    int unit;
    char *our_name;
{
    eap_state *cstate = &eap[unit];


	if (username[0] == 0) {
		/*
			no identity, get it from a plugin
		*/

		eap_ext 	*eap;

		for (eap = eap_extensions; eap; eap = eap->next) {
		   if (eap->identity
				&& (eap->identity(username, sizeof(username)) == EAP_NO_ERROR))
				break;
		}
	}
	
    cstate->our_identity = username;
    cstate->username = username;
    cstate->password = passwd;

    if (cstate->clientstate == EAPCS_INITIAL ||
	cstate->clientstate == EAPCS_PENDING) {
	/* lower layer isn't up - wait until later */
	cstate->clientstate = EAPCS_PENDING;
	return;
    }

    /*
     * We get here as a result of LCP coming up.
     * So even if EAP was open before, we will 
     * have to re-authenticate ourselves.
     */
    cstate->clientstate = EAPCS_LISTEN;
}


/*
 * EapAuthPeer - Authenticate our peer (start server).
 */
void
EapAuthPeer(unit, our_name)
    int unit;
    char *our_name;
{
    eap_state *cstate = &eap[unit];
      
    cstate->our_identity = user; 
    cstate->username = 0;
    cstate->password = 0;

    cstate->req_type = EAP_TYPE_IDENTITY;
    cstate->req_transmits = 0;
    cstate->req_id = 1;

    if (cstate->serverstate == EAPSS_INITIAL ||
	cstate->serverstate == EAPSS_PENDING) {
	/* lower layer isn't up - wait until later */
	cstate->serverstate = EAPSS_PENDING;
	return;
    }

    EapSendIdentityRequest(cstate);
    cstate->serverstate = EAPSS_INITIAL_CHAL;
}


/*
 * EapChallengeTimeout - Timeout expired on sending challenge.
 */
static void
EapChallengeTimeout(arg)
    void *arg;
{
    eap_state *cstate = (eap_state *) arg;
  
    /* if we aren't sending challenges, don't worry.  then again we */
    /* probably shouldn't be here either */
    if (cstate->serverstate != EAPSS_INITIAL_CHAL &&
	cstate->serverstate != EAPSS_RECHALLENGE)
	return;

    if (cstate->req_transmits >= cstate->max_transmits) {
	/* give up on peer */
	error("Peer failed to respond to EAP challenge");
	cstate->serverstate = EAPSS_BADAUTH;
	auth_peer_fail(cstate->unit, PPP_EAP);
	return;
    }

    if (cstate->req_type == EAP_TYPE_IDENTITY)
        EapSendIdentityRequest(cstate);		/* Re-send challenge */
    else
        EAPServerProcess(cstate, EAP_NOTIFICATION_TIMEOUT, 0, 0);
}


/*
 * EapRechallenge - Time to challenge the peer again.
 */
static void
EapRechallenge(arg)
    void *arg;
{
    eap_state  	*cstate = (eap_state *) arg;
    
    /* if we aren't sending a response, don't worry. */
    if (cstate->serverstate != EAPSS_OPEN)
	return;

    cstate->req_id++;
    cstate->req_transmits = 0;
    
    EAPServerProcess(cstate, EAP_NOTIFICATION_RESTART, 0, 0);
    
    cstate->serverstate = EAPSS_RECHALLENGE;
}

/*
 * EapLostSuccess - EAP success has been lost.
 *
 * Simulate an EAP success packet .
 */
void
EapLostSuccess(unit)
    int unit;
{
    eap_state *cstate = &eap[unit];
    u_char inpacket[EAP_HEADERLEN];
	u_char *inp = inpacket;

    PUTCHAR(EAP_SUCCESS, inp);		/* simulate success */
    PUTCHAR(cstate->resp_id, inp);  /* id must match the last response we sent */
    PUTSHORT(EAP_HEADERLEN, inp);

	EapReceiveSuccess(cstate, inpacket, EAP_HEADERLEN, inp, cstate->resp_id, 0);
}

/*
 * EapLostFailure - EAP failure has been lost.
 *
 * Simulate an EAP failure packet .
 */
void
EapLostFailure(unit)
    int unit;
{
    eap_state *cstate = &eap[unit];
    u_char inpacket[EAP_HEADERLEN];
	u_char *inp = inpacket;
	
	MAKEHEADER(inp, PPP_EAP);		/* paste in a EAP header */
    PUTCHAR(EAP_FAILURE, inp);		/* simulate failure */
    PUTCHAR(cstate->resp_id, inp);  /* id must match the last response we sent */
    PUTSHORT(EAP_HEADERLEN, inp);

	EapReceiveFailure(cstate, inpacket, EAP_HEADERLEN, inp, cstate->resp_id, 0);
}

/*
 * EapLowerUp - The lower layer is up.
 *
 * Start up if we have pending requests.
 */
static void
EapLowerUp(unit)
    int unit;
{
    eap_state *cstate = &eap[unit];
  
    if (cstate->clientstate == EAPCS_INITIAL)
	cstate->clientstate = EAPCS_CLOSED;
    else if (cstate->clientstate == EAPCS_PENDING)
	cstate->clientstate = EAPCS_LISTEN;

    if (cstate->serverstate == EAPSS_INITIAL)
	cstate->serverstate = EAPSS_CLOSED;
    else if (cstate->serverstate == EAPSS_PENDING) {
        EapSendIdentityRequest(cstate);
	cstate->serverstate = EAPSS_INITIAL_CHAL;
    }
}


/*
 * EapLowerDown - The lower layer is down.
 *
 * Cancel all timeouts.
 */
static void
EapLowerDown(unit)
    int unit;
{
    eap_state *cstate = &eap[unit];
  
    /* Timeout(s) pending?  Cancel if so. */
    if (cstate->serverstate == EAPSS_INITIAL_CHAL ||
	cstate->serverstate == EAPSS_RECHALLENGE)
	UNTIMEOUT(EapChallengeTimeout, cstate);
    else if (cstate->serverstate == EAPSS_OPEN
	     && cstate->req_interval != 0)
	UNTIMEOUT(EapRechallenge, cstate);

    cstate->clientstate = EAPCS_INITIAL;
    cstate->serverstate = EAPSS_INITIAL;

    if (cstate->client_ext) {
        cstate->client_ext->dispose(cstate->client_ext_ctx);
        free (cstate->client_ext_input);
        free (cstate->client_ext_output);
        cstate->client_ext = 0;
        cstate->client_ext_ctx = 0;
        cstate->client_ext_input = 0;
        cstate->client_ext_output = 0;
    }

    if (cstate->server_ext) {
        cstate->server_ext->dispose(cstate->server_ext_ctx);
        free (cstate->server_ext_input);
        free (cstate->server_ext_output);
        cstate->server_ext = 0;
        cstate->server_ext_ctx = 0;
        cstate->server_ext_input = 0;
        cstate->server_ext_output = 0;
    }
}


/*
 * EapProtocolReject - Peer doesn't grok EAP.
 */
static void
EapProtocolReject(unit)
    int unit;
{
    eap_state *cstate = &eap[unit];

    if (cstate->serverstate != EAPSS_INITIAL &&
	cstate->serverstate != EAPSS_CLOSED)
	auth_peer_fail(unit, PPP_EAP);
    if (cstate->clientstate != EAPCS_INITIAL &&
	cstate->clientstate != EAPCS_CLOSED)
	auth_withpeer_fail(unit, PPP_EAP);
    EapLowerDown(unit);		/* shutdown eap */
}

/*
 * EapInput - Input EAP packet.
 */
static void
EapInput(unit, inpacket, packet_len)
    int unit;
    u_char *inpacket;
    int packet_len;
{
    eap_state *cstate = &eap[unit];
    u_char *inp;
    u_char code, id;
    int len;
  
    /*
     * Parse header (code, id and length).
     * If packet too short, drop it.
     */
    inp = inpacket;
    if (packet_len < EAP_HEADERLEN) {
	EAPDEBUG(("EapInput: rcvd short header."));
	return;
    }
    GETCHAR(code, inp);
    GETCHAR(id, inp);
    GETSHORT(len, inp);
    if (len < EAP_HEADERLEN) {
	EAPDEBUG(("EapInput: rcvd illegal length."));
	return;
    }
    if (len > packet_len) {
	EAPDEBUG(("EapInput: rcvd short packet."));
	return;
    }
    len -= EAP_HEADERLEN;
  
    /*
     * Action depends on code (as in fact it usually does :-).
     */
   switch (code) {
    case EAP_REQUEST:
	EapReceiveRequest(cstate, inpacket, packet_len, inp, id, len);
	break;
    
    case EAP_RESPONSE:
	EapReceiveResponse(cstate, inpacket, packet_len, inp, id, len);
	break;
    
    case EAP_FAILURE:
	EapReceiveFailure(cstate, inpacket, packet_len, inp, id, len);
	break;

    case EAP_SUCCESS:
	EapReceiveSuccess(cstate, inpacket, packet_len, inp, id, len);
	break;

    default:				/* Need code reject? */
	warning("Unknown EAP code (%d) received.", code);
	break;
    }
}

/*
 * EapSendIdentityRequest - Send an Request for Identity.
 */
static void
EapSendIdentityRequest(cstate)
    eap_state *cstate;
{
    u_char *outp;
    int outlen = 0;

    outlen = EAP_HEADERLEN + sizeof (u_char);
    outp = outpacket_buf;
    MAKEHEADER(outp, PPP_EAP);		/* paste in a EAP header */
    PUTCHAR(EAP_REQUEST, outp);
    PUTCHAR(cstate->req_id, outp);
    PUTSHORT(outlen, outp);
    PUTCHAR(cstate->req_type, outp);

    output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN);

    TIMEOUT(EapChallengeTimeout, cstate, cstate->timeouttime);
    ++cstate->req_transmits;
}

/*
 * EAPAllowedAddr - check with the plugin if the address is OK.
 */
int
EAPAllowedAddr(unit, addr)
    int unit;
    u_int32_t addr;
{
    // always say OK for now.
    return 1;
}

/*
 * EapExtAdd - add a new eap type handler.
 */
int
EapExtAdd(eap_ext *newext)
{
    eap_ext *eap, *last;

    for (last = eap = eap_extensions; eap; last = eap, eap = eap->next) {
        if (eap->type == newext->type)
            return 1; // already exists
    }

	if (last) 
		last->next = newext;
	else 
		eap_extensions = newext;
	newext->next = NULL;
    return 0;
}

/*
 * EapSupportedType - check if an eap type is supported with the specified flags.
 */
static 
eap_ext * EapSupportedType(int type)
{
    eap_ext 	*eap;
    u_int32_t	flags = 0;
    

    for (eap = eap_extensions; eap; eap = eap->next) {
       if (eap->type == type
            && (eap->flags & flags) == flags)
            return eap;
    }
    
    return 0;
}

/*
 * EapReceiveRequest - Receive Challenge and send Response.
 */
static void
EapReceiveRequest(cstate, inpacket, packet_len, inp, id, len)
    eap_state *cstate;
    u_char *inpacket;
    int packet_len;
    u_char *inp;
    int id;
    int len;
{
    int req, err, outlen;
    u_char *outp;
    struct eap_ext	*eap;

    if (cstate->clientstate == EAPCS_CLOSED ||
	cstate->clientstate == EAPCS_PENDING) {
	EAPDEBUG(("EapReceiveRequest: in state %d", cstate->clientstate));
	return;
    }

    if (len < 1) {
	EAPDEBUG(("EapReceiveRequest: rcvd short packet."));
	return;
    }

    GETCHAR(req, inp);
    len -= 1;

    switch (req) {
        case EAP_TYPE_IDENTITY:
            outlen = EAP_HEADERLEN + sizeof(char) + strlen(cstate->our_identity);
            outp = outpacket_buf;
            MAKEHEADER(outp, PPP_EAP);
            PUTCHAR(EAP_RESPONSE, outp);	/* we are a response */
            PUTCHAR(id, outp);	/* copy id from request packet */
            PUTSHORT(outlen, outp);	/* packet length */
            PUTCHAR(req, outp);		/* copy type from request packet */
            BCOPY(cstate->our_identity, outp, outlen - EAP_HEADERLEN - sizeof(char)); /* append the response */

            /* send the packet */
            output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN);
            break;
            
        case EAP_TYPE_NOTIFICATION:
			
			/* print the remote message */
			PRINTMSG(inp, len);
			
            /* send an empty response */
			outlen = EAP_HEADERLEN;
            outp = outpacket_buf;
            MAKEHEADER(outp, PPP_EAP);
            PUTCHAR(EAP_RESPONSE, outp);	/* we are a response */
            PUTCHAR(id, outp);	/* copy id from request packet */
            PUTSHORT(outlen, outp);	/* packet length */
            /* send the packet */
            output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN);
            break;

        case EAP_TYPE_NAK:
            break;
            
        default:
            if (cstate->client_ext) {
                /* if a request was already received, check that we get the same type */
                if (cstate->client_ext->type != req) {
                    error("EAP received an unexpected request for type %d", req);
                    break;
                }
            }
            else {
                /* first time, create client eap extension context */
                eap = EapSupportedType(req);
                if (eap == NULL) {
					error("EAP refuse to authenticate using type %d", req);

					/* send a NAK with the type we support */
					if (eap_extensions) {
						error("EAP send NAK requesting type %d", eap_extensions->type);
						outlen = EAP_HEADERLEN + sizeof(char) + sizeof(char);
						outp = outpacket_buf;
						MAKEHEADER(outp, PPP_EAP);
						PUTCHAR(EAP_RESPONSE, outp);	/* we are a response */
						PUTCHAR(id, outp);	/* copy id from request packet */
						PUTSHORT(outlen, outp);	/* packet length */
						PUTCHAR(EAP_TYPE_NAK, outp);		/* type NAK */
						PUTCHAR(eap_extensions->type, outp);		/* copy the type we prefer */

						/* send the packet */
						output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN);
					}
                    break;
                }

                /* init client context on first request */
                /* we don't necessarily know peer_name at this point */
                cstate->client_ext = eap;

                cstate->client_ext_input = (EAP_Input *)malloc(sizeof(EAP_Input));
                cstate->client_ext_output = (EAP_Output *)malloc(sizeof(EAP_Output));
                if (cstate->client_ext_input == 0 || cstate->client_ext_output == 0)
                    novm("Couldn't allocate memory for EAP Plugin data");
                
                /* this part is initialized only once */
                cstate->client_ext_input->size = sizeof(EAP_Input);
                cstate->client_ext_output->size = sizeof(EAP_Output);
                cstate->client_ext_input->mode = 0; // client mode
                cstate->client_ext_input->initial_id = 0; // no sense in client mode
                cstate->client_ext_input->mtu = netif_get_mtu(cstate->unit);
                cstate->client_ext_input->identity = cstate->our_identity;
                cstate->client_ext_input->username = cstate->username;
                cstate->client_ext_input->password = cstate->password;
                cstate->client_ext_input->log_debug = dbglog;
                cstate->client_ext_input->log_error = error;

                /* this part depends on the message */
            	cstate->client_ext_input->notification = EAP_NOTIFICATION_NONE; // no notification in the init message
                cstate->client_ext_input->data = cstate->client_ext->plugin; // bundle ref
                cstate->client_ext_input->data_len = 0;
                
                err = cstate->client_ext->init(cstate->client_ext_input, &cstate->client_ext_ctx);
                if (err) {
                    error("EAP cannot initialize plugin for %s (request type %d)", eap->name ? eap->name : "???", req);
					auth_withpeer_fail(cstate->unit, PPP_EAP);
                    break;
                }
            }
            
            /* process the request */
            EAPClientProcess(cstate, EAP_NOTIFICATION_PACKET, inpacket, packet_len);
            
    }
    
}

/*
 * EapReceiveResponse - Receive and process response.
 */
static void
EapReceiveResponse(cstate, inpacket, packet_len,  inp, id, len)
    eap_state *cstate;
    u_char *inpacket;
    int packet_len;
    u_char *inp;
    int id;
    int len;
{
    u_char type, auth_type;
    int  err;
    eap_ext	*eap, *last_eap;

    if (cstate->serverstate == EAPSS_CLOSED ||
	cstate->serverstate == EAPSS_PENDING) {
	EAPDEBUG(("EapReceiveResponse: in state %d", cstate->serverstate));
	return;
    }

    if (id != cstate->req_id)
	return;			/* doesn't match ID of last challenge */

    if (len < 1) {
	EAPDEBUG(("EapReceiveResponse: rcvd short packet."));
	return;
    }

    GETCHAR(type, inp);		/* get type */
    len -= 1;
    if ((type != EAP_TYPE_NAK && type != cstate->req_type)
		|| (type == EAP_TYPE_NAK && cstate->server_ext == 0)) {
	EAPDEBUG(("EapReceiveResponse: type doesn't match our request."));
	return;			/* doesn't match type of last challenge */
    }

    UNTIMEOUT(EapChallengeTimeout, cstate);            

    switch (type) { 

        case EAP_TYPE_IDENTITY:
            
            if (len >= MAX_NAME_LENGTH)
                len = MAX_NAME_LENGTH - 1;
        
            BCOPY(inp, cstate->peer_identity, len);
            cstate->peer_identity[len] = 0;

            /* XXX : Lookup to find out the protocol to use based on identity */

			/* use the first plugin available */
			eap = eap_extensions; 

            if (eap == NULL) {
                error("No EAP server protocol available");
				cstate->serverstate = EAPSS_BADAUTH;
				auth_peer_fail(cstate->unit, PPP_EAP);
                break;
            }

			/* no break */
			
        case EAP_TYPE_NAK:

			if (type == EAP_TYPE_NAK) {
								
				GETCHAR(auth_type, inp);		/* get authentication type */
				len -= 1;

				last_eap = cstate->server_ext;

				/* free previous selected eap module */ 
				cstate->server_ext->dispose(cstate->server_ext_ctx);
				free (cstate->server_ext_input);
				free (cstate->server_ext_output);
				cstate->server_ext = 0;
				cstate->server_ext_ctx = 0;
				cstate->server_ext_input = 0;
				cstate->server_ext_output = 0;
				
                /* check if we support the type desired by the client */
                eap = EapSupportedType(auth_type);
                if (eap == NULL) {
				
					/* if not, then use next in list */
					eap = last_eap->next; 
					if (eap == NULL) {
						error("Server and client disagree on EAP type");
						cstate->serverstate = EAPSS_BADAUTH;
						auth_peer_fail(cstate->unit, PPP_EAP);
						break;
					}
                }
			}
                                   
            cstate->server_ext = eap;
            cstate->req_type = eap->type;

            cstate->server_ext_input = (EAP_Input *)malloc(sizeof(EAP_Input));
            cstate->server_ext_output = (EAP_Output *)malloc(sizeof(EAP_Output));
            if (cstate->server_ext_input == 0 || cstate->server_ext_output == 0)
                novm("Couldn't allocate memory for EAP Plugin data");

            /* this part is initialized only once */
            cstate->server_ext_input->size = sizeof(EAP_Input);
            cstate->server_ext_output->size = sizeof(EAP_Output);
            cstate->server_ext_input->mode = 1; // server mode
            cstate->server_ext_input->initial_id = cstate->req_id + 1;
            cstate->server_ext_input->mtu = netif_get_mtu(cstate->unit);
            cstate->server_ext_input->identity = cstate->peer_identity;
            cstate->server_ext_input->username = 0; /* irrelevant in server mode */
            cstate->server_ext_input->password = 0; /* irrelevant in server mode */
            cstate->server_ext_input->log_debug = dbglog;
            cstate->server_ext_input->log_error = error;
 
            /* this part depends on the message */
            cstate->server_ext_input->notification = EAP_NOTIFICATION_NONE; // no notification in the init message
            cstate->server_ext_input->data = cstate->server_ext->plugin;
            cstate->server_ext_input->data_len = 0;
            
            err = cstate->server_ext->init(cstate->server_ext_input, &cstate->server_ext_ctx);
            if (err) {
                error("EAP cannot initialize plugin for %s (request type %d)", eap->name ? eap->name : "???", cstate->req_type);
                break;
            }
            
            /* now, start conversation */
            EAPServerProcess(cstate, EAP_NOTIFICATION_START, 0, 0);
            break;
        
        default:

            EAPServerProcess(cstate, EAP_NOTIFICATION_PACKET, inpacket, packet_len);
    }
}

/*
 * EapReceiveSuccess - Receive Success
 */
static void
EapReceiveSuccess(cstate, inpacket, packet_len, inp, id, len)
    eap_state *cstate;
    u_char *inpacket;
    int packet_len;
    u_char *inp;
    u_char id;
    int len;
{

    if (cstate->clientstate == EAPCS_OPEN)
	/* presumably an answer to a duplicate response */
	return;

    EAPClientProcess(cstate, EAP_NOTIFICATION_PACKET, inpacket, packet_len);
}


/*
 * EapReceiveFailure - Receive failure.
 */
static void
EapReceiveFailure(cstate, inpacket, packet_len, inp, id, len)
    eap_state *cstate;
    u_char *inpacket;
    int packet_len;
    u_char *inp;
    u_char id;
    int len;
{

    EAPClientProcess(cstate, EAP_NOTIFICATION_PACKET, inpacket, packet_len);
}

/*
 * EAPClientProcess - Process a packet in a client context.
 */
static int
EAPClientProcess(cstate, notification, inpacket, packet_len)
    eap_state *cstate;
    u_int16_t notification;
    u_char *inpacket;
    int packet_len;
{
    int err;
    
    if (cstate->client_ext == 0)
        /* ignore the request */
    	return 0;

    /* setup in and out structures */
    cstate->client_ext_input->notification = notification;
    cstate->client_ext_input->data = inpacket;
    cstate->client_ext_input->data_len = packet_len;
    cstate->client_ext_output->action = EAP_ACTION_NONE;
    cstate->client_ext_output->data = 0;
    cstate->client_ext_output->data_len = 0;
    cstate->client_ext_output->username = 0;

    err = cstate->client_ext->process(cstate->client_ext_ctx, cstate->client_ext_input, cstate->client_ext_output);
    if (err) {
        error("EAP error while processing packet for %s (request type %d, error %d)", 
                cstate->client_ext->name ? cstate->client_ext->name : "???", cstate->client_ext->type, err);
        return -1;
    }

    EAPClientAction(cstate);
    return 0;
}

/*
 * EAPClientGetAttributes - get client specific attributes.
 */
static void
EAPClientGetAttributes(cstate)
    eap_state *cstate;
{
    EAP_Attribute attribute;
    int err;
    char *str;
    
    /* let's see it we have mppe keys */
    if (cstate->client_ext->attribute == 0)
        return;

    attribute.type = EAP_ATTRIBUTE_MPPE_SEND_KEY;
    err = cstate->client_ext->attribute(cstate->client_ext_ctx, &attribute);
    if (err) {
        str = "MPPE_SEND_KEY";
        goto bad;
    }

    bcopy(attribute.data, mppe_send_key, MIN(attribute.data_len, MPPE_MAX_KEY_LEN));

    attribute.type = EAP_ATTRIBUTE_MPPE_RECV_KEY;
    err = cstate->client_ext->attribute(cstate->client_ext_ctx, &attribute);
    if (err) {
        str = "MPPE_RECV_KEY";
        goto bad;
    }

    bcopy(attribute.data, mppe_recv_key, MIN(attribute.data_len, MPPE_MAX_KEY_LEN));
    
    return;
    
bad:
    dbglog("EAP plugin %s (type %d) does not have %s attribute", 
        cstate->client_ext->name ? cstate->client_ext->name : "???", cstate->client_ext->type, str);
        
}

/*
 * EAPInput_fd - called when activity occurs on a file descriptor, 
 * so eap has a chance to test its file descriptors.
 */
void EAPInput_fd(void)
{
    int unit = 0;
    eap_state *cstate = &eap[unit];
    char	result;

    if (cstate->client_ext_ui_fds[0] != -1 && is_ready_fd(cstate->client_ext_ui_fds[0])) {
    
        result = 0;
        read(cstate->client_ext_ui_fds[0], &result, 1);
        
        wait_input_hook = 0;
        remove_fd(cstate->client_ext_ui_fds[0]);
        close(cstate->client_ext_ui_fds[0]);
        close(cstate->client_ext_ui_fds[1]);
        cstate->client_ext_ui_fds[0] = -1;
        cstate->client_ext_ui_fds[1] = -1;

        if (result == -1) {
            error("EAP error while requesting user input for %s (request type %d)", 
                    cstate->client_ext->name ? cstate->client_ext->name : "???", cstate->client_ext->type);
            return;
        }

        EAPClientProcess(cstate, EAP_NOTIFICATION_DATA_FROM_UI, cstate->client_ext_ui_data, cstate->client_ext_ui_data_len);
    }
}

/*
 * EAPClientUIThread - XXX User interface thread.
 */
void *EAPClientUIThread(void *arg)
{
    int 	unit = (int)arg;
    eap_state 	*cstate = &eap[unit];
    char	result = -1;
    int 	err;

    if (pthread_detach(pthread_self()) == 0) {
        
        if (cstate->client_ext->interactive_ui) {
            err = cstate->client_ext->interactive_ui(cstate->client_ext_ui_data, cstate->client_ext_ui_data_len, 
                            &cstate->client_ext_ui_data, &cstate->client_ext_ui_data_len);
            if (err == 0)
                result = 0;
        }
    }

    write(eap->client_ext_ui_fds[1], &result, 1);
    return 0;
}

/*
 * EAPClientInvokeUI - Perform the action in the client context.
 */
static int
EAPClientInvokeUI(cstate)
    eap_state *cstate;
{
    if (pipe(cstate->client_ext_ui_fds) < 0) {
        error("EAP failed to create pipe for User Interface...\n");
        return -1;
    }

    if (pthread_create(&cstate->client_ui_thread, NULL, EAPClientUIThread, (void*)cstate->unit)) {
        error("EAP failed to create thread for client User Interface...\n");
        close(cstate->client_ext_ui_fds[0]);
        close(cstate->client_ext_ui_fds[1]);
        return -1;
    }
    
    wait_input_hook = EAPInput_fd;
    add_fd(cstate->client_ext_ui_fds[0]);
    return 0;
}

/*
 * EAPClientAction - Perform the action in the client context.
 */
static void
EAPClientAction(cstate)
    eap_state *cstate;
{
    EAP_Output *eap_out = cstate->client_ext_output;
    u_char *outp;

    switch (eap_out->action) {
        case EAP_ACTION_NONE:
            break;
            
        case EAP_ACTION_SEND_WITH_TIMEOUT:
        case EAP_ACTION_SEND_AND_DONE:
            // irrelevant for client
            break;

        case EAP_ACTION_SEND:
            if (eap_out->data == 0 ||
                eap_out->data_len < EAP_HEADERLEN ||
                eap_out->data_len > PPP_MRU) {
                error("EAP plugin tries to send a packet with with incorrect data");
                break;
            }

            outp = outpacket_buf;
            MAKEHEADER(outp, PPP_EAP);		/* paste in a EAP header */
            BCOPY(eap_out->data, outp, eap_out->data_len);
            
			cstate->resp_id = outp[1]; 	/* let's copy the id for future use */
			
            if (cstate->client_ext->free)
                cstate->client_ext->free(cstate->client_ext_ctx, eap_out);
                
            /* send the packet */
            output(cstate->unit, outpacket_buf, eap_out->data_len + PPP_HDRLEN);
            break;
            
        case EAP_ACTION_INVOKE_UI:
            cstate->client_ext_ui_data = eap_out->data;
            cstate->client_ext_ui_data_len = eap_out->data_len;
            EAPClientInvokeUI(cstate);
            break;
            
        case EAP_ACTION_ACCESS_GRANTED:
            EAPClientGetAttributes(cstate);
            cstate->clientstate = EAPCS_OPEN;
            auth_withpeer_success(cstate->unit, PPP_EAP, 0);
            break;
            
        case EAP_ACTION_ACCESS_DENIED:
            error("EAP authentication failed");
            auth_withpeer_fail(cstate->unit, PPP_EAP);
            break;

        case EAP_ACTION_CANCEL:
            auth_withpeer_cancelled(cstate->unit, PPP_EAP);
            break;
            
    }
}

/*
 * EAPServerProcess - Process a packet in a client context.
 */
static int
EAPServerProcess(cstate, notification, inpacket, packet_len)
    eap_state *cstate;
    u_int16_t notification;
    u_char *inpacket;
    int packet_len;
{
    int err;
    
    if (cstate->server_ext == 0)
        /* ignore the call */
    	return 0;

    /* setup in and out structures */
    cstate->server_ext_input->notification = notification;
    cstate->server_ext_input->data = inpacket;
    cstate->server_ext_input->data_len = packet_len;
    cstate->server_ext_output->action = EAP_ACTION_NONE;
    cstate->server_ext_output->data = 0;
    cstate->server_ext_output->data_len = 0;
    cstate->server_ext_output->username = 0;
    
    err = cstate->server_ext->process(cstate->server_ext_ctx, cstate->server_ext_input, cstate->server_ext_output);
    if (err) {
        error("EAP error while processing packet for %s (request type %d, error %d)", 
                cstate->server_ext->name ? cstate->server_ext->name : "???", cstate->server_ext->type, err);
        return -1;
    }

    EAPServerAction(cstate);
    return 0;
}

/*
 * EAPServerGetAttributes - get server specific attributes.
 */
static void
EAPServerGetAttributes(cstate)
    eap_state *cstate;
{
    EAP_Attribute attribute;
    int err;
    char *str;
    
    /* let's see it we have mppe keys */
    if (cstate->server_ext->attribute == 0)
        return;

    attribute.type = EAP_ATTRIBUTE_MPPE_SEND_KEY;
    err = cstate->server_ext->attribute(cstate->server_ext_ctx, &attribute);
    if (err) {
        str = "MPPE_SEND_KEY";
        goto bad;
    }

    bcopy(attribute.data, mppe_send_key, MIN(attribute.data_len, MPPE_MAX_KEY_LEN));

    attribute.type = EAP_ATTRIBUTE_MPPE_RECV_KEY;
    err = cstate->server_ext->attribute(cstate->server_ext_ctx, &attribute);
    if (err) {
        str = "MPPE_RECV_KEY";
        goto bad;
    }

    bcopy(attribute.data, mppe_recv_key, MIN(attribute.data_len, MPPE_MAX_KEY_LEN));
    
    return;
    
bad:
    dbglog("EAP plugin %s (type %d) does not have %s attribute", 
        cstate->server_ext->name ? cstate->server_ext->name : "???", cstate->server_ext->type, str);
        
}

/*
 * EAPClientAction - Perform the action in the client context.
 */
static void
EAPServerAction(cstate)
    eap_state *cstate;
{

    EAP_Output *eap_out = cstate->server_ext_output;
    u_char code, *outp;
    int old_state;
    char *name;
    
    switch (eap_out->action) {
        case EAP_ACTION_NONE:
            break;
            
        case EAP_ACTION_SEND_WITH_TIMEOUT:
        case EAP_ACTION_SEND_AND_DONE:
        case EAP_ACTION_SEND:
            if (eap_out->data == 0 ||
                eap_out->data_len < EAP_HEADERLEN ||
                eap_out->data_len > PPP_MRU) {
                error("EAP plugin tries to send a packet with with incorrect data");
                break;
            }
            outp = outpacket_buf;
            MAKEHEADER(outp, PPP_EAP);		/* paste in a EAP header */
            BCOPY(eap_out->data, outp, eap_out->data_len);

            code = outp[0];
            cstate->req_transmits = 0;
            cstate->req_id = outp[1]; 	/* let's copy the id for future use */
            
            if (cstate->server_ext->free)
                cstate->server_ext->free(cstate->server_ext_ctx, eap_out);
                
            /* send the packet */
            output(cstate->unit, outpacket_buf, eap_out->data_len + PPP_HDRLEN);
            
            if (eap_out->action == EAP_ACTION_SEND_WITH_TIMEOUT)
                TIMEOUT(EapChallengeTimeout, cstate, cstate->timeouttime);
            ++cstate->req_transmits;

            if (eap_out->action == EAP_ACTION_SEND_AND_DONE) {

                /*
                * If we have received a duplicate or bogus Response,
                * we have to send the same answer (Success/Failure)
                * as we did for the first Response we saw.
                * The packet we are sending is a result of this retransmission,
                * so nothing more to do.
                */
                if (cstate->serverstate == EAPSS_OPEN
                    || cstate->serverstate == EAPSS_BADAUTH)
                    break;

                /*
                * the eap server plugin is done. Let's see what is the result code.
                * the plugin can return a username to override the identity
                */
                name = eap_out->username ? eap_out->username : cstate->peer_identity;

                if (code == EAP_SUCCESS) {
                    UNTIMEOUT(EapChallengeTimeout, cstate);            
                    old_state = cstate->serverstate;
                    cstate->serverstate = EAPSS_OPEN;
                    if (old_state == EAPSS_INITIAL_CHAL) {
                        EAPServerGetAttributes(cstate);
                        auth_peer_success(cstate->unit, PPP_EAP, 0, name, strlen(name));
                    }
                    if (cstate->req_interval != 0)
                        TIMEOUT(EapRechallenge, cstate, cstate->req_interval);
                            
                    notice("EAP peer authentication succeeded for %s", name);
                }
                else 
                {
                    UNTIMEOUT(EapChallengeTimeout, cstate);
                    error("EAP peer authentication failed for remote host %s", name);
                    cstate->serverstate = EAPSS_BADAUTH;
                    auth_peer_fail(cstate->unit, PPP_EAP);        
                }
            }
            break;
            
        case EAP_ACTION_INVOKE_UI:            
        case EAP_ACTION_ACCESS_GRANTED:
        case EAP_ACTION_ACCESS_DENIED:
        case EAP_ACTION_CANCEL:
            // no used for server
            break;
    }
}

/*
 * EapPrintPkt - print the contents of a EAP packet.
 */
static char *EapCodenames[] = {
    "Request", "Response", "Success", "Failure"
};
static char *EapRequestnames[] = {
    "Identity", "Notification", "Nak"
};

static int
EapPrintPkt(p, plen, printer, arg)
    u_char *p;
    int plen;
    void (*printer) __P((void *, char *, ...));
    void *arg;
{
    int code, id, len, req;
    int clen;
    u_char x;
    eap_ext	*eap;

    if (plen < EAP_HEADERLEN)
	return 0;
    GETCHAR(code, p);
    GETCHAR(id, p);
    GETSHORT(len, p);
    if (len < EAP_HEADERLEN || len > plen)
	return 0;

    if (code >= 1 && code <= sizeof(EapCodenames) / sizeof(char *))
	printer(arg, " %s", EapCodenames[code-1]);
    else
	printer(arg, " code=0x%x", code);
    printer(arg, " id=0x%x", id);
    len -= EAP_HEADERLEN;
    switch (code) {
    case EAP_REQUEST:
    case EAP_RESPONSE:
	if (len < 1)
	    break;
        GETCHAR(req, p);
        if (req >= 1 && req <= sizeof(EapRequestnames) / sizeof(char *))
            printer(arg, " %s ", EapRequestnames[req-1]);
        else if (eap = EapSupportedType(req))
            printer(arg, " %s ", eap->name ? eap->name : "???");
        else
            printer(arg, " type=0x%x ", req);
        len -= 1;
        if (len == 0)
            break;
        switch (req) {
            case EAP_TYPE_IDENTITY: 
            case EAP_TYPE_NOTIFICATION: 
                printer(arg, "<");
                print_string((char *)p, len, printer, arg);
                printer(arg, ">");
                break;
            default :
                eap = EapSupportedType(req);
                if (eap && eap->print_packet) {
                    eap->print_packet(printer, arg, code, p, len);
                }
                else {
                    printer(arg, "<");
                    clen = len;
                    for (; clen > 0; clen--) {
                        GETCHAR(x, p);
                        printer(arg, "%.2x", x);
                    }
                    printer(arg, ">");
                }
        }
	break;
    case EAP_FAILURE:
    case EAP_SUCCESS:
	printer(arg, " ");
	print_string((char *)p, len, printer, arg);
	break;
    default:
	for (clen = len; clen > 0; --clen) {
	    GETCHAR(x, p);
	    printer(arg, " %.2x", x);
	}
    }

    return len + EAP_HEADERLEN + 1;
}

/*
 * EapGetClientSecret - get the secret for a given name.
 */
int
EapGetClientSecret(void *cookie, u_char *our_name, u_char *peer_name, u_char *secret, int *secretlen)
{
    eap_state *cstate = (eap_state *)cookie;

    /* get secret for authenticating ourselves with the specified host */
    if (!get_secret(cstate->unit, our_name, peer_name, secret, secretlen, 0)) {
        *secretlen = 0;		/* assume null secret if can't find one */
        warning("No EAP secret found for authenticating us to %s", peer_name);
        return 1;
    }
    return 0;
}

/*
 * EapGetServerSecret - get the secret for a given name.
 */
int
EapGetServerSecret(void *cookie, u_char *our_name, u_char *peer_name, u_char *secret, int *secretlen)
{
    eap_state *cstate = (eap_state *)cookie;
    
    /* get secret for authenticating ourselves with the specified host */
    if (!get_secret(cstate->unit, our_name, peer_name, secret, secretlen, 1)) {
        *secretlen = 0;		/* assume null secret if can't find one */
        warning("No EAP secret found for authenticating %s", peer_name);
        return 1;
    }
    return 0;
}

/*
 * eaploadplugin - load the eap plugin
 */
static int
eaploadplugin(argv)
    char **argv;
{
    char *arg = *argv;
    int err;
    eap_ext *eap;
    
    eap = (eap_ext *)malloc(sizeof(eap_ext));
    if (eap == 0)
        novm("Couldn't allocate memory for EAP plugin");
    
    err = sys_eaploadplugin(*argv, eap);
    if (err) {
	option_error("Couldn't load EAP plugin %s", arg);
		// continue without loading plugin
        return 1;
    }

    if (eap->init == 0 || eap->dispose == 0 || eap->process == 0) {
	option_error("EAP plugin %s has no Init() Dispose() or Process() function", arg);
        return 0;
    }
                    
    if (EapSupportedType(eap->type)) {
	option_error("EAP plugin %s is trying to use an already loaded EAP type %d", arg, eap->type);
        return 0;
    }
    
    EapExtAdd(eap);
    //info("Plugin %s loaded.", arg);

    return 1;
}

