/**
 * A client-side 802.1x implementation 
 *
 * This code is released under both the GPL version 2 and BSD licenses.
 * Either license may be used.  The respective licenses are found below.
 *
 * Copyright (C) 2002 Bryan D. Payne & Nick L. Petroni Jr.
 * All Rights Reserved
 *
 * --- GPL Version 2 License ---
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * --- BSD License ---
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  - All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *       This product includes software developed by the University of
 *       Maryland at College Park and its contributors.
 *  - Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*******************************************************************
 * The driver function for a Linux application layer EAPOL 
 * implementation
 * File: statemachine.c
 *
 * Authors: Chris.Hessing@utah.edu
 *
 * $Id: statemachine.c,v 1.20 2004/08/10 01:59:26 chessing Exp $
 * $Date: 2004/08/10 01:59:26 $
 * $Log: statemachine.c,v $
 * Revision 1.20  2004/08/10 01:59:26  chessing
 *
 * Added support for the SNMP supplicant counters defined in the IEEE 802.1X-2001 document.
 *
 * Revision 1.19  2004/07/15 04:15:35  chessing
 *
 * True/false int values are now bit flags in a byte.  PEAP now calls back in to functions from eap.c for phase 2 methods.  This allows for any phase 1 EAP type to work.  This resulted in some major changes the functions in eap.c, and in peap_phase2.c.  PEAP has been tested using both MS-CHAPv2, and EAP-MD5 as inner types.  More types need to be tested, and enabled.
 *
 * Revision 1.18  2004/06/15 03:22:29  chessing
 *
 * XSupplicant Release 1.0
 *
 *
 *******************************************************************/

#include <stdio.h>
#include <netinet/in.h>

#include "snmp.h"
#include "statemachine.h"
#include "xsup_debug.h"
#include "frame_structs.h"
#include "config.h"
#include "eap.h"
#include "eapol.h"
#include "xsup_err.h"
#include "cardif/cardif.h"
#include "eap_types/leap/eapleap.h"

/******************************************
 *
 * Decrement a value, as long as it is greater than 0.
 *
 ******************************************/
void dec_if_nz(int *decval)
{
  if (!decval) return;

  if (*decval > 0) (*decval)--;
}

/******************************************
 *
 * Initalize the state machine
 *
 ******************************************/
int statemachine_init(struct interface_data *newint)
{
  if (!newint) 
    {
      debug_printf(DEBUG_NORMAL, "newint == NULL in statemachine_init()!\n");
      return XEMALLOC;
    }

  newint->statemachine = (struct dot1x_state *)malloc(sizeof(struct dot1x_state));
  if (newint->statemachine == NULL) return XEMALLOC;

  // Make sure our state machine is in initalize mode.
  newint->statemachine->initialize = 1;

  // Now, we want to set up a few defaults as per the 802.1x doc, and
  // initalize a few other statemachine variables that we will be needing.
  newint->statemachine->authPeriod = 30;
  newint->statemachine->authWhile = newint->statemachine->authPeriod;

  newint->statemachine->heldPeriod = 60;
  newint->statemachine->heldWhile = newint->statemachine->heldPeriod;

  newint->statemachine->startPeriod = 30;
  newint->statemachine->startWhen = 0;     // Trigger sending an EAPOL-Start
  newint->statemachine->maxStart = 3;

  // Set up our inital state.
  newint->statemachine->reqId = FALSE;
  newint->statemachine->userLogoff = FALSE;
  newint->statemachine->logoffSent = FALSE;
  newint->statemachine->reqAuth = FALSE;
  newint->statemachine->eapSuccess = FALSE;
  newint->statemachine->eapFail = FALSE;
  newint->statemachine->startCount = 0;
  newint->statemachine->previousId = 0xff;
  newint->statemachine->receivedId = 0xff;
  newint->statemachine->suppStatus = UNAUTHORIZED;

  newint->statemachine->tick = FALSE;

  return XENONE;
}

/******************************************
 *
 * Process the state machine, send a frame if we need to.  Returns >0 if
 * there is a frame to be send.
 *
 ******************************************/
