/**HEADER********************************************************************
* 
* Copyright (c) 2008 Freescale Semiconductor;
* All Rights Reserved
*
* Copyright (c) 2004-2008 Embedded Access Inc.;
* All Rights Reserved
*
* Copyright (c) 1989-2008 ARC International;
* All Rights Reserved
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED 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 FREESCALE OR ITS 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.
*
**************************************************************************
*
* $FileName: chap.c$
* $Version : 3.0.3.0$
* $Date    : Nov-21-2008$
*
* Comments:
*
*   This file contains the implementation for the
*   Challenge Handshake Authentication Protocol.
*
*END************************************************************************/

#include <string.h>
#include <ppp.h>
#include "ppp_prv.h"


static void CHAP_timeout (pointer, PCB_PTR, boolean);
static void CHAP_up      (_ppp_handle);
static void CHAP_fail    (_ppp_handle);
static void CHAP_close   (pointer, PCB_PTR, boolean);


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  CHAP_init
* Returned Value  :  void
* Comments        :
*     Called by PPP_initialize.  Initializes the CHAP state.
*
*END*-----------------------------------------------------------------*/

void CHAP_init
   (
      _ppp_handle  handle
            /* [IN] - the PPP state structure */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = handle;

   ppp_ptr->CHAP_STATE.CLIENT_STATE = CHAP_STATE_CLOSED;
   ppp_ptr->CHAP_STATE.SERVER_STATE = CHAP_STATE_CLOSED;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  CHAP_timeout
* Returned Value  :  void
* Comments        :
*     Called by PPP_tx_task.  Timeout expired.
*
*END*-----------------------------------------------------------------*/

static void CHAP_timeout
   (
      pointer        chap_data,
            /* [IN/OUT] - CHAP state */
      PCB_PTR        pcb,
            /* [IN] - expired packet */
      boolean        hard
            /* [IN] - TRUE if this is a hard timeout (TO- event) */
   )
{ /* Body */
   CHAP_DATA_PTR  chap_ptr = chap_data;
   uint_16 i;

   /* Increment the identifier */
   chap_ptr->CURID = (chap_ptr->CURID + 1) & 0xFF;
   pcb->FRAG[0].FRAGMENT[3] = chap_ptr->CURID;

   /* Generate a new challenge */
   for (i=0; i<CHAP_CHALLENGE_LEN; i++) {
      pcb->FRAG[0].FRAGMENT[i+7]= chap_ptr->MD5[i] = RTCS_rand() & 0xFF;
   } /* Endfor */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  CHAP_open
* Returned Value  :  void
* Comments        :
*     Called by LCP_up.  Starts the CHAP client.
*
*END*-----------------------------------------------------------------*/

void CHAP_open
   (
      _ppp_handle    handle
            /* [IN] - the PPP state structure */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = handle;

   ppp_ptr->CHAP_STATE.CLIENT_STATE = CHAP_STATE_INITIAL;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  CHAP_challenge
* Returned Value  :  void
* Comments        :
*     Called by LCP_up.  Sends a Challenge packet.
*
*END*-----------------------------------------------------------------*/

void CHAP_challenge
   (
      _ppp_handle    handle
            /* [IN] - the PPP state structure */
   )
{ /* Body */
   PPP_CFG_PTR    ppp_ptr = handle;
   CHAP_DATA_PTR  chap_ptr = &ppp_ptr->CHAP_STATE;
   PCB_PTR        pcb;
   uchar_ptr      outp;
   uint_16        len;
   uint_16        i, idlen;

   chap_ptr->SERVER_STATE = CHAP_STATE_INITIAL;
   chap_ptr->CURID = RTCS_rand() & 0xFF;

   /* Acquire a message buffer */
   /* Start CR 2207 */
   idlen = strlen(PPP_SECRET(ppp_ptr,_PPP_CHAP_LNAME));
   /* End CR 2207 */
   len = CHAP_HDR_LEN + 1 + CHAP_CHALLENGE_LEN + idlen;
   pcb = PPP_pcballoc(PPP_PROT_CHAP, len);
   if (pcb == NULL) {
      return;
   } /* Endif */

   /* Build a Challenge packet */
   outp = pcb->FRAG[0].FRAGMENT + 2;
   *outp++ = CHAP_CODE_CHALLENGE;
   *outp++ = chap_ptr->CURID;
   *outp++ = (len >> 8) & 0xFF;
   *outp++ =  len       & 0xFF;
   *outp++ = CHAP_CHALLENGE_LEN;
   for (i=0; i<CHAP_CHALLENGE_LEN; i++) {
      *outp++ = chap_ptr->MD5[i] = RTCS_rand() & 0xFF;
   } /* Endfor */
   /* Start CR 2207 */
   PPP_memcopy(PPP_SECRET(ppp_ptr,_PPP_CHAP_LNAME), outp, idlen);
   /* End CR 2207 */
   /* Send the PCB */
   PPP_send_rexmit(ppp_ptr, PPP_PROT_CHAP, pcb, 0, CHAP_timeout, chap_ptr);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  CHAP_input
* Returned Value  :  void
* Comments        :
*     Called by PPP_rx_task.  Parses a CHAP packet.
* Side effects    :
*     Consumes (sends or frees) the packet.
*
*END*-----------------------------------------------------------------*/

void CHAP_input
   (
      PCB_PTR        pcb,
            /* [IN] - CHAP packet */
      _ppp_handle    handle
            /* [IN] - the PPP state structure */
   )
{ /* Body */
   PPP_CFG_PTR    ppp_ptr = handle;
   CHAP_DATA_PTR  chap_ptr = &ppp_ptr->CHAP_STATE;
   PPP_SECRET_PTR secret;
   uchar_ptr      inp = pcb->FRAG[0].FRAGMENT + 2;
   uchar_ptr      valp, idp;
   uchar          code, id;
   uchar          vallen, idlen, namelen;
   uint_16        len;
   boolean        delay = FALSE;

   /*
   ** Parse header (code, id and length).
   ** If packet too short, drop it.
   */
   if (pcb->FRAG[0].LENGTH < 2 + CHAP_HDR_LEN) {
      chap_ptr->ST_CHAP_SHORT++;
      PCB_free(pcb);
      return;
   } /* Endif */

   code = *inp++;
   id = *inp++;
   len = *inp++ << 8;
   len += *inp++;

   if ((len < CHAP_HDR_LEN) || (len > pcb->FRAG[0].LENGTH - 2)) {
      chap_ptr->ST_CHAP_SHORT++;
      PCB_free(pcb);
      return;
   } /* Endif */
   len -= CHAP_HDR_LEN;

   /*
   ** For Challenge and Response packets, also parse the
   ** value and name fields.
   */
   if ((code == CHAP_CODE_CHALLENGE) || (code == CHAP_CODE_RESPONSE)) {

      if (len < 1) {
         chap_ptr->ST_CHAP_SHORT++;
         PCB_free(pcb);
         return;
      } /* Endif */
      valp = inp;
      vallen = *valp++;
      len--;

      if (len < vallen) {
         chap_ptr->ST_CHAP_SHORT++;
         PCB_free(pcb);
         return;
      } /* Endif */
      idp = valp + vallen;
      idlen = len - vallen;

   } /* Endif */

   switch (code) {

   case CHAP_CODE_CHALLENGE:

      if (chap_ptr->CLIENT_STATE != CHAP_STATE_INITIAL) {
         chap_ptr->ST_CHAP_NOAUTH++;
         PCB_free(pcb);
         break;
      } /* Endif */

      /* OK -- Generate a Response packet */
      /* Start CR 2207 */
      secret = PPP_SECRET(ppp_ptr,_PPP_CHAP_LSECRETS);
      /* End CR 2207 */
      inp = pcb->FRAG[0].FRAGMENT + 2;
      *inp++ = CHAP_CODE_RESPONSE;
      inp++;                           /* Keep same ID */
      /* Start CR 2207 */
      namelen = strlen(PPP_SECRET(ppp_ptr,_PPP_CHAP_LNAME));
      /* End CR 2207 */
      len = CHAP_HDR_LEN + 1 + 16 + namelen;
      *inp++ = (len >> 8) & 0xFF;
      *inp++ =  len       & 0xFF;
      *inp++ = 16;
      for (;;) {
         if ((secret->PPP_ID_LENGTH == 0)
          && (secret->PPP_PW_LENGTH == 0)) {
            /* Couldn't find a secret for this peer */
            chap_ptr->ST_CHAP_NOPW++;
            PPP_MD5(inp, (uint_32)1, &id,       /* id */
                         (uint_32)vallen, valp, /* challenge */
                         (uint_32)0);
            break;
         } /* Endif */
         if ((secret->PPP_ID_LENGTH == idlen)
          && (memcmp(secret->PPP_ID_PTR, idp, idlen) == 0)) {
            /* Found a secret; send response */
            PPP_MD5(inp, (uint_32)1, &id,                /* id */
                         (uint_32)secret->PPP_PW_LENGTH, /* secret */
                         secret->PPP_PW_PTR,
                         (uint_32)vallen, valp,          /* challenge */
                         (uint_32)0);
            break;
         } /* Endif */
         secret++;
      } /* Endfor */
      inp += 16;
      /* Start CR 2207 */
      PPP_memcopy(PPP_SECRET(ppp_ptr,_PPP_CHAP_LNAME), inp, namelen);
      /* End CR 2207 */
      pcb->FRAG[0].LENGTH = len + 2;
      PPP_send_one(ppp_ptr, PPP_PROT_CHAP, pcb);
      break;

   case CHAP_CODE_RESPONSE:

      switch (chap_ptr->SERVER_STATE) {
      case CHAP_STATE_INITIAL:

         /* Check the ID against our last challenge */
         if (id != chap_ptr->CURID) {
            chap_ptr->ST_CHAP_ID++;
            PCB_free(pcb);
            break;
         } /* Endif */

         /* Stop the challenge, whether or not the response is correct */
         PPP_send_stop(ppp_ptr, PPP_PROT_CHAP);

         chap_ptr->SERVER_STATE = CHAP_STATE_FAILURE;

         if (vallen != 16) {
            delay = TRUE;
            CHAP_fail(handle);

         } else {
            /* Search for name in the secrets table */
            /* Start CR 2207 */
            secret = PPP_SECRET(ppp_ptr,_PPP_CHAP_RSECRETS);
            /* End CR 2207 */
            for (;;) {
               if ((secret->PPP_ID_LENGTH == 0)
                && (secret->PPP_PW_LENGTH == 0)) {
                     /* Couldn't find a secret for this peer */
                  delay = TRUE;
                  CHAP_fail(handle);
                  break;
               } /* Endif */
               if ((secret->PPP_ID_LENGTH == idlen)
                && (memcmp(secret->PPP_ID_PTR, idp, idlen) == 0)) {
                     /* Found a secret; compute hash */
                  uchar digest[16];

                  PPP_MD5(digest, (uint_32)1, &id,                /* id */
                                  (uint_32)secret->PPP_PW_LENGTH, /* secret */
                                  secret->PPP_PW_PTR,
                                  (uint_32)CHAP_CHALLENGE_LEN,    /* challenge */
                                  chap_ptr->MD5,
                                  (uint_32)0);

                  if (memcmp(digest, valp, vallen) == 0) {
                     chap_ptr->SERVER_STATE = CHAP_STATE_SUCCESS;
                     CHAP_up(handle);
                  } else {
                     delay = TRUE;
                     CHAP_fail(handle);
                  } /* Endif */
                  break;
               } /* Endif */
               secret++;
            } /* Endfor */
         } /* Endif */

         /* fall through */
      case CHAP_STATE_SUCCESS:
      case CHAP_STATE_FAILURE:
         /* Build a Success or Failure reply */
         inp = pcb->FRAG[0].FRAGMENT + 2;
         *inp++ = chap_ptr->SERVER_STATE;
         inp++;                              /* Keep same ID */
         *inp++ = 0;
         *inp++ = 4;
         pcb->FRAG[0].LENGTH = 6;
         if (delay) {
            PPP_send_rexmit(ppp_ptr, PPP_PROT_CHAP, pcb, 1, CHAP_close, handle);
         } else {
            PPP_send_one(ppp_ptr, PPP_PROT_CHAP, pcb);
         } /* Endif */
         break;

      default:
         chap_ptr->ST_CHAP_NOCHAL++;
         PCB_free(pcb);
         break;
      } /* Endswitch */
      break;

   case CHAP_CODE_SUCCESS:
   case CHAP_CODE_FAILURE:
      if (chap_ptr->CLIENT_STATE != CHAP_STATE_INITIAL) {
         chap_ptr->ST_CHAP_NOAUTH++;
      } /* Endif */
      PCB_free(pcb);
      break;

   default:
      chap_ptr->ST_CHAP_CODE++;
      PCB_free(pcb);
      break;
   } /* Endswitch */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  CHAP_up
* Returned Value  :  void
* Comments        :
*     Called by CHAP_input once the peer has been authenticated.
*
*END*-----------------------------------------------------------------*/

static void CHAP_up
   (
      _ppp_handle   handle
            /* [IN] - the PPP state structure */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = handle;

   PPP_mutex_lock(&ppp_ptr->MUTEX);
   ppp_ptr->LINK_AUTH = TRUE;
   PPP_mutex_unlock(&ppp_ptr->MUTEX);

   PPP_up(handle);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  CHAP_fail
* Returned Value  :  void
* Comments        :
*     Called by CHAP_Input if the peer's credentials are rejected.
*
*END*-----------------------------------------------------------------*/

static void CHAP_fail
   (
      _ppp_handle    handle
            /* [IN] - the PPP state structure */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = handle;
   PPP_CALL_INTERNAL_PTR call_ptr = &ppp_ptr->LCP_CALL[PPP_CALL_AUTH_FAIL];

   if (call_ptr->CALLBACK) {
      call_ptr->CALLBACK(call_ptr->PARAM);
   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  CHAP_close
* Returned Value  :  void
* Comments        :
*     Called by the Tx task.  Terminates the link after a
*     Failure packet is sent.
*
*END*-----------------------------------------------------------------*/

static void CHAP_close
   (
      pointer        handle,
            /* [IN] - the PPP state structure */
      PCB_PTR        pcb,
            /* [IN] - expired packet */
      boolean        hard
            /* [IN] - TRUE if this is a hard timeout (TO- event) */
   )
{ /* Body */

   PPP_close(handle);

} /* Endbody */


/* EOF */
