/**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: lcp.c$
* $Version : 3.5.6.0$
* $Date    : Dec-2-2009$
*
* Comments:
*
*   This file contains the implementation for the
*   Link Control Protocol.
*
*END************************************************************************/

/*
** lcp.c - PPP Link Control Protocol.
**
** Copyright (c) 1989 Carnegie Mellon University.
** All rights reserved.
**
** 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 Carnegie Mellon University.  The name of the
** University 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.
*/

/*
** Things to do:
**
** We need a way to let the application specify which configuration
** options to request (in order of preference), and which options to
** allow the peer (unordered set).
**
** Currently, the following are hard coded for both sides:
**
**       MRU <= 1500            (1500 preferred, Receive side)
**       MRU >= 68              (Send side)
**       ACCM = _PPP_ACCM
**       PFC = TRUE or FALSE    (TRUE preferred)
**       ACFC = TRUE or FALSE   (TRUE preferred)
**       FCS = CCITT-32, CCITT-16 or NULL
**                              (CCITT-16 preferred)
**
** The choice of authentication protocols depends on the
** existence of secrets.  The following are supported
**
**       AP = none or PAP
**
*/

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


static boolean LCP_up(PPPFSM_CFG_PTR);
static void    LCP_down(PPPFSM_CFG_PTR);

static void    LCP_resetconfreq(PPPFSM_CFG_PTR);
static uint_32 LCP_buildconfreq(PPPFSM_CFG_PTR, uchar_ptr, uint_32);

static uint_32 LCP_recvconfreq(PPPFSM_CFG_PTR, boolean);
static boolean LCP_recvconfack(PPPFSM_CFG_PTR);
static boolean LCP_recvconfnak(PPPFSM_CFG_PTR);
static boolean LCP_recvconfrej(PPPFSM_CFG_PTR);
static boolean LCP_recvcoderej(PPPFSM_CFG_PTR);
static boolean LCP_recvextcode(PPPFSM_CFG_PTR);

static PPPFSM_CALL LCP_CALL = {
   /* PROTOCOL    */    PPP_PROT_LCP,
   /* linkup()    */    LCP_up,
   /* linkdown()  */    LCP_down,
   /* resetreq()  */    LCP_resetconfreq,
   /* buildreq()  */    LCP_buildconfreq,
   /* recvreq()   */    LCP_recvconfreq,
   /* recvack()   */    LCP_recvconfack,
   /* recvnak()   */    LCP_recvconfnak,
   /* recvrej()   */    LCP_recvconfrej,
   /* testcode()  */    LCP_recvcoderej,
   /* recvcode()  */    LCP_recvextcode
};

static LCP_NEG LCP_DEFAULT_NEG = {
   /* NEG_* */    0,1,0,0,0,1,1,0,
   /* MRU   */    DEFAULT_MRU,
   /* ACCM  */    0xFFFFFFFFl,
   /* AP    */    0
};

static LCP_NEG LCP_DEFAULT_NEG_PPPOE = {
   /* NEG_* */    1,0,0,0,0,0,0,0,
   /* MRU   */    1492,
   /* ACCM  */    0,
   /* AP    */    0
};


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_init
* Returned Value  :  error code
* Comments        :
*     Called by PPP_init.  Initializes the LCP Finite State Machine.
*
*END*-----------------------------------------------------------------*/

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

   ppp_ptr->LCP_STATE.DEVICE = ppp_ptr->DEVICE;
/* Start CR 2207 */
#if PPP_SECRETS_NOT_SHARED
   ppp_ptr->LCP_STATE.HANDLE = handle;
#endif
/* End CR 2207 */
      /* Initialize the FSM */
   return PPPFSM_init(&ppp_ptr->LCP_FSM, handle, &LCP_CALL, &ppp_ptr->LCP_STATE);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_destroy
* Returned Value  :  error code
* Comments        :
*     Called by PPP_init.  Destroys the LCP Finite State Machine.
*     Assumes the Finite State Machine has been initialized.
*
*END*-----------------------------------------------------------------*/

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

      /* Destroy the FSM */
   return PPPFSM_destroy(&ppp_ptr->LCP_FSM);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPP_lowerup
* Returned Value  :  void
* Comments        :
*     Called by application.  Lower layer is up.
*
*END*-----------------------------------------------------------------*/

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

   PPPFSM_lowerup(&ppp_ptr->LCP_FSM);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPP_lowerdown
* Returned Value  :  void
* Comments        :
*     Called by application.  Lower layer is down.
*
*END*-----------------------------------------------------------------*/

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

   PPPFSM_lowerdown(&ppp_ptr->LCP_FSM);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPP_open
* Returned Value  :  error code
* Comments        :
*     Called by application.  Begin negotiating PPP.
*
*END*-----------------------------------------------------------------*/