int statemachine_run(struct interface_data *thisint, char *inframe, 
		     int insize, char *outframe, int *outsize)
{
  int retVal = XENONE;

  if ((!thisint) || (!outframe) || (!outsize))
    {
      debug_printf(DEBUG_NORMAL, "Invalid data passed in to statemachine_run()!\n");
      return XEMALLOC;
    }

  if (thisint->statemachine == NULL)
    {
      debug_printf(DEBUG_NORMAL, "Statemachine is not set up correctly in statemachine_run()!\n");
      return XEMALLOC;
    }

  if (thisint->statemachine->tick == TRUE)
    {
      // The clock ticked -- Update all of the needed counters.
      dec_if_nz(&thisint->statemachine->authWhile);
      dec_if_nz(&thisint->statemachine->heldWhile);
      dec_if_nz(&thisint->statemachine->startWhen);
      thisint->statemachine->tick = FALSE;
      debug_printf(DEBUG_EVERYTHING, "Clock tick! authWhile=%d heldWhile=%d "
		   "startWhen=%d curState=",
		   thisint->statemachine->authWhile,
		   thisint->statemachine->heldWhile,
		   thisint->statemachine->startWhen,
		   thisint->statemachine->curState);
      
      switch (thisint->statemachine->curState)
	{
	case DISCONNECTED:
	  debug_printf_nl(DEBUG_EVERYTHING, "DISCONNECTED\n");
	  break;
	case LOGOFF:
	  debug_printf_nl(DEBUG_EVERYTHING, "LOGOFF\n");
	  break;
	case ACQUIRED:
	  debug_printf_nl(DEBUG_EVERYTHING, "ACQUIRED\n");
	  break;
	case AUTHENTICATING:
	  debug_printf_nl(DEBUG_EVERYTHING, "AUTHENTICATING\n");
	  break;
	case AUTHENTICATED:
	  debug_printf_nl(DEBUG_EVERYTHING, "AUTHENTICATED\n");
	  break;
	case CONNECTING:
	  debug_printf_nl(DEBUG_EVERYTHING, "CONNECTING\n");
	  break;
	case HELD:
	  debug_printf_nl(DEBUG_EVERYTHING, "HELD\n");
	  break;
	default:
	  debug_printf_nl(DEBUG_EVERYTHING, "UNKNOWN!\n");
	  break;
	}
    }

  thisint->statemachine->portEnabled = get_if_state(thisint);

  // Our state machine is in initalize mode, so set things up.  (THIS ONE
  // MUST COME LAST, before the switch!)
  if ((thisint->statemachine->initialize == TRUE) || 
      (thisint->statemachine->portEnabled == FALSE))
    {
      debug_printf(DEBUG_STATE, "(global) -> DISCONNECTED\n");
      thisint->statemachine->curState = DISCONNECTED;
      thisint->statemachine->initialize = FALSE;
      if (thisint->flags & IS_WIRELESS) cardif_reset_keys(thisint);
    }
  else if (thisint->statemachine->eapFail &&
	   !(thisint->statemachine->initialize || 
	     !thisint->statemachine->portEnabled) &&
	   !thisint->statemachine->userLogoff &&
	   !thisint->statemachine->logoffSent)
    {
      debug_printf(DEBUG_STATE, "(global) -> HELD\n");
      thisint->statemachine->lastState = thisint->statemachine->curState;
      thisint->statemachine->curState = HELD;
      if (thisint->flags & IS_WIRELESS) cardif_reset_keys(thisint);
    }
  else if (thisint->statemachine->userLogoff &&
	   !thisint->statemachine->logoffSent &&
	   !(thisint->statemachine->initialize || 
	     !thisint->statemachine->portEnabled))
    {
      debug_printf(DEBUG_STATE, "(global) -> LOGOFF\n");
      thisint->statemachine->lastState = thisint->statemachine->curState;
      thisint->statemachine->curState = LOGOFF;
      if (thisint->flags & IS_WIRELESS) cardif_reset_keys(thisint);
    }
  else if (thisint->statemachine->eapSuccess &&
	   !(thisint->statemachine->initialize || 
	     !thisint->statemachine->portEnabled) &&
	   !thisint->statemachine->userLogoff &&
	   !thisint->statemachine->logoffSent)
    {
      debug_printf(DEBUG_STATE, "(global) -> AUTHENTICATED\n");
      thisint->statemachine->lastState = thisint->statemachine->curState;
      thisint->statemachine->curState = AUTHENTICATED;
    }

  switch (thisint->statemachine->curState)
    {
    case DISCONNECTED:
      debug_printf(DEBUG_STATE, "Processing DISCONNECTED state.\n");
      thisint->statemachine->eapSuccess = FALSE;
      thisint->statemachine->eapFail = FALSE;
      thisint->statemachine->startCount = 0;
      thisint->statemachine->logoffSent = FALSE;
      thisint->statemachine->previousId = 256;
      thisint->statemachine->suppStatus = UNAUTHORIZED;
      thisint->statemachine->lastState = DISCONNECTED;

      // Automatically change to connected state.
      thisint->statemachine->curState = CONNECTING;
      debug_printf(DEBUG_STATE, "DISCONNECTED -> CONNECTING\n");
      break;

    case LOGOFF:
      if (((thisint->statemachine->userLogoff == TRUE) &&
	   (thisint->statemachine->logoffSent == FALSE)) &&
	  !((thisint->statemachine->initialize == TRUE) ||
	    (thisint->statemachine->portEnabled == FALSE)))
	{
	  debug_printf(DEBUG_STATE, "Processing LOGOFF state.\n");
	  thisint->snmp->dot1xSuppEapolLogoffFramesTx++;
	  txLogoff(outframe, outsize);
	  thisint->statemachine->logoffSent = TRUE;
	  thisint->statemachine->suppStatus = UNAUTHORIZED;
	  retVal = XDATA;    // We have some data to return.
	}
      if (thisint->statemachine->userLogoff != 1)
	{
	  // If we aren't stuck in logoff state, switch to disconnected.
	  thisint->statemachine->lastState = LOGOFF;
	  thisint->statemachine->curState = DISCONNECTED;
	  debug_printf(DEBUG_STATE, "LOGOFF -> DISCONNECTED\n");
	}
      thisint->statemachine->lastState = LOGOFF;
      break;

    case HELD:
      if ((thisint->statemachine->eapFail == TRUE) && 
	  !((thisint->statemachine->initialize == TRUE) || 
	    (thisint->statemachine->portEnabled == FALSE)) &&
	  (thisint->statemachine->userLogoff == FALSE) &&
	  (thisint->statemachine->logoffSent == FALSE))
	{
	  debug_printf(DEBUG_STATE, "Processing HELD state.\n");
	  thisint->statemachine->heldWhile = thisint->statemachine->heldPeriod;
	  thisint->statemachine->eapFail = FALSE;
	  thisint->statemachine->suppStatus = UNAUTHORIZED;
	}
      if (thisint->statemachine->heldWhile == 0)
	{
	  thisint->statemachine->lastState = HELD;
	  thisint->statemachine->curState = DISCONNECTED;
	  debug_printf(DEBUG_STATE, "HELD -> DISCONNECTED\n");
	}
      if (thisint->statemachine->reqId == TRUE)
	{
	  thisint->statemachine->lastState = HELD;
	  thisint->statemachine->curState = ACQUIRED;
	  debug_printf(DEBUG_STATE, "HELD -> ACQUIRED\n");
	}
      thisint->statemachine->lastState = HELD;
      break;

    case AUTHENTICATED:
      if ((thisint->statemachine->eapSuccess == TRUE) &&
	  !((thisint->statemachine->initialize == TRUE) ||
	    (thisint->statemachine->portEnabled == FALSE)))
	{
	  thisint->statemachine->eapSuccess = FALSE;
	  thisint->statemachine->eapFail = FALSE;
	  thisint->statemachine->suppStatus = AUTHORIZED;


	}
      if (thisint->statemachine->reqId == TRUE)
	{
	  thisint->statemachine->lastState = AUTHENTICATED;
	  thisint->statemachine->curState = ACQUIRED;
	  debug_printf(DEBUG_STATE, "AUTHENTICATED -> ACQUIRED\n");
	}
      thisint->statemachine->lastState = AUTHENTICATED;
      break;

    case ACQUIRED:
      if (thisint->statemachine->reqId)
	{
	  debug_printf(DEBUG_STATE, "Processing ACQUIRED state.\n");
	  debug_printf(DEBUG_NORMAL, "Connection established, authenticating...\n");
	  thisint->statemachine->authWhile = thisint->statemachine->authPeriod;
	  thisint->statemachine->startCount = 0;
	  thisint->statemachine->reqId = FALSE;
	  thisint->statemachine->reqAuth = FALSE;
	  txRspId(thisint, outframe, outsize);
	  thisint->statemachine->previousId = thisint->statemachine->receivedId;
	  retVal = XDATA;
	}
      if (thisint->statemachine->reqAuth == TRUE)
	{
	  thisint->statemachine->lastState = ACQUIRED;
	  thisint->statemachine->curState = AUTHENTICATING;
	  debug_printf(DEBUG_STATE, "ACQUIRED -> AUTHENTICATING)\n");
	  // Below is a hack.  We should find a better way to handle this!
	  retVal=statemachine_run(thisint, inframe, insize, outframe, outsize);
	}
      thisint->statemachine->lastState = ACQUIRED;
      break;

    case AUTHENTICATING:
      if (thisint->statemachine->reqAuth == TRUE)
	{
	  debug_printf(DEBUG_STATE, "Processing AUTHENTICATING state.\n");
	  thisint->statemachine->authWhile = thisint->statemachine->authPeriod;
	  thisint->statemachine->reqAuth = FALSE;
	  txRspAuth(thisint, inframe, insize, outframe, outsize);
	  
	  if (inframe != NULL)
	    thisint->statemachine->previousId = thisint->statemachine->receivedId;
	  if (*outsize != 0) 
	    {
	      retVal = XDATA;
	    }
	} else {
	  // Even though reqAuth != when we are in this state, we want to
	  // call txRspAuth in order to allow EAP types to request 
	  // interactive data.
	  txRspAuth(thisint, inframe, insize, outframe, outsize);
	  if (*outsize != 0)
	    {
	      retVal = XDATA;
	    }
	}
      if (thisint->statemachine->authWhile == 0)
	{
	  thisint->statemachine->lastState = AUTHENTICATING;
	  thisint->statemachine->curState = CONNECTING;
	  debug_printf(DEBUG_STATE, "AUTHENTICATING -> CONNECTING\n");
	}
      if (thisint->statemachine->reqId == TRUE)
	{
	  thisint->statemachine->lastState = AUTHENTICATING;
	  thisint->statemachine->curState = ACQUIRED;
	  debug_printf(DEBUG_STATE, "AUTHENTICATING -> ACQUIRED\n");
	}
      thisint->statemachine->lastState = AUTHENTICATING;
      break;

    case CONNECTING:
      if ((thisint->statemachine->startWhen==0) && 
	  (thisint->statemachine->startCount < thisint->statemachine->maxStart))
	{
	  debug_printf(DEBUG_STATE, "Processing CONNECTING state.\n");
	  thisint->statemachine->startWhen = thisint->statemachine->startPeriod;
	  thisint->statemachine->startCount++;
	  thisint->statemachine->reqId = FALSE;
	  thisint->snmp->dot1xSuppEapolStartFramesTx++;
	  txStart(outframe, outsize);
	  retVal = XDATA;
	}
      if (thisint->statemachine->reqId == TRUE)
	{
	  thisint->statemachine->lastState = CONNECTING;
	  thisint->statemachine->curState = ACQUIRED;
	  debug_printf(DEBUG_STATE, "CONNECTING -> ACQUIRED\n");
	}
      if ((thisint->statemachine->startWhen == 0) && 
	  (thisint->statemachine->startCount >= thisint->statemachine->maxStart))
	{
	  debug_printf(DEBUG_NORMAL, "Defaulting to AUTHENTICATED state!\n");

	  thisint->statemachine->lastState = CONNECTING;
	  thisint->statemachine->curState = AUTHENTICATED;
	  debug_printf(DEBUG_STATE, "CONNECTING -> AUTHENTICATED\n");
	}
      thisint->statemachine->lastState = CONNECTING;
      break;
    }

  return retVal;
}

/*****************************************
 *
 * Clean up our state machine.
 *
 *****************************************/
int statemachine_cleanup(struct interface_data *thisint)
{
  debug_printf(DEBUG_EVERYTHING, "Doing statemachine cleanup!\n");

  if (!thisint)
    {
      debug_printf(DEBUG_NORMAL, "Invalid data passed in to statemachine_cleanup()!\n");
      return XEMALLOC;
    }

  if (thisint->statemachine != NULL)
    {
      free(thisint->statemachine);
      thisint->statemachine = NULL;
    }
  
  return XENONE;
}

/*****************************************
 *
 * Create a logoff frame to be sent out to the network.
 *
 *****************************************/
int txLogoff(char *outframe, int *outsize)
{
  struct eapol_header *myframe;

  if ((!outframe) || (!outsize))
    {
      debug_printf(DEBUG_NORMAL, "Invalid data passed in to txLogoff()!\n");
      return XEMALLOC;
    }

  debug_printf(DEBUG_STATE, "Sending EAPOL-Logoff Frame.\n");

  myframe = (struct eapol_header *)&outframe[OFFSET_PAST_MAC];

  myframe->frame_type = htons(EAPOL_FRAME);
  myframe->eapol_version = MAX_EAPOL_VER;
  myframe->eapol_type = EAPOL_LOGOFF;
  myframe->eapol_length = 0;

  *outsize = sizeof(struct eapol_header)+OFFSET_PAST_MAC;
  return *outsize;
}