uint_32 PPP_open
   (
      _ppp_handle    handle,
            /* [IN] - the PPP state structure */
      uint_32        options
            /* [IN] - Startup options */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = handle;

      /* Do some error checking */
   if (ppp_ptr->VALID != PPP_VALID) {
      return RTCSERR_PPP_INVALID_HANDLE;
   } /* Endif */

   PPPFSM_open(&ppp_ptr->LCP_FSM, options);
   return PPP_OK;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPP_close
* Returned Value  :  error code
* Comments        :
*     Called by application.  Begin closing PPP.
*
*END*-----------------------------------------------------------------*/

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

      /* Do some error checking */
   if (ppp_ptr->VALID != PPP_VALID) {
      return RTCSERR_PPP_INVALID_HANDLE;
   } /* Endif */

   PPPFSM_close(&ppp_ptr->LCP_FSM);
   return PPP_OK;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_recvextcode
* Returned Value  :  TRUE or FALSE
* Comments        :
*     Called by PPPFSM_Input.  Parses an LCP packet with an extended
*     code.  Consumes fsm->PACKET if TRUE is returned.
*
*END*-----------------------------------------------------------------*/

static boolean LCP_recvextcode
   (
      PPPFSM_CFG_PTR    fsm
            /* [IN] - State Machine */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = fsm->HANDLE;
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   PPP_CALL_INTERNAL_PTR call_ptr = &ppp_ptr->LCP_CALL[PPP_CALL_ECHO_REPLY];

   switch (fsm->CODE) {
   case LCP_CODE_PROT_REJ:
      lcp_ptr->ST_LCP_RX_REJECT++;

      /*
      ** We should notify the rejected protocol, but there
      ** currently isn't any way to do this.  What we'll do
      ** instead is to send a STOP message to the Tx task on
      ** behalf of the protocol.
      **
      ** This will usually be sufficient, since the rejected
      ** protocol isn't likely to send another packet until
      ** either it receives a packet (in which case it's OK to
      ** reply) or the retransmission timer kicks in (which
      ** we're about to disable).
      **
      ** Thus, this will effectively stop the rejected protocol.
      */

      if (fsm->LENGTH >= 2) {
         uint_16 protocol = ntohs(fsm->DATA);
         PPP_send_stop(ppp_ptr, protocol);
      } /* Endif */
      PCB_free(fsm->PACKET);
      break;

   case LCP_CODE_ECHO_REQ:
      lcp_ptr->ST_LCP_RX_ECHO++;
      if (fsm->STATE < PPP_STATE_OPENED) {
         PCB_free(fsm->PACKET);
      } else {
         htonc(fsm->DATA - CP_HDR_LEN, LCP_CODE_ECHO_REP);
         htonl(fsm->DATA, 0);                      /* Set magic field to 0 */
         lcp_ptr->ST_LCP_TX_REPLY++;
         PPP_send_one(ppp_ptr, PPP_PROT_LCP, fsm->PACKET);
      } /* Endif */
      break;

   case LCP_CODE_ECHO_REP:
      lcp_ptr->ST_LCP_RX_REPLY++;
      if (call_ptr->CALLBACK) {
         call_ptr->CALLBACK(call_ptr->PARAM, fsm->ID, fsm->PACKET);
      } /* Endif */
      PCB_free(fsm->PACKET);
      break;

   case LCP_CODE_DISC_REQ:
      lcp_ptr->ST_LCP_RX_DISCARD++;
      PCB_free(fsm->PACKET);
      break;

   default:
      return FALSE;
   } /* Endswitch */

   return TRUE;
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_sendprotrej
* Returned Value  :  void
* Comments        :
*     Called by PPP_rx_task.  Sends a ProtRej packet for some protocol.
*     Consumes (sends) 'pcb'.
*
*END*-----------------------------------------------------------------*/

void LCP_sendprotrej
   (
      PCB_PTR           pcb,
            /* [IN] - packet to reject */
      PPPFSM_CFG_PTR    fsm
            /* [IN/OUT] - State Machine */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = fsm->HANDLE;
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   uchar_ptr   packet, src, dst;
   uint_16     length;
   uint_16     size = ppp_ptr->SEND_OPTIONS->MRU - CP_HDR_LEN;

   length = pcb->FRAG[0].LENGTH;
   packet = pcb->FRAG[0].FRAGMENT;

   /* Truncate the packet if necessary */
   if (length > size) {
      length = size;
   } /* Endif */

   /* Make room for a Code-Reject header */
   size = length + CP_HDR_LEN;

   /* Copy the received packet (including protocol field) to the */
   /* data portion of the Protocol-Reject                        */
   src = packet + length;
   dst = src + 2 + CP_HDR_LEN;
   while (length--) *--dst = *--src;

   /* Build a Code-Reject packet */
   htons(packet, PPP_PROT_LCP); packet += 2;
   *packet++ = LCP_CODE_PROT_REJ;
   *packet++ = fsm->CURID;
   fsm->CURID = (fsm->CURID + 1) & 0xFF;
   htons(packet, size); packet += 2;
   pcb->FRAG[0].LENGTH = size + 2;

   lcp_ptr->ST_LCP_TX_REJECT++;
   PPP_send_one(ppp_ptr, PPP_PROT_LCP, pcb);

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_resetconfreq
* Returned Value  :  void
* Comments        :
*     Called by PPPFSM_Sendconfreq when starting negotiation.
*     Initializes the Send Options.
*
*END*-----------------------------------------------------------------*/

static void LCP_resetconfreq
   (
      PPPFSM_CFG_PTR    fsm
            /* [IN] - State Machine */
   )
{ /* Body */
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   uint_32     if_type;
/* Start CR 2207 */
#if PPP_SECRETS_NOT_SHARED
   PPP_CFG_PTR    ppp_ptr = lcp_ptr->HANDLE;
#endif
/* End CR 2207 */

   _iopcb_ioctl(lcp_ptr->DEVICE, IOPCB_IOCTL_GET_IFTYPE, &if_type);
   if (if_type == IPIFTYPE_ETHERNET) {
      lcp_ptr->SEND_OPT = PPP_DEFAULT_OPTIONS_PPPOE;
      lcp_ptr->RECV_OPT = PPP_DEFAULT_OPTIONS_PPPOE;
      lcp_ptr->RECV_NEG = LCP_DEFAULT_NEG_PPPOE;
   } else {
      lcp_ptr->SEND_OPT = PPP_DEFAULT_OPTIONS;
      lcp_ptr->RECV_OPT = PPP_DEFAULT_OPTIONS;
      lcp_ptr->RECV_NEG = LCP_DEFAULT_NEG;
      lcp_ptr->RECV_NEG.ACCM = _PPP_ACCM;
   } /* Endif */

/* Start CR 2207 */
   if (PPP_SECRET(ppp_ptr,_PPP_CHAP_RSECRETS)) {
/* End CR 2207 */
      lcp_ptr->RECV_NEG.NEG_AP = 1;
      lcp_ptr->RECV_NEG.AP = PPP_PROT_CHAP;
/* Start CR 2207 */
   } else if (PPP_SECRET(ppp_ptr,_PPP_PAP_RSECRETS)) {
/* End CR 2207 */
      lcp_ptr->RECV_NEG.NEG_AP = 1;
      lcp_ptr->RECV_NEG.AP = PPP_PROT_PAP;
   } else {
      lcp_ptr->RECV_NEG.NEG_AP = 0;
   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_buildconfreq
* Returned Value  :  number of bytes written
* Comments        :
*     Called by PPPFSM_Sendconfreq.  Builds a ConfReq packet.
*
*END*-----------------------------------------------------------------*/

static uint_32 LCP_buildconfreq
   (
      PPPFSM_CFG_PTR    fsm,
            /* [IN] - State Machine */
      uchar_ptr         outp,
            /* [IN] - free packet */
      uint_32           sizeleft
            /* [IN] - size of packet */
   )
{ /* Body */
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   uint_32     totlen = 0;
   uint_32     aplen;

#define LCP_BREQ(ci,len) \
         *outp++ = LCP_CI_ ## ci; \
         *outp++ = len;           \
         totlen += len

   /*
   ** Generate configuration information for each option
   ** we want to negotiate.
   */

   if (lcp_ptr->RECV_NEG.NEG_MRU) {
      if (sizeleft >= totlen+4) {
         LCP_BREQ(MRU,4);
         htons(outp, lcp_ptr->RECV_NEG.MRU); outp += 2;
      } /* Endif */
   } /* Endif */

   if (lcp_ptr->RECV_NEG.NEG_ACCM) {
      if (sizeleft >= totlen+6) {
         LCP_BREQ(ACCM,6);
         htonl(outp, lcp_ptr->RECV_NEG.ACCM); outp += 4;
      } /* Endif */
   } /* Endif */

   if (lcp_ptr->RECV_NEG.NEG_AP) {
      switch (lcp_ptr->RECV_NEG.AP) {
      case PPP_PROT_CHAP: aplen = 5; break;
      default:            aplen = 4; break;
      } /* Endswitch */
      if (sizeleft >= totlen+aplen) {
         LCP_BREQ(AP,aplen);
         htons(outp, lcp_ptr->RECV_NEG.AP); outp += 2;
         switch (lcp_ptr->RECV_NEG.AP) {
         case PPP_PROT_CHAP:
            htonc(outp, 5); outp++;          /* Only MD5 supported */
            break;
         } /* Endswitch */
      } /* Endif */
   } /* Endif */

   if (lcp_ptr->RECV_NEG.NEG_PFC) {
      if (sizeleft >= totlen+2) {
         LCP_BREQ(PFC,2);
      } /* Endif */
   } /* Endif */

   if (lcp_ptr->RECV_NEG.NEG_ACFC) {
      if (sizeleft >= totlen+2) {
         LCP_BREQ(ACFC,2);
      } /* Endif */
   } /* Endif */

   return totlen;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_recvconfack
* Returned Value  :  TRUE or FALSE
* Comments        :
*     Called by PPPFSM_Recvconfack.  Parses a ConfAck packet.
*     This function should not modify any state if the ack is bad.
*
*END*-----------------------------------------------------------------*/

static boolean LCP_recvconfack
   (
      PPPFSM_CFG_PTR    fsm
            /* [IN] - State Machine */
   )
{ /* Body */
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   uchar_ptr   inp = fsm->DATA;
   uint_32     sizeleft = fsm->LENGTH;
   uint_32     aplen;
   PPP_OPT     ack_opt = lcp_ptr->RECV_OPT;

#define LCP_RACK(ci,len) \
      if (sizeleft < len) goto badack;          \
      if (*inp++ != LCP_CI_ ## ci) goto badack; \
      if (*inp++ != len)           goto badack; \
      sizeleft -= len

   /*
   ** The ack must be identical to the last ConfReq we sent
   */

   if (lcp_ptr->RECV_NEG.NEG_MRU) {
      LCP_RACK(MRU,4);
      ack_opt.MRU = ntohs(inp); inp += 2;
      if (ack_opt.MRU != lcp_ptr->RECV_NEG.MRU) goto badack;
   } /* Endif */

   if (lcp_ptr->RECV_NEG.NEG_ACCM) {
      LCP_RACK(ACCM,6);
      ack_opt.ACCM = ntohl(inp); inp += 4;
      if (ack_opt.ACCM != lcp_ptr->RECV_NEG.ACCM) goto badack;
   } /* Endif */

   if (lcp_ptr->RECV_NEG.NEG_AP) {
      switch (lcp_ptr->RECV_NEG.AP) {
      case PPP_PROT_CHAP: aplen = 5; break;
      default:            aplen = 4; break;
      } /* Endswitch */
      LCP_RACK(AP,aplen);
      ack_opt.AP = ntohs(inp); inp += 2;
      if (ack_opt.AP != lcp_ptr->RECV_NEG.AP) goto badack;
      switch (ack_opt.AP) {
      case PPP_PROT_CHAP:
         if (ntohc(inp) != 5) goto badack;   /* Only MD5 supported */
         inp++;
         ack_opt.AP_Start = CHAP_challenge;
         break;
      case PPP_PROT_PAP:
         ack_opt.AP_Start = PAP_open;
         break;
      default:
         ack_opt.AP = 0;
         break;
      } /* Endswitch */
   } /* Endif */

   if (lcp_ptr->RECV_NEG.NEG_PFC) {
      LCP_RACK(PFC,2);
      ack_opt.PFC = TRUE;
   } /* Endif */

   if (lcp_ptr->RECV_NEG.NEG_ACFC) {
      LCP_RACK(ACFC,2);
      ack_opt.ACFC = TRUE;
   } /* Endif */

   if (sizeleft) goto badack;

   if (fsm->STATE < PPP_STATE_OPENED) {
      lcp_ptr->RECV_OPT = ack_opt;
   } /* Endif */
   return TRUE;

badack:
   return FALSE;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_recvconfnak
* Returned Value  :  TRUE or FALSE
* Comments        :
*     Called by PPPFSM_recvconfnak.  Parses a ConfNak packet.
*     This function should not modify any state if the nak is bad.
*
*END*-----------------------------------------------------------------*/

static boolean LCP_recvconfnak
   (
      PPPFSM_CFG_PTR    fsm
            /* [IN] - State Machine */
   )
{ /* Body */
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   uchar_ptr   inp = fsm->DATA;
   uint_32     sizeleft = fsm->LENGTH;
   LCP_NEG     req_neg = lcp_ptr->RECV_NEG;
   uchar       code;
/* Start CR 2207 */
#if PPP_SECRETS_NOT_SHARED
   PPP_CFG_PTR    ppp_ptr = lcp_ptr->HANDLE;
#endif
/* End CR 2207 */

#define LCP_RNAK(len) \
      if (sizeleft < len) goto badnak; \
      if (*inp++ != len)  goto badnak; \
      sizeleft -= len

   /*
   ** Nak'd codes must be in the same order as they were in the ConfReq
   */

   code = *inp++;

   if (sizeleft && req_neg.NEG_MRU && code == LCP_CI_MRU) {
      LCP_RNAK(4);

         /* We are prepared to accept maximum MRU of 1500 */
      req_neg.MRU = ntohs(inp); inp += 2;
      if (req_neg.MRU > DEFAULT_MRU) {
         req_neg.MRU = DEFAULT_MRU;
      } /* Endif */

      code = *inp++;
   } /* Endif */

   if (sizeleft && req_neg.NEG_ACCM && code == LCP_CI_ACCM) {
      LCP_RNAK(6);

         /* Add any characters they want to our ACCM */
      req_neg.ACCM |= ntohl(inp); inp += 4;

      code = *inp++;
   } /* Endif */

   if (sizeleft && req_neg.NEG_AP && code == LCP_CI_AP) {
      if (sizeleft < *inp || *inp < 4) goto badnak;
      sizeleft -= *inp;

         /*
         ** If AP is nak'd, we don't care what they want --
         ** we choose the next most desirable protocol.  If there
         ** aren't any more to choose, we keep the last one we tried.
         */
      inp += *inp - 1;
      
      if (PPP_SECRET(ppp_ptr,_PPP_CHAP_RSECRETS)) {
          req_neg.AP = PPP_PROT_CHAP;
      } else {
        switch (req_neg.AP) {
        case PPP_PROT_CHAP:
        	 // CHAP was NAKed, try PAP
             if (PPP_SECRET(ppp_ptr,_PPP_PAP_RSECRETS)) {
              	req_neg.AP = PPP_PROT_PAP;            
             } /* Endif */
             break;
        
        case PPP_PROT_PAP:
           /* no other authentication protocols known */
           break;
           
        default:
            // Unknown protocol NAKed, try CHAP
         	if (PPP_SECRET(ppp_ptr,_PPP_CHAP_RSECRETS)) {
            	req_neg.AP = PPP_PROT_CHAP;
         	}
            break;

        } /* Endswitch */
      }
      code = *inp++;
   } /* Endif */

   /*
   ** If we advertised PFC and/or ACFC and the peer doesn't
   ** want it, they should send a ConfRej rather than a ConfNak,
   ** so we're not going to check for them here (if they are
   ** here, the switch statement below will catch it).
   */


#define LCP_RNAK_ADD(ci,len) \
         if (req_neg.NEG_ ## ci || sizeleft < len || *inp++ != len) goto badnak; \
         sizeleft -= len;                                                        \
         req_neg.NEG_ ## ci = 1

   /*
   ** There may be remaining codes if the peer wants us to
   ** negotiate an option we didn't include.
   */

   while (sizeleft) {
      switch (code) {
      case LCP_CI_MRU:
         LCP_RNAK_ADD(MRU,4);
         req_neg.MRU = ntohs(inp); inp += 2;
         if (req_neg.MRU > DEFAULT_MRU) {
            req_neg.MRU = DEFAULT_MRU;
         } /* Endif */
         break;
      case LCP_CI_ACCM:
         LCP_RNAK_ADD(ACCM,6);
         req_neg.ACCM |= ntohl(inp); inp += 4;
         break;
      case LCP_CI_AP:
         if (req_neg.NEG_AP || sizeleft < *inp || *inp < 4) goto badnak;
         sizeleft -= *inp;
            /*
            ** If AP is nak'd, we don't care what they want --
            ** we still don't negotiate
            */
         inp += *inp - 1;
         break;
      case LCP_CI_PFC:
         LCP_RNAK_ADD(PFC,2);
         break;
      case LCP_CI_ACFC:
         LCP_RNAK_ADD(ACFC,2);
         break;
      default:
         if (sizeleft < *inp || *inp < 2) goto badnak;
         sizeleft -= *inp;
         inp += *inp - 1;
      } /* Endswtich */
      code = *inp++;
   } /* Endwhile */

   if (fsm->STATE < PPP_STATE_OPENED) {
      lcp_ptr->RECV_NEG = req_neg;
   } /* Endif */
   return TRUE;

badnak:
   return FALSE;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_recvconfrej
* Returned Value  :  TRUE or FALSE
* Comments        :
*     Called by PPPFSM_recvconfnak.  Parses a ConfRej packet.
*     This function should not modify any state if the rej is bad.
*
*END*-----------------------------------------------------------------*/

static boolean LCP_recvconfrej
   (
      PPPFSM_CFG_PTR    fsm
            /* [IN] - State Machine */
   )
{ /* Body */
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   uchar_ptr   inp = fsm->DATA;
   uint_32     sizeleft = fsm->LENGTH;
   uint_32     aplen;
   PPP_OPT     req_opt = lcp_ptr->RECV_OPT;
   LCP_NEG     req_neg = lcp_ptr->RECV_NEG;
   uchar       code;

#define LCP_RREJ(ci,len) \
      if (sizeleft < len) goto badrej; \
      if (*inp++ != len)  goto badrej; \
      sizeleft -= len;                 \
      req_neg.NEG_ ## ci = 0;          \
      req_opt.ci = PPP_DEFAULT_OPTIONS.ci

   /*
   ** Rej'd codes must be in the same order as they were in the ConfReq
   */

   code = *inp++;

   if (sizeleft && req_neg.NEG_MRU && (code == LCP_CI_MRU)) {
      LCP_RREJ(MRU,4);
      if (req_neg.MRU != ntohs(inp)) goto badrej; inp += 2;
      code = *inp++;
   } /* Endif */

   if (sizeleft && req_neg.NEG_ACCM && (code == LCP_CI_ACCM)) {
      LCP_RREJ(ACCM,6);
      if (req_neg.ACCM != ntohl(inp)) goto badrej; inp += 4;
      code = *inp++;
   } /* Endif */

   if (sizeleft && req_neg.NEG_AP && (code == LCP_CI_AP)) {
      switch (req_neg.AP) {
      case PPP_PROT_CHAP: aplen = 5; break;
      default:            aplen = 4; break;
      } /* Endswitch */
      LCP_RREJ(AP,aplen);
      if (req_neg.AP != ntohs(inp)) goto badrej; inp += 2;
      switch (req_neg.AP) {
      case PPP_PROT_CHAP:
         if (5 != ntohc(inp)) goto badrej;   /* Only MD5 supported */
         inp++;
         break;
      } /* Endswitch */
      req_opt.AP_Start = PPP_DEFAULT_OPTIONS.AP_Start;
      code = *inp++;
   } /* Endif */

   if (sizeleft && req_neg.NEG_PFC && (code == LCP_CI_PFC)) {
      LCP_RREJ(PFC,2);
      code = *inp++;
   } /* Endif */

   if (sizeleft && req_neg.NEG_ACFC && (code == LCP_CI_ACFC)) {
      LCP_RREJ(ACFC,2);
      code = *inp++;
   } /* Endif */

   if (sizeleft) goto badrej;

   if (fsm->STATE < PPP_STATE_OPENED) {
      lcp_ptr->RECV_OPT = req_opt;
      lcp_ptr->RECV_NEG = req_neg;
   } /* Endif */
   return TRUE;

badrej:
   return FALSE;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_recvconfreq
* Returned Value  :  number of bytes written
* Comments        :
*     Called by PPPFSM_recvconfreq.  Parses a ConfReq packet.
*     Builds a ConfAck/ConfNak/ConfRej packet out of fsm->PACKET,
*     and sets fsm->CODE appropriately.
*
*END*-----------------------------------------------------------------*/

static uint_32 LCP_recvconfreq
   (
      PPPFSM_CFG_PTR    fsm,
            /* [IN] - State Machine */
      boolean           reject
            /* [IN] - whether to ConfRej if we disagree */
   )
{ /* Body */
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   uchar_ptr   inp = fsm->DATA;
   uchar_ptr   nakp, rejp;
   uint_32     sizeleft = fsm->LENGTH;
   uint_32     naklen, rejlen;
   boolean     apneg = FALSE;
   PPP_OPT     req_opt = lcp_ptr->SEND_OPT;
   uchar       code;
   uchar       cicode, cilen;
/* Start CR 2207 */
#if PPP_SECRETS_NOT_SHARED
   PPP_CFG_PTR    ppp_ptr = lcp_ptr->HANDLE;
#endif
/* End CR 2207 */

#define CI_REJECT(n)                   \
   inp -= n;                           \
   code = CP_CODE_CONF_REJ;            \
   rejlen += cilen;                    \
   while (cilen--) *rejp++ = *inp++

#define CI_NAK                               \
   if (code != CP_CODE_CONF_REJ) {           \
      code = CP_CODE_CONF_NAK;               \
      if (reject) {                          \
         inp -= cilen;                       \
         naklen += cilen;                    \
         while (cilen--) *nakp++ = *inp++;   \
      } else {                               \
         apneg = FALSE;

#define CI_ENDNAK                            \
      } /* Endif */                          \
   } /* Endif */

#define CI_NAKAP(n)                          \
   if (code != CP_CODE_CONF_REJ) {           \
      code = CP_CODE_CONF_NAK;               \
      if (reject) {                          \
         inp -= n;                           \
         naklen += cilen;                    \
         while (cilen--) *nakp++ = *inp++;   \
      } else {                               \
         apneg = TRUE;                       \
         inp += cilen - n;                   \
      } /* Endif */                          \
   } else {                                  \
      inp += cilen - n;                      \
   } /* Endif */

   /*
   ** Process all requested codes
   */

   rejp = nakp = inp;
   rejlen = naklen = 0;
   code = CP_CODE_CONF_ACK;

   while (sizeleft) {
         /* check remaining length */
      if (sizeleft < inp[1] || inp[1] < 2) {
         code = CP_CODE_CONF_REJ;
         rejlen += sizeleft;
         while (sizeleft--) *rejp++ = *inp++;
         break;
      } /* Endif */

      cicode = *inp++;
      sizeleft -= cilen = *inp++;

      switch (cicode) {
      case LCP_CI_MRU:
         if (cilen != 4) {
            CI_REJECT(2);
            break;
         } /* Endif */

            /* Accept any MRU no smaller than 68 bytes */
         req_opt.MRU = ntohs(inp); inp += 2;
         if (req_opt.MRU > DEFAULT_MRU) {
            req_opt.MRU = DEFAULT_MRU;
         } else if (req_opt.MRU < MINIMUM_MRU) {
            CI_NAK {
               *nakp++ = LCP_CI_MRU;
               naklen += *nakp++ = 4;
               req_opt.MRU = MINIMUM_MRU;
               htons(nakp, req_opt.MRU); nakp += 2;
            } CI_ENDNAK;
         } /* Endif */
         break;

      case LCP_CI_ACCM:
         if (cilen != 6) {
            CI_REJECT(2);
            break;
         } /* Endif */

            /* If we want any characters not in their ACCM, nak it */
         req_opt.ACCM = ntohl(inp); inp += 4;
         if ((req_opt.ACCM & _PPP_ACCM) != _PPP_ACCM) {
            CI_NAK {
               *nakp++ = LCP_CI_ACCM;
               naklen += *nakp++ = 6;
               req_opt.ACCM |= _PPP_ACCM;
               htonl(nakp, req_opt.ACCM); nakp += 4;
            } CI_ENDNAK;
         } /* Endif */
         break;

         /*
         ** AP is unusual in that it is a variable length
         ** option.  Thus it is possible that we may want
         ** to nak AP and make a suggestion longer than
         ** the peer's request.
         **
         ** This is bad.
         **
         ** It's bad because this function wants to reuse
         ** the incoming ConfReq to build the reply, and
         ** in this case, it is possible for nakp to
         ** overtake inp, thus overwriting an option not
         ** yet parsed.
         **
         ** So we do the following:  If we decide to nak
         ** the requested AP, we just set apneg=TRUE and
         ** append the nak'd AP at the end of our ConfNak
         ** after we've completed parsing the ConfReq.
         **
         ** Note that we won't nak AP if we generate
         ** nak's for other options afterward, because
         ** the RFC states that nak'd options MUST be in
         ** the same order that they were in the ConfReq.
         */

      case LCP_CI_AP:
         if (cilen < 4) {
            CI_REJECT(2);
            break;
         } /* Endif */

            /* If we don't have any secrets, reject AP */
/* Start CR 2207 */
         if (!(PPP_SECRET(ppp_ptr,_PPP_PAP_LSECRET) || PPP_SECRET(ppp_ptr,_PPP_CHAP_LSECRETS))) {
/* End CR 2207 */
            CI_REJECT(2);
            break;
         } /* Endif */

            /* Check the desired authentication protocol */
         req_opt.AP = ntohs(inp); inp += 2;
         switch (req_opt.AP) {

            /* Accept PAP only if we have a secret */
         case PPP_PROT_PAP:
            if (cilen != 4) {
               CI_REJECT(4);
               break;
/* Start CR 2207 */
            } else if (PPP_SECRET(ppp_ptr,_PPP_PAP_LSECRET) == NULL) {
/* End CR 2207 */
               CI_NAKAP(4);
               break;
            } /* Endif */
            req_opt.AP_Start = PAP_send;
            break;

            /* Accept CHAP only if we have a secrets table */
         case PPP_PROT_CHAP:
            if (cilen != 5) {
               CI_REJECT(4);
               break;
/* Start CR 2207 */
            } else if (PPP_SECRET(ppp_ptr,_PPP_CHAP_LSECRETS) == NULL) {
/* End CR 2207 */
               CI_NAKAP(4);
               break;
            } else if (*inp++ != 5) {     /* Only MD5 supported */
               CI_NAKAP(5);
               break;
            } /* Endif */
            req_opt.AP_Start = CHAP_open;
            break;

         default:
            CI_NAKAP(4);
            break;
         } /* Endswitch */
         break;

      case LCP_CI_MAGIC:
         if (cilen != 6) {
            CI_REJECT(2);
            break;
         } /* Endif */

         inp += 4;
         break;

      case LCP_CI_PFC:
         if (cilen != 2) {
            CI_REJECT(2);
            break;
         } /* Endif */

         req_opt.PFC = TRUE;
         break;

      case LCP_CI_ACFC:
         if (cilen != 2) {
            CI_REJECT(2);
            break;
         } /* Endif */

         req_opt.ACFC = TRUE;
         break;

      default:
         CI_REJECT(2);
         break;
      } /* Endswitch */
   } /* Endwhile */

   /*
   ** If we wanted to send additional naks for unsent codes, they
   ** would go here, at *nakp.
   */

   if (code == CP_CODE_CONF_ACK) {
      lcp_ptr->SEND_OPT = req_opt;
      sizeleft = fsm->LENGTH;
   } else if (code == CP_CODE_CONF_REJ) {
      sizeleft = rejlen;
   } else if (reject) {
      code = CP_CODE_CONF_REJ;
      sizeleft = naklen;
   } else { /* if code == NAK */

         /* We want to nak AP, and we didn't nak anything else after it */
      if (apneg) {
/* Start CR 2207 */
         if (PPP_SECRET(ppp_ptr,_PPP_CHAP_LSECRETS)) {
/* End CR 2207 */
            *nakp++ = LCP_CI_AP;
            naklen += *nakp++ = 5;
            htons(nakp, PPP_PROT_CHAP); nakp += 2;
            *nakp++ = 5;                  /* Only MD5 supported */
/* Start CR 2207 */
         } else if (PPP_SECRET(ppp_ptr,_PPP_PAP_LSECRET)) {
/* End CR 2207 */
            *nakp++ = LCP_CI_AP;
            naklen += *nakp++ = 4;
            htons(nakp, PPP_PROT_PAP); nakp += 2;
         } /* Endif */
      } /* Endif */

      sizeleft = naklen;
   } /* Endif */

   fsm->CODE = code;
   return sizeleft;

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_recvcoderej
* Returned Value  :  TRUE or FALSE
* Comments        :
*     Called by PPPFSM_Recvcoderej.  Returns TRUE if the rejected
*     code is catastrophic.
*
*END*-----------------------------------------------------------------*/

static boolean LCP_recvcoderej
   (
      PPPFSM_CFG_PTR    fsm
            /* [IN] - State Machine */
   )
{ /* Body */
   uchar code = *fsm->DATA;
   return (code >= 1 && code <= 7);
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_up
* Returned Value  :  TRUE or FALSE
* Comments        :
*     Called by the FSM when entering the OPENED state.  Returns
*     TRUE if the link can be allowed to remain up.
*
*END*-----------------------------------------------------------------*/

static boolean LCP_up
   (
      PPPFSM_CFG_PTR          fsm
            /* [IN] - State Machine */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = fsm->HANDLE;
   LCP_CFG_PTR lcp_ptr = fsm->PRIVATE;
   PPP_CALL_INTERNAL_PTR callauth_ptr = &ppp_ptr->LCP_CALL[PPP_CALL_AUTH_FAIL];

/* Start CR 2207 */
   if ((PPP_SECRET(ppp_ptr,_PPP_PAP_RSECRETS) || PPP_SECRET(ppp_ptr,_PPP_CHAP_RSECRETS))
/* End CR 2207 */
       && !lcp_ptr->RECV_OPT.AP) {
      if (callauth_ptr->CALLBACK) {
         (*callauth_ptr->CALLBACK)(callauth_ptr->PARAM);
      } /* Endif */
      return FALSE;
   } /* Endif */

      /* Initialize the authentication protocols */
   PAP_init(fsm->HANDLE);
   CHAP_init(fsm->HANDLE);

   PPP_mutex_lock(&ppp_ptr->MUTEX);
   ppp_ptr->LINK_STATE = TRUE;
   ppp_ptr->LINK_AUTH = (lcp_ptr->RECV_OPT.AP == 0);
   ppp_ptr->RECV_OPTIONS = &lcp_ptr->RECV_OPT;
   ppp_ptr->SEND_OPTIONS = &lcp_ptr->SEND_OPT;
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_S_ACCM, &lcp_ptr->SEND_OPT.ACCM);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_R_ACCM, &lcp_ptr->RECV_OPT.ACCM);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_S_PFC,  &lcp_ptr->SEND_OPT.PFC);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_R_PFC,  &lcp_ptr->RECV_OPT.PFC);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_S_ACFC, &lcp_ptr->SEND_OPT.ACFC);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_R_ACFC, &lcp_ptr->RECV_OPT.ACFC);
   PPP_mutex_unlock(&ppp_ptr->MUTEX);

   /* Start CR 2158 */
   if ((lcp_ptr->SEND_OPT.AP != 0) || (lcp_ptr->RECV_OPT.AP != 0)) {
         // The link requires authentication
         
      /* If the peer requested authentication, send it */
      if (lcp_ptr->SEND_OPT.AP != 0) {
         (*lcp_ptr->SEND_OPT.AP_Start)(fsm->HANDLE);
	  } /* Endif */

      /*
      ** Note: we really should call RECV_OPT.AP_Start() before
      ** setting LINK_STATE (but still call PPP_LINK_UP() after),
      ** but, LCP_up() is always called from the context of the
      ** PPP rx task, so there isn't a problem.
      **
      ** (There would be a problem if an AP packet could be processed
      ** after setting LINK_STATE=TRUE, LINK_AUTH=FALSE and before
      ** AP_Start() could initialize the AP's state.  But there isn't
      ** a problem because we're always in the context of the only task
      ** that does this processing.)
      */

      /* If we requested authentication, wait for it */
      if (lcp_ptr->RECV_OPT.AP != 0) {
         (*lcp_ptr->RECV_OPT.AP_Start)(fsm->HANDLE);
	  }

   } else {
   /* Otherwise, call the application's PPP_LINK_UP callback */
      PPP_up(ppp_ptr);
   /* End CR 2158 */
   } /* Endif */

   return TRUE;
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  LCP_down
* Returned Value  :  void
* Comments        :
*     Called by the FSM when leaving the OPENED state.
*
*END*-----------------------------------------------------------------*/

static void LCP_down
   (
      PPPFSM_CFG_PTR          fsm
            /* [IN] - State Machine */
   )
{ /* Body */
   PPP_CFG_PTR ppp_ptr = fsm->HANDLE;
   PPP_CALL_INTERNAL_PTR calldown_ptr = &ppp_ptr->LCP_CALL[PPP_CALL_LINK_DOWN];
   boolean linkauth;

      /* Stop compression */
   CCP_lowerdown(fsm->HANDLE);

   PPP_mutex_lock(&ppp_ptr->MUTEX);
   ppp_ptr->LINK_STATE = FALSE;
   linkauth = ppp_ptr->LINK_AUTH;
   ppp_ptr->RECV_OPTIONS = &PPP_DEFAULT_OPTIONS;
   ppp_ptr->SEND_OPTIONS = &PPP_DEFAULT_OPTIONS;
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_S_ACCM, &PPP_DEFAULT_OPTIONS.ACCM);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_R_ACCM, &PPP_DEFAULT_OPTIONS.ACCM);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_S_PFC,  &PPP_DEFAULT_OPTIONS.PFC);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_R_PFC,  &PPP_DEFAULT_OPTIONS.PFC);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_S_ACFC, &PPP_DEFAULT_OPTIONS.ACFC);
   _iopcb_ioctl(ppp_ptr->DEVICE, IOPCB_IOCTL_R_ACFC, &PPP_DEFAULT_OPTIONS.ACFC);
   PPP_mutex_unlock(&ppp_ptr->MUTEX);

      /* Call the application's PPP_LINK_DOWN callback */
   if (calldown_ptr->CALLBACK && linkauth) {
      (*calldown_ptr->CALLBACK)(calldown_ptr->PARAM);
   } /* Endif */

} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   :  PPP_up
* Returned Value  :  void
* Comments        :
*     Called when the PPP link goes up.  Called either by LCP
*     when entering the OPENED state, or by an authentication
*     protocol once the peer has authenticated itself.
*
*END*-----------------------------------------------------------------*/

void PPP_up
   (
      _ppp_handle   handle
            /* [IN] - the PPP state structure */
   )
{ /* Body */
   PPP_CFG_PTR           ppp_ptr    = handle;
   PPP_CALL_INTERNAL_PTR callup_ptr = &ppp_ptr->LCP_CALL[PPP_CALL_LINK_UP];

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

   /* Try to negotiate compression */
   CCP_lowerup(handle);

} /* Endbody */


/* EOF */