/********************************************
 *
 * Build the response ID frame to be sent out to the network.
 *
 ********************************************/
int txRspId(struct interface_data *thisint, char *outframe, int *outsize)
{
  struct eapol_header *myframe;
  int eapsize;

  debug_printf(DEBUG_STATE, "Sending EAPOL-Response-Identification\n");

  if ((!thisint) || (!outframe) || (!outsize) || (!thisint->userdata) ||
      (!thisint->statemachine))
    {
      debug_printf(DEBUG_NORMAL, "Invalid data passed in to txRspId()!\n");
      return XEMALLOC;
    }

  myframe = (struct eapol_header *)&outframe[OFFSET_PAST_MAC];

  myframe->frame_type = htons(EAPOL_FRAME);
  myframe->eapol_version = MAX_EAPOL_VER;
  myframe->eapol_type = EAP_PACKET;

  // See if we need to populate our ID.
  eap_prepopulate_id(thisint);

  // Now, build the response ID packet.
  eap_request_id(thisint->userdata->identity, 
		 thisint->statemachine->receivedId, 
		 (char *)&outframe[OFFSET_TO_EAP], &eapsize);

  myframe->eapol_length = htons(eapsize); 

  *outsize = (eapsize + OFFSET_TO_EAP);

  thisint->snmp->dot1xSuppEapolRespIdFramesTx++;

  return XDATA;
}

/*************************************************
 *
 * Build the authentication response frame, and return it to be sent out the
 * interface.
 *
 *************************************************/
int txRspAuth(struct interface_data *thisint, char *inframe, int insize,
	      char *outframe, int *outsize)
{
  struct eapol_header *myframe;
  int eapsize=0;
  char *tempframe;

  debug_printf(DEBUG_STATE, "Sending EAPOL-Response-Authentication\n");

  if ((!thisint) || (!outsize))
    {
      debug_printf(DEBUG_NORMAL, "Invalid data passed in to txRspAuth()!\n");
      return XEMALLOC;
    }

  if (outframe == NULL)
    {
      debug_printf(DEBUG_STATE, "Output appears to be NULL!\n");
      return XENOTHING_TO_DO;
    }

  myframe = (struct eapol_header *)&outframe[OFFSET_PAST_MAC];

  if (inframe == NULL)
    {
      tempframe = NULL;
    } else {
      tempframe = (char *)&inframe[OFFSET_TO_EAP];
    }

  if (eap_create_active_method(&thisint->userdata->activemethod,
			       thisint->userdata->identity,
			       thisint->tempPassword) != 0)
    {
      debug_printf(DEBUG_NORMAL, "Couldn't build active method!  Authentication will not happen!\n");
      return XEMALLOC;
    }

  if (eap_request_auth(thisint->userdata->activemethod, 
		       thisint->userdata->methods, tempframe, insize, 
		       (char *)&outframe[OFFSET_TO_EAP], &eapsize) == 2)
    {
      // We got a LEAP success, so we need to tell the state machine. And
      // create the keying material.
      thisint->statemachine->eapSuccess = 1;
      eapleap_get_keys(thisint);
    }

  if (eapsize != 0)
    {
      myframe->frame_type = htons(EAPOL_FRAME);
      myframe->eapol_version = MAX_EAPOL_VER;
      myframe->eapol_type = EAP_PACKET;
      myframe->eapol_length = htons(eapsize);

      //      *outsize = (payloadsize + OFFSET_PAST_MAC);
      *outsize = (eapsize + OFFSET_TO_EAP);
    } else {
      *outsize = 0;
    }

  thisint->snmp->dot1xSuppEapolRespFramesTx++;
  return *outsize;
}

/*********************************************
 *
 * Build an EAPoL Start frame to be sent out to the network.
 *
 *********************************************/
int txStart(char *outframe, int *outsize)
{
  struct eapol_header *myframe;

  if ((!outframe) || (!outsize))
    {
      debug_printf(DEBUG_NORMAL, "Invalid data passed in to txStart()!\n");
      return XEMALLOC;
    }

  debug_printf(DEBUG_STATE, "Sending EAPOL-Start Frame.\n");
  
  myframe = (struct eapol_header *)&outframe[OFFSET_PAST_MAC];

  myframe->frame_type = htons(EAPOL_FRAME);
  myframe->eapol_version = MAX_EAPOL_VER;
  myframe->eapol_type = EAPOL_START;
  myframe->eapol_length = 0;

  *outsize = sizeof(struct eapol_header)+OFFSET_PAST_MAC;
  return *outsize;
}
