/*
 * lcp.c - PPP Link Control Protocol.
 *
 * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any legal
 *    details, please contact
 *      Office of Technology Transfer
 *      Carnegie Mellon University
 *      5000 Forbes Avenue
 *      Pittsburgh, PA  15213-3890
 *      (412) 268-4387, fax: (412) 268-7395
 *      tech-transfer@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "netif/ppp/ppp_opts.h"
#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */

/*
 * @todo:
 */

#if 0 /* UNUSED */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#endif /* UNUSED */

#include "netif/ppp/ppp_impl.h"

#include "netif/ppp/fsm.h"
#include "netif/ppp/lcp.h"
#if CHAP_SUPPORT
#include "netif/ppp/chap-new.h"
#endif /* CHAP_SUPPORT */
#include "netif/ppp/magic.h"

/*
 * When the link comes up we want to be able to wait for a short while,
 * or until seeing some input from the peer, before starting to send
 * configure-requests.  We do this by delaying the fsm_lowerup call.
 */
/* steal a bit in fsm flags word */
#define DELAYED_UP  0x80

static void lcp_delayed_up(void *arg);

/*
 * LCP-related command-line options.
 */
#if 0 /* UNUSED */
int lcp_echo_interval = 0;  /* Interval between LCP echo-requests */
int lcp_echo_fails = 0; /* Tolerance to unanswered echo-requests */
#endif /* UNUSED */

#if 0 /* UNUSED */
/* options */
static u_int lcp_echo_interval      = LCP_ECHOINTERVAL; /* Interval between LCP echo-requests */
static u_int lcp_echo_fails         = LCP_MAXECHOFAILS; /* Tolerance to unanswered echo-requests */
#endif /* UNUSED */

#if 0 /* UNUSED */
#if PPP_LCP_ADAPTIVE
bool    lcp_echo_adaptive = 0;  /* request echo only if the link was idle */
#endif
bool    lax_recv = 0;       /* accept control chars in asyncmap */
bool    noendpoint = 0;     /* don't send/accept endpoint discriminator */
#endif /* UNUSED */

#if PPP_OPTIONS
static int noopt (char **);
#endif /* PPP_OPTIONS */

#ifdef HAVE_MULTILINK
static int setendpoint (char **);
static void printendpoint (option_t *, void (*)(void *, char *, ...),
                   void *);
#endif /* HAVE_MULTILINK */

#if PPP_OPTIONS
static option_t lcp_option_list[] = {
    /* LCP options */
    { "-all", o_special_noarg, (void *)noopt,
      "Don't request/allow any LCP options" },

    { "noaccomp", o_bool, &lcp_wantoptions[0].neg_accompression,
      "Disable address/control compression",
      OPT_A2CLR, &lcp_allowoptions[0].neg_accompression },
    { "-ac", o_bool, &lcp_wantoptions[0].neg_accompression,
      "Disable address/control compression",
      OPT_ALIAS | OPT_A2CLR, &lcp_allowoptions[0].neg_accompression },

    { "asyncmap", o_uint32, &lcp_wantoptions[0].asyncmap,
      "Set asyncmap (for received packets)",
      OPT_OR, &lcp_wantoptions[0].neg_asyncmap },
    { "-as", o_uint32, &lcp_wantoptions[0].asyncmap,
      "Set asyncmap (for received packets)",
      OPT_ALIAS | OPT_OR, &lcp_wantoptions[0].neg_asyncmap },
    { "default-asyncmap", o_uint32, &lcp_wantoptions[0].asyncmap,
      "Disable asyncmap negotiation",
      OPT_OR | OPT_NOARG | OPT_VAL(~0U) | OPT_A2CLR,
      &lcp_allowoptions[0].neg_asyncmap },
    { "-am", o_uint32, &lcp_wantoptions[0].asyncmap,
      "Disable asyncmap negotiation",
      OPT_ALIAS | OPT_OR | OPT_NOARG | OPT_VAL(~0U) | OPT_A2CLR,
      &lcp_allowoptions[0].neg_asyncmap },

    { "nomagic", o_bool, &lcp_wantoptions[0].neg_magicnumber,
      "Disable magic number negotiation (looped-back line detection)",
      OPT_A2CLR, &lcp_allowoptions[0].neg_magicnumber },
    { "-mn", o_bool, &lcp_wantoptions[0].neg_magicnumber,
      "Disable magic number negotiation (looped-back line detection)",
      OPT_ALIAS | OPT_A2CLR, &lcp_allowoptions[0].neg_magicnumber },

    { "mru", o_int, &lcp_wantoptions[0].mru,
      "Set MRU (maximum received packet size) for negotiation",
      OPT_PRIO, &lcp_wantoptions[0].neg_mru },
    { "default-mru", o_bool, &lcp_wantoptions[0].neg_mru,
      "Disable MRU negotiation (use default 1500)",
      OPT_PRIOSUB | OPT_A2CLR, &lcp_allowoptions[0].neg_mru },
    { "-mru", o_bool, &lcp_wantoptions[0].neg_mru,
      "Disable MRU negotiation (use default 1500)",
      OPT_ALIAS | OPT_PRIOSUB | OPT_A2CLR, &lcp_allowoptions[0].neg_mru },

    { "mtu", o_int, &lcp_allowoptions[0].mru,
      "Set our MTU", OPT_LIMITS, NULL, MAXMRU, MINMRU },

    { "nopcomp", o_bool, &lcp_wantoptions[0].neg_pcompression,
      "Disable protocol field compression",
      OPT_A2CLR, &lcp_allowoptions[0].neg_pcompression },
    { "-pc", o_bool, &lcp_wantoptions[0].neg_pcompression,
      "Disable protocol field compression",
      OPT_ALIAS | OPT_A2CLR, &lcp_allowoptions[0].neg_pcompression },

    { "passive", o_bool, &lcp_wantoptions[0].passive,
      "Set passive mode", 1 },
    { "-p", o_bool, &lcp_wantoptions[0].passive,
      "Set passive mode", OPT_ALIAS | 1 },

    { "silent", o_bool, &lcp_wantoptions[0].silent,
      "Set silent mode", 1 },

    { "lcp-echo-failure", o_int, &lcp_echo_fails,
      "Set number of consecutive echo failures to indicate link failure",
      OPT_PRIO },
    { "lcp-echo-interval", o_int, &lcp_echo_interval,
      "Set time in seconds between LCP echo requests", OPT_PRIO },
#if PPP_LCP_ADAPTIVE
    { "lcp-echo-adaptive", o_bool, &lcp_echo_adaptive,
      "Suppress LCP echo requests if traffic was received", 1 },
#endif
    { "lcp-restart", o_int, &lcp_fsm[0].timeouttime,
      "Set time in seconds between LCP retransmissions", OPT_PRIO },
    { "lcp-max-terminate", o_int, &lcp_fsm[0].maxtermtransmits,
      "Set maximum number of LCP terminate-request transmissions", OPT_PRIO },
    { "lcp-max-configure", o_int, &lcp_fsm[0].maxconfreqtransmits,
      "Set maximum number of LCP configure-request transmissions", OPT_PRIO },
    { "lcp-max-failure", o_int, &lcp_fsm[0].maxnakloops,
      "Set limit on number of LCP configure-naks", OPT_PRIO },

    { "receive-all", o_bool, &lax_recv,
      "Accept all received control characters", 1 },

#ifdef HAVE_MULTILINK
    { "mrru", o_int, &lcp_wantoptions[0].mrru,
      "Maximum received packet size for multilink bundle",
      OPT_PRIO, &lcp_wantoptions[0].neg_mrru },

    { "mpshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf,
      "Use short sequence numbers in multilink headers",
      OPT_PRIO | 1, &lcp_allowoptions[0].neg_ssnhf },
    { "nompshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf,
      "Don't use short sequence numbers in multilink headers",
      OPT_PRIOSUB | OPT_A2CLR, &lcp_allowoptions[0].neg_ssnhf },

    { "endpoint", o_special, (void *) setendpoint,
      "Endpoint discriminator for multilink",
      OPT_PRIO | OPT_A2PRINTER, (void *) printendpoint },
#endif /* HAVE_MULTILINK */

    { "noendpoint", o_bool, &noendpoint,
      "Don't send or accept multilink endpoint discriminator", 1 },

    {NULL}
};
#endif /* PPP_OPTIONS */

/*
 * Callbacks for fsm code.  (CI = Configuration Information)
 */
static void lcp_resetci(fsm *f);    /* Reset our CI */
static int  lcp_cilen(fsm *f);      /* Return length of our CI */
static void lcp_addci(fsm *f, u_char *ucp, int *lenp); /* Add our CI to pkt */
static int  lcp_ackci(fsm *f, u_char *p, int len); /* Peer ack'd our CI */
static int  lcp_nakci(fsm *f, u_char *p, int len, int treat_as_reject); /* Peer nak'd our CI */
static int  lcp_rejci(fsm *f, u_char *p, int len); /* Peer rej'd our CI */
static int  lcp_reqci(fsm *f, u_char *inp, int *lenp, int reject_if_disagree); /* Rcv peer CI */
static void lcp_up(fsm *f);     /* We're UP */
static void lcp_down(fsm *f);       /* We're DOWN */
static void lcp_starting (fsm *);   /* We need lower layer up */
static void lcp_finished (fsm *);   /* We need lower layer down */
static int  lcp_extcode(fsm *f, int code, int id, u_char *inp, int len);
static void lcp_rprotrej(fsm *f, u_char *inp, int len);

/*
 * routines to send LCP echos to peer
 */

static void lcp_echo_lowerup(ppp_pcb *pcb);
static void lcp_echo_lowerdown(ppp_pcb *pcb);
static void LcpEchoTimeout(void *arg);
static void lcp_received_echo_reply(fsm *f, int id, u_char *inp, int len);
static void LcpSendEchoRequest(fsm *f);
static void LcpLinkFailure(fsm *f);
static void LcpEchoCheck(fsm *f);

static const fsm_callbacks lcp_callbacks = {    /* LCP callback routines */
    lcp_resetci,        /* Reset our Configuration Information */
    lcp_cilen,          /* Length of our Configuration Information */
    lcp_addci,          /* Add our Configuration Information */
    lcp_ackci,          /* ACK our Configuration Information */
    lcp_nakci,          /* NAK our Configuration Information */
    lcp_rejci,          /* Reject our Configuration Information */
    lcp_reqci,          /* Request peer's Configuration Information */
    lcp_up,         /* Called when fsm reaches OPENED state */
    lcp_down,           /* Called when fsm leaves OPENED state */
    lcp_starting,       /* Called when we want the lower layer up */
    lcp_finished,       /* Called when we want the lower layer down */
    NULL,           /* Called when Protocol-Reject received */
    NULL,           /* Retransmission is necessary */
    lcp_extcode,        /* Called to handle LCP-specific codes */
    "LCP"           /* String name of protocol */
};

/*
 * Protocol entry points.
 * Some of these are called directly.
 */

static void lcp_init(ppp_pcb *pcb);
static void lcp_input(ppp_pcb *pcb, u_char *p, int len);
static void lcp_protrej(ppp_pcb *pcb);
#if PRINTPKT_SUPPORT
static int lcp_printpkt(const u_char *p, int plen,
        void (*printer) (void *, const char *, ...), void *arg);
#endif /* PRINTPKT_SUPPORT */

const struct protent lcp_protent = {
    PPP_LCP,
    lcp_init,
    lcp_input,
    lcp_protrej,
    lcp_lowerup,
    lcp_lowerdown,
    lcp_open,
    lcp_close,
#if PRINTPKT_SUPPORT
    lcp_printpkt,
#endif /* PRINTPKT_SUPPORT */
#if PPP_DATAINPUT
    NULL,
#endif /* PPP_DATAINPUT */
#if PRINTPKT_SUPPORT
    "LCP",
    NULL,
#endif /* PRINTPKT_SUPPORT */
#if PPP_OPTIONS
    lcp_option_list,
    NULL,
#endif /* PPP_OPTIONS */
#if DEMAND_SUPPORT
    NULL,
    NULL
#endif /* DEMAND_SUPPORT */
};

/*
 * Length of each type of configuration option (in octets)
 */
#define CILEN_VOID  2
#define CILEN_CHAR  3
#define CILEN_SHORT 4   /* CILEN_VOID + 2 */
#if CHAP_SUPPORT
#define CILEN_CHAP  5   /* CILEN_VOID + 2 + 1 */
#endif /* CHAP_SUPPORT */
#define CILEN_LONG  6   /* CILEN_VOID + 4 */
#if LQR_SUPPORT
#define CILEN_LQR   8   /* CILEN_VOID + 2 + 4 */
#endif /* LQR_SUPPORT */
#define CILEN_CBCP  3

#define CODENAME(x) ((x) == CONFACK ? "ACK" : \
             (x) == CONFNAK ? "NAK" : "REJ")

#if PPP_OPTIONS
/*
 * noopt - Disable all options (why?).
 */
static int
noopt(argv)
    char **argv;
{
    BZERO((char *) &lcp_wantoptions[0], sizeof (struct lcp_options));
    BZERO((char *) &lcp_allowoptions[0], sizeof (struct lcp_options));

    return (1);
}
#endif /* PPP_OPTIONS */

#ifdef HAVE_MULTILINK
static int
setendpoint(argv)
    char **argv;
{
    if (str_to_epdisc(&lcp_wantoptions[0].endpoint, *argv)) {
    lcp_wantoptions[0].neg_endpoint = 1;
    return 1;
    }
    option_error("Can't parse '%s' as an endpoint discriminator", *argv);
    return 0;
}

static void
printendpoint(opt, printer, arg)
    option_t *opt;
    void (*printer) (void *, char *, ...);
    void *arg;
{
    printer(arg, "%s", epdisc_to_str(&lcp_wantoptions[0].endpoint));
}
#endif /* HAVE_MULTILINK */

/*
 * lcp_init - Initialize LCP.
 */
static void lcp_init(ppp_pcb *pcb) {
    fsm *f = &pcb->lcp_fsm;
    lcp_options *wo = &pcb->lcp_wantoptions;
    lcp_options *ao = &pcb->lcp_allowoptions;

    f->pcb = pcb;
    f->protocol = PPP_LCP;
    f->callbacks = &lcp_callbacks;

    fsm_init(f);

    BZERO(wo, sizeof(*wo));
    wo->neg_mru = 1;
    wo->mru = PPP_DEFMRU;
    wo->neg_asyncmap = 1;
    wo->neg_magicnumber = 1;
    wo->neg_pcompression = 1;
    wo->neg_accompression = 1;

    BZERO(ao, sizeof(*ao));
    ao->neg_mru = 1;
    ao->mru = PPP_MAXMRU;
    ao->neg_asyncmap = 1;
#if CHAP_SUPPORT
    ao->neg_chap = 1;
    ao->chap_mdtype = CHAP_MDTYPE_SUPPORTED;
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
    ao->neg_upap = 1;
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
    ao->neg_eap = 1;
#endif /* EAP_SUPPORT */
    ao->neg_magicnumber = 1;
    ao->neg_pcompression = 1;
    ao->neg_accompression = 1;
    ao->neg_endpoint = 1;
}


/*
 * lcp_open - LCP is allowed to come up.
 */
void lcp_open(ppp_pcb *pcb) {
    fsm *f = &pcb->lcp_fsm;
    lcp_options *wo = &pcb->lcp_wantoptions;

    f->flags &= ~(OPT_PASSIVE | OPT_SILENT);
    if (wo->passive)
    f->flags |= OPT_PASSIVE;
    if (wo->silent)
    f->flags |= OPT_SILENT;
    fsm_open(f);
}


/*
 * lcp_close - Take LCP down.
 */
void lcp_close(ppp_pcb *pcb, const char *reason) {
    fsm *f = &pcb->lcp_fsm;
    int oldstate;

    if (pcb->phase != PPP_PHASE_DEAD
#ifdef HAVE_MULTILINK
    && pcb->phase != PPP_PHASE_MASTER
#endif /* HAVE_MULTILINK */
    )
    new_phase(pcb, PPP_PHASE_TERMINATE);

    if (f->flags & DELAYED_UP) {
    UNTIMEOUT(lcp_delayed_up, f);
    f->state = PPP_FSM_STOPPED;
    }
    oldstate = f->state;

    fsm_close(f, reason);
    if (oldstate == PPP_FSM_STOPPED && (f->flags & (OPT_PASSIVE|OPT_SILENT|DELAYED_UP))) {
    /*
     * This action is not strictly according to the FSM in RFC1548,
     * but it does mean that the program terminates if you do a
     * lcp_close() when a connection hasn't been established
     * because we are in passive/silent mode or because we have
     * delayed the fsm_lowerup() call and it hasn't happened yet.
     */
    f->flags &= ~DELAYED_UP;
    lcp_finished(f);
    }
}


/*
 * lcp_lowerup - The lower layer is up.
 */
void lcp_lowerup(ppp_pcb *pcb) {
    lcp_options *wo = &pcb->lcp_wantoptions;
    fsm *f = &pcb->lcp_fsm;
    /*
     * Don't use A/C or protocol compression on transmission,
     * but accept A/C and protocol compressed packets
     * if we are going to ask for A/C and protocol compression.
     */
    if (ppp_send_config(pcb, PPP_MRU, 0xffffffff, 0, 0) < 0
    || ppp_recv_config(pcb, PPP_MRU, (pcb->settings.lax_recv? 0: 0xffffffff),
               wo->neg_pcompression, wo->neg_accompression) < 0)
        return;
    pcb->peer_mru = PPP_MRU;

    if (pcb->settings.listen_time != 0) {
    f->flags |= DELAYED_UP;
    TIMEOUTMS(lcp_delayed_up, f, pcb->settings.listen_time);
    } else
    fsm_lowerup(f);
}


/*
 * lcp_lowerdown - The lower layer is down.
 */
void lcp_lowerdown(ppp_pcb *pcb) {
    fsm *f = &pcb->lcp_fsm;

    if (f->flags & DELAYED_UP) {
    f->flags &= ~DELAYED_UP;
    UNTIMEOUT(lcp_delayed_up, f);
    } else
    fsm_lowerdown(f);
}


/*
 * lcp_delayed_up - Bring the lower layer up now.
 */
static void lcp_delayed_up(void *arg) {
    fsm *f = (fsm*)arg;

    if (f->flags & DELAYED_UP) {
    f->flags &= ~DELAYED_UP;
    fsm_lowerup(f);
    }
}


/*
 * lcp_input - Input LCP packet.
 */
static void lcp_input(ppp_pcb *pcb, u_char *p, int len) {
    fsm *f = &pcb->lcp_fsm;

    if (f->flags & DELAYED_UP) {
    f->flags &= ~DELAYED_UP;
    UNTIMEOUT(lcp_delayed_up, f);
    fsm_lowerup(f);
    }
    fsm_input(f, p, len);
}

/*
 * lcp_extcode - Handle a LCP-specific code.
 */
static int lcp_extcode(fsm *f, int code, int id, u_char *inp, int len) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;
    u_char *magp;

    switch( code ){
    case PROTREJ:
    lcp_rprotrej(f, inp, len);
    break;
    
    case ECHOREQ:
    if (f->state != PPP_FSM_OPENED)
        break;
    magp = inp;
    PUTLONG(go->magicnumber, magp);
    fsm_sdata(f, ECHOREP, id, inp, len);
    break;
    
    case ECHOREP:
    lcp_received_echo_reply(f, id, inp, len);
    break;

    case DISCREQ:
    case IDENTIF:
    case TIMEREM:
    break;

    default:
    return 0;
    }
    return 1;
}

    
/*
 * lcp_rprotrej - Receive an Protocol-Reject.
 *
 * Figure out which protocol is rejected and inform it.
 */
static void lcp_rprotrej(fsm *f, u_char *inp, int len) {
    int i;
    const struct protent *protp;
    u_short prot;
#if PPP_PROTOCOLNAME
    const char *pname;
#endif /* PPP_PROTOCOLNAME */

    if (len < 2) {
    LCPDEBUG(("lcp_rprotrej: Rcvd short Protocol-Reject packet!"));
    return;
    }

    GETSHORT(prot, inp);

    /*
     * Protocol-Reject packets received in any state other than the LCP
     * OPENED state SHOULD be silently discarded.
     */
    if( f->state != PPP_FSM_OPENED ){
    LCPDEBUG(("Protocol-Reject discarded: LCP in state %d", f->state));
    return;
    }

#if PPP_PROTOCOLNAME
    pname = protocol_name(prot);
#endif /* PPP_PROTOCOLNAME */

    /*
     * Upcall the proper Protocol-Reject routine.
     */
    for (i = 0; (protp = protocols[i]) != NULL; ++i)
    if (protp->protocol == prot) {
#if PPP_PROTOCOLNAME
        if (pname != NULL)
        ppp_dbglog("Protocol-Reject for '%s' (0x%x) received", pname,
               prot);
        else
#endif /* PPP_PROTOCOLNAME */
        ppp_dbglog("Protocol-Reject for 0x%x received", prot);
        (*protp->protrej)(f->pcb);
        return;
    }

#if PPP_PROTOCOLNAME
    if (pname != NULL)
    ppp_warn("Protocol-Reject for unsupported protocol '%s' (0x%x)", pname,
         prot);
    else
#endif /* #if PPP_PROTOCOLNAME */
    ppp_warn("Protocol-Reject for unsupported protocol 0x%x", prot);
}


/*
 * lcp_protrej - A Protocol-Reject was received.
 */
/*ARGSUSED*/
static void lcp_protrej(ppp_pcb *pcb) {
    /*
     * Can't reject LCP!
     */
    ppp_error("Received Protocol-Reject for LCP!");
    fsm_protreject(&pcb->lcp_fsm);
}


/*
 * lcp_sprotrej - Send a Protocol-Reject for some protocol.
 */
void lcp_sprotrej(ppp_pcb *pcb, u_char *p, int len) {
    fsm *f = &pcb->lcp_fsm;
    /*
     * Send back the protocol and the information field of the
     * rejected packet.  We only get here if LCP is in the OPENED state.
     */
#if 0
    p += 2;
    len -= 2;
#endif

    fsm_sdata(f, PROTREJ, ++f->id,
          p, len);
}


/*
 * lcp_resetci - Reset our CI.
 */
static void lcp_resetci(fsm *f) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *wo = &pcb->lcp_wantoptions;
    lcp_options *go = &pcb->lcp_gotoptions;
    lcp_options *ao = &pcb->lcp_allowoptions;

#if PPP_AUTH_SUPPORT

    /* note: default value is true for allow options */
    if (pcb->settings.user && pcb->settings.passwd) {
#if PAP_SUPPORT
      if (pcb->settings.refuse_pap) {
        ao->neg_upap = 0;
      }
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
      if (pcb->settings.refuse_chap) {
        ao->chap_mdtype &= ~MDTYPE_MD5;
      }
#if MSCHAP_SUPPORT
      if (pcb->settings.refuse_mschap) {
        ao->chap_mdtype &= ~MDTYPE_MICROSOFT;
      }
      if (pcb->settings.refuse_mschap_v2) {
        ao->chap_mdtype &= ~MDTYPE_MICROSOFT_V2;
      }
#endif /* MSCHAP_SUPPORT */
      ao->neg_chap = (ao->chap_mdtype != MDTYPE_NONE);
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
      if (pcb->settings.refuse_eap) {
        ao->neg_eap = 0;
      }
#endif /* EAP_SUPPORT */

#if PPP_SERVER
      /* note: default value is false for wanted options */
      if (pcb->settings.auth_required) {
#if PAP_SUPPORT
        if (!pcb->settings.refuse_pap) {
          wo->neg_upap = 1;
        }
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
        if (!pcb->settings.refuse_chap) {
          wo->chap_mdtype |= MDTYPE_MD5;
        }
#if MSCHAP_SUPPORT
        if (!pcb->settings.refuse_mschap) {
          wo->chap_mdtype |= MDTYPE_MICROSOFT;
        }
        if (!pcb->settings.refuse_mschap_v2) {
          wo->chap_mdtype |= MDTYPE_MICROSOFT_V2;
        }
#endif /* MSCHAP_SUPPORT */
        wo->neg_chap = (wo->chap_mdtype != MDTYPE_NONE);
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
        if (!pcb->settings.refuse_eap) {
          wo->neg_eap = 1;
        }
#endif /* EAP_SUPPORT */
      }
#endif /* PPP_SERVER */

    } else {
#if PAP_SUPPORT
      ao->neg_upap = 0;
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
      ao->neg_chap = 0;
      ao->chap_mdtype = MDTYPE_NONE;
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
      ao->neg_eap = 0;
#endif /* EAP_SUPPORT */
    }

    PPPDEBUG(LOG_DEBUG, ("ppp: auth protocols:"));
#if PAP_SUPPORT
    PPPDEBUG(LOG_DEBUG, (" PAP=%d", ao->neg_upap));
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
    PPPDEBUG(LOG_DEBUG, (" CHAP=%d CHAP_MD5=%d", ao->neg_chap, !!(ao->chap_mdtype&MDTYPE_MD5)));
#if MSCHAP_SUPPORT
    PPPDEBUG(LOG_DEBUG, (" CHAP_MS=%d CHAP_MS2=%d", !!(ao->chap_mdtype&MDTYPE_MICROSOFT), !!(ao->chap_mdtype&MDTYPE_MICROSOFT_V2)));
#endif /* MSCHAP_SUPPORT */
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
    PPPDEBUG(LOG_DEBUG, (" EAP=%d", ao->neg_eap));
#endif /* EAP_SUPPORT */
    PPPDEBUG(LOG_DEBUG, ("\n"));

#endif /* PPP_AUTH_SUPPORT */

    wo->magicnumber = magic();
    wo->numloops = 0;
    *go = *wo;
#ifdef HAVE_MULTILINK
    if (!multilink) {
    go->neg_mrru = 0;
#endif /* HAVE_MULTILINK */
    go->neg_ssnhf = 0;
    go->neg_endpoint = 0;
#ifdef HAVE_MULTILINK
    }
#endif /* HAVE_MULTILINK */
    if (pcb->settings.noendpoint)
    ao->neg_endpoint = 0;
    pcb->peer_mru = PPP_MRU;
#if 0 /* UNUSED */
    auth_reset(pcb);
#endif /* UNUSED */
}


/*
 * lcp_cilen - Return length of our CI.
 */
static int lcp_cilen(fsm *f) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;

#define LENCIVOID(neg)  ((neg) ? CILEN_VOID : 0)
#if CHAP_SUPPORT
#define LENCICHAP(neg)  ((neg) ? CILEN_CHAP : 0)
#endif /* CHAP_SUPPORT */
#define LENCISHORT(neg) ((neg) ? CILEN_SHORT : 0)
#define LENCILONG(neg)  ((neg) ? CILEN_LONG : 0)
#if LQR_SUPPORT
#define LENCILQR(neg)   ((neg) ? CILEN_LQR: 0)
#endif /* LQR_SUPPORT */
#define LENCICBCP(neg)  ((neg) ? CILEN_CBCP: 0)
    /*
     * NB: we only ask for one of CHAP, UPAP, or EAP, even if we will
     * accept more than one.  We prefer EAP first, then CHAP, then
     * PAP.
     */
    return (LENCISHORT(go->neg_mru && go->mru != PPP_DEFMRU) +
        LENCILONG(go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) +
#if EAP_SUPPORT
        LENCISHORT(go->neg_eap) +
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT /* cannot be improved, embedding a directive within macro arguments is not portable */
#if EAP_SUPPORT
        LENCICHAP(!go->neg_eap && go->neg_chap) +
#endif /* EAP_SUPPORT */
#if !EAP_SUPPORT
        LENCICHAP(go->neg_chap) +
#endif /* !EAP_SUPPORT */
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT /* cannot be improved, embedding a directive within macro arguments is not portable */
#if EAP_SUPPORT && CHAP_SUPPORT
        LENCISHORT(!go->neg_eap && !go->neg_chap && go->neg_upap) +
#endif /* EAP_SUPPORT && CHAP_SUPPORT */
#if EAP_SUPPORT && !CHAP_SUPPORT
        LENCISHORT(!go->neg_eap && go->neg_upap) +
#endif /* EAP_SUPPORT && !CHAP_SUPPORT */
#if !EAP_SUPPORT && CHAP_SUPPORT
        LENCISHORT(!go->neg_chap && go->neg_upap) +
#endif /* !EAP_SUPPORT && CHAP_SUPPORT */
#if !EAP_SUPPORT && !CHAP_SUPPORT
        LENCISHORT(go->neg_upap) +
#endif /* !EAP_SUPPORT && !CHAP_SUPPORT */
#endif /* PAP_SUPPORT */
#if LQR_SUPPORT
        LENCILQR(go->neg_lqr) +
#endif /* LQR_SUPPORT */
        LENCICBCP(go->neg_cbcp) +
        LENCILONG(go->neg_magicnumber) +
        LENCIVOID(go->neg_pcompression) +
        LENCIVOID(go->neg_accompression) +
#ifdef HAVE_MULTILINK
        LENCISHORT(go->neg_mrru) +
#endif /* HAVE_MULTILINK */
        LENCIVOID(go->neg_ssnhf) +
        (go->neg_endpoint? CILEN_CHAR + go->endpoint.length: 0));
}


/*
 * lcp_addci - Add our desired CIs to a packet.
 */
static void lcp_addci(fsm *f, u_char *ucp, int *lenp) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;
    u_char *start_ucp = ucp;

#define ADDCIVOID(opt, neg) \
    if (neg) { \
    PUTCHAR(opt, ucp); \
    PUTCHAR(CILEN_VOID, ucp); \
    }
#define ADDCISHORT(opt, neg, val) \
    if (neg) { \
    PUTCHAR(opt, ucp); \
    PUTCHAR(CILEN_SHORT, ucp); \
    PUTSHORT(val, ucp); \
    }
#if CHAP_SUPPORT
#define ADDCICHAP(opt, neg, val) \
    if (neg) { \
    PUTCHAR((opt), ucp); \
    PUTCHAR(CILEN_CHAP, ucp); \
    PUTSHORT(PPP_CHAP, ucp); \
    PUTCHAR((CHAP_DIGEST(val)), ucp); \
    }
#endif /* CHAP_SUPPORT */
#define ADDCILONG(opt, neg, val) \
    if (neg) { \
    PUTCHAR(opt, ucp); \
    PUTCHAR(CILEN_LONG, ucp); \
    PUTLONG(val, ucp); \
    }
#if LQR_SUPPORT
#define ADDCILQR(opt, neg, val) \
    if (neg) { \
    PUTCHAR(opt, ucp); \
    PUTCHAR(CILEN_LQR, ucp); \
    PUTSHORT(PPP_LQR, ucp); \
    PUTLONG(val, ucp); \
    }
#endif /* LQR_SUPPORT */
#define ADDCICHAR(opt, neg, val) \
    if (neg) { \
    PUTCHAR(opt, ucp); \
    PUTCHAR(CILEN_CHAR, ucp); \
    PUTCHAR(val, ucp); \
    }
#define ADDCIENDP(opt, neg, class, val, len) \
    if (neg) { \
    int i; \
    PUTCHAR(opt, ucp); \
    PUTCHAR(CILEN_CHAR + len, ucp); \
    PUTCHAR(class, ucp); \
    for (i = 0; i < len; ++i) \
        PUTCHAR(val[i], ucp); \
    }

    ADDCISHORT(CI_MRU, go->neg_mru && go->mru != PPP_DEFMRU, go->mru);
    ADDCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF,
          go->asyncmap);
#if EAP_SUPPORT
    ADDCISHORT(CI_AUTHTYPE, go->neg_eap, PPP_EAP);
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT /* cannot be improved, embedding a directive within macro arguments is not portable */
#if EAP_SUPPORT
    ADDCICHAP(CI_AUTHTYPE, !go->neg_eap && go->neg_chap, go->chap_mdtype);
#endif /* EAP_SUPPORT */
#if !EAP_SUPPORT
    ADDCICHAP(CI_AUTHTYPE, go->neg_chap, go->chap_mdtype);
#endif /* !EAP_SUPPORT */
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT /* cannot be improved, embedding a directive within macro arguments is not portable */
#if EAP_SUPPORT && CHAP_SUPPORT
    ADDCISHORT(CI_AUTHTYPE, !go->neg_eap && !go->neg_chap && go->neg_upap, PPP_PAP);
#endif /* EAP_SUPPORT && CHAP_SUPPORT */
#if EAP_SUPPORT && !CHAP_SUPPORT
    ADDCISHORT(CI_AUTHTYPE, !go->neg_eap && go->neg_upap, PPP_PAP);
#endif /* EAP_SUPPORT && !CHAP_SUPPORT */
#if !EAP_SUPPORT && CHAP_SUPPORT
    ADDCISHORT(CI_AUTHTYPE, !go->neg_chap && go->neg_upap, PPP_PAP);
#endif /* !EAP_SUPPORT && CHAP_SUPPORT */
#if !EAP_SUPPORT && !CHAP_SUPPORT
    ADDCISHORT(CI_AUTHTYPE, go->neg_upap, PPP_PAP);
#endif /* !EAP_SUPPORT && !CHAP_SUPPORT */
#endif /* PAP_SUPPORT */
#if LQR_SUPPORT
    ADDCILQR(CI_QUALITY, go->neg_lqr, go->lqr_period);
#endif /* LQR_SUPPORT */
    ADDCICHAR(CI_CALLBACK, go->neg_cbcp, CBCP_OPT);
    ADDCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber);
    ADDCIVOID(CI_PCOMPRESSION, go->neg_pcompression);
    ADDCIVOID(CI_ACCOMPRESSION, go->neg_accompression);
#ifdef HAVE_MULTILINK
    ADDCISHORT(CI_MRRU, go->neg_mrru, go->mrru);
#endif
    ADDCIVOID(CI_SSNHF, go->neg_ssnhf);
    ADDCIENDP(CI_EPDISC, go->neg_endpoint, go->endpoint.class_,
          go->endpoint.value, go->endpoint.length);

    if (ucp - start_ucp != *lenp) {
    /* this should never happen, because peer_mtu should be 1500 */
    ppp_error("Bug in lcp_addci: wrong length");
    }
}


/*
 * lcp_ackci - Ack our CIs.
 * This should not modify any state if the Ack is bad.
 *
 * Returns:
 *  0 - Ack was bad.
 *  1 - Ack was good.
 */
static int lcp_ackci(fsm *f, u_char *p, int len) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;
    u_char cilen, citype, cichar;
    u_short cishort;
    u32_t cilong;

    /*
     * CIs must be in exactly the same order that we sent.
     * Check packet length and CI length at each step.
     * If we find any deviations, then this packet is bad.
     */
#define ACKCIVOID(opt, neg) \
    if (neg) { \
    if ((len -= CILEN_VOID) < 0) \
        goto bad; \
    GETCHAR(citype, p); \
    GETCHAR(cilen, p); \
    if (cilen != CILEN_VOID || \
        citype != opt) \
        goto bad; \
    }
#define ACKCISHORT(opt, neg, val) \
    if (neg) { \
    if ((len -= CILEN_SHORT) < 0) \
        goto bad; \
    GETCHAR(citype, p); \
    GETCHAR(cilen, p); \
    if (cilen != CILEN_SHORT || \
        citype != opt) \
        goto bad; \
    GETSHORT(cishort, p); \
    if (cishort != val) \
        goto bad; \
    }
#define ACKCICHAR(opt, neg, val) \
    if (neg) { \
    if ((len -= CILEN_CHAR) < 0) \
        goto bad; \
    GETCHAR(citype, p); \
    GETCHAR(cilen, p); \
    if (cilen != CILEN_CHAR || \
        citype != opt) \
        goto bad; \
    GETCHAR(cichar, p); \
    if (cichar != val) \
        goto bad; \
    }
#if CHAP_SUPPORT
#define ACKCICHAP(opt, neg, val) \
    if (neg) { \
    if ((len -= CILEN_CHAP) < 0) \
        goto bad; \
    GETCHAR(citype, p); \
    GETCHAR(cilen, p); \
    if (cilen != CILEN_CHAP || \
        citype != (opt)) \
        goto bad; \
    GETSHORT(cishort, p); \
    if (cishort != PPP_CHAP) \
        goto bad; \
    GETCHAR(cichar, p); \
    if (cichar != (CHAP_DIGEST(val))) \
      goto bad; \
    }
#endif /* CHAP_SUPPORT */
#define ACKCILONG(opt, neg, val) \
    if (neg) { \
    if ((len -= CILEN_LONG) < 0) \
        goto bad; \
    GETCHAR(citype, p); \
    GETCHAR(cilen, p); \
    if (cilen != CILEN_LONG || \
        citype != opt) \
        goto bad; \
    GETLONG(cilong, p); \
    if (cilong != val) \
        goto bad; \
    }
#if LQR_SUPPORT
#define ACKCILQR(opt, neg, val) \
    if (neg) { \
    if ((len -= CILEN_LQR) < 0) \
        goto bad; \
    GETCHAR(citype, p); \
    GETCHAR(cilen, p); \
    if (cilen != CILEN_LQR || \
        citype != opt) \
        goto bad; \
    GETSHORT(cishort, p); \
    if (cishort != PPP_LQR) \
        goto bad; \
    GETLONG(cilong, p); \
    if (cilong != val) \
      goto bad; \
    }
#endif /* LQR_SUPPORT */
#define ACKCIENDP(opt, neg, class, val, vlen) \
    if (neg) { \
    int i; \
    if ((len -= CILEN_CHAR + vlen) < 0) \
        goto bad; \
    GETCHAR(citype, p); \
    GETCHAR(cilen, p); \
    if (cilen != CILEN_CHAR + vlen || \
        citype != opt) \
        goto bad; \
    GETCHAR(cichar, p); \
    if (cichar != class) \
        goto bad; \
    for (i = 0; i < vlen; ++i) { \
        GETCHAR(cichar, p); \
        if (cichar != val[i]) \
        goto bad; \
    } \
    }

    ACKCISHORT(CI_MRU, go->neg_mru && go->mru != PPP_DEFMRU, go->mru);
    ACKCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF,
          go->asyncmap);
#if EAP_SUPPORT
    ACKCISHORT(CI_AUTHTYPE, go->neg_eap, PPP_EAP);
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT /* cannot be improved, embedding a directive within macro arguments is not portable */
#if EAP_SUPPORT
    ACKCICHAP(CI_AUTHTYPE, !go->neg_eap && go->neg_chap, go->chap_mdtype);
#endif /* EAP_SUPPORT */
#if !EAP_SUPPORT
    ACKCICHAP(CI_AUTHTYPE, go->neg_chap, go->chap_mdtype);
#endif /* !EAP_SUPPORT */
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT /* cannot be improved, embedding a directive within macro arguments is not portable */
#if EAP_SUPPORT && CHAP_SUPPORT
    ACKCISHORT(CI_AUTHTYPE, !go->neg_eap && !go->neg_chap && go->neg_upap, PPP_PAP);
#endif /* EAP_SUPPORT && CHAP_SUPPORT */
#if EAP_SUPPORT && !CHAP_SUPPORT
    ACKCISHORT(CI_AUTHTYPE, !go->neg_eap && go->neg_upap, PPP_PAP);
#endif /* EAP_SUPPORT && !CHAP_SUPPORT */
#if !EAP_SUPPORT && CHAP_SUPPORT
    ACKCISHORT(CI_AUTHTYPE, !go->neg_chap && go->neg_upap, PPP_PAP);
#endif /* !EAP_SUPPORT && CHAP_SUPPORT */
#if !EAP_SUPPORT && !CHAP_SUPPORT
    ACKCISHORT(CI_AUTHTYPE, go->neg_upap, PPP_PAP);
#endif /* !EAP_SUPPORT && !CHAP_SUPPORT */
#endif /* PAP_SUPPORT */
#if LQR_SUPPORT
    ACKCILQR(CI_QUALITY, go->neg_lqr, go->lqr_period);
#endif /* LQR_SUPPORT */
    ACKCICHAR(CI_CALLBACK, go->neg_cbcp, CBCP_OPT);
    ACKCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber);
    ACKCIVOID(CI_PCOMPRESSION, go->neg_pcompression);
    ACKCIVOID(CI_ACCOMPRESSION, go->neg_accompression);
#ifdef HAVE_MULTILINK
    ACKCISHORT(CI_MRRU, go->neg_mrru, go->mrru);
#endif /* HAVE_MULTILINK */
    ACKCIVOID(CI_SSNHF, go->neg_ssnhf);
    ACKCIENDP(CI_EPDISC, go->neg_endpoint, go->endpoint.class_,
          go->endpoint.value, go->endpoint.length);

    /*
     * If there are any remaining CIs, then this packet is bad.
     */
    if (len != 0)
    goto bad;
    return (1);
bad:
    LCPDEBUG(("lcp_acki: received bad Ack!"));
    return (0);
}


/*
 * lcp_nakci - Peer has sent a NAK for some of our CIs.
 * This should not modify any state if the Nak is bad
 * or if LCP is in the OPENED state.
 *
 * Returns:
 *  0 - Nak was bad.
 *  1 - Nak was good.
 */
static int lcp_nakci(fsm *f, u_char *p, int len, int treat_as_reject) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;
    lcp_options *wo = &pcb->lcp_wantoptions;
    u_char citype, cichar, *next;
    u_short cishort;
    u32_t cilong;
    lcp_options no;     /* options we've seen Naks for */
    lcp_options try_;       /* options to request next time */
    int looped_back = 0;
    int cilen;

    BZERO(&no, sizeof(no));
    try_ = *go;

    /*
     * Any Nak'd CIs must be in exactly the same order that we sent.
     * Check packet length and CI length at each step.
     * If we find any deviations, then this packet is bad.
     */
#define NAKCIVOID(opt, neg) \
    if (go->neg && \
    len >= CILEN_VOID && \
    p[1] == CILEN_VOID && \
    p[0] == opt) { \
    len -= CILEN_VOID; \
    INCPTR(CILEN_VOID, p); \
    no.neg = 1; \
    try_.neg = 0; \
    }
#if CHAP_SUPPORT
#define NAKCICHAP(opt, neg, code) \
    if (go->neg && \
    len >= CILEN_CHAP && \
    p[1] == CILEN_CHAP && \
    p[0] == opt) { \
    len -= CILEN_CHAP; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    GETCHAR(cichar, p); \
    no.neg = 1; \
    code \
    }
#endif /* CHAP_SUPPORT */
#define NAKCICHAR(opt, neg, code) \
    if (go->neg && \
    len >= CILEN_CHAR && \
    p[1] == CILEN_CHAR && \
    p[0] == opt) { \
    len -= CILEN_CHAR; \
    INCPTR(2, p); \
    GETCHAR(cichar, p); \
    no.neg = 1; \
    code \
    }
#define NAKCISHORT(opt, neg, code) \
    if (go->neg && \
    len >= CILEN_SHORT && \
    p[1] == CILEN_SHORT && \
    p[0] == opt) { \
    len -= CILEN_SHORT; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    no.neg = 1; \
    code \
    }
#define NAKCILONG(opt, neg, code) \
    if (go->neg && \
    len >= CILEN_LONG && \
    p[1] == CILEN_LONG && \
    p[0] == opt) { \
    len -= CILEN_LONG; \
    INCPTR(2, p); \
    GETLONG(cilong, p); \
    no.neg = 1; \
    code \
    }
#if LQR_SUPPORT
#define NAKCILQR(opt, neg, code) \
    if (go->neg && \
    len >= CILEN_LQR && \
    p[1] == CILEN_LQR && \
    p[0] == opt) { \
    len -= CILEN_LQR; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    GETLONG(cilong, p); \
    no.neg = 1; \
    code \
    }
#endif /* LQR_SUPPORT */
#define NAKCIENDP(opt, neg) \
    if (go->neg && \
    len >= CILEN_CHAR && \
    p[0] == opt && \
    p[1] >= CILEN_CHAR && \
    p[1] <= len) { \
    len -= p[1]; \
    INCPTR(p[1], p); \
    no.neg = 1; \
    try_.neg = 0; \
    }

    /*
     * NOTE!  There must be no assignments to individual fields of *go in
     * the code below.  Any such assignment is a BUG!
     */
    /*
     * We don't care if they want to send us smaller packets than
     * we want.  Therefore, accept any MRU less than what we asked for,
     * but then ignore the new value when setting the MRU in the kernel.
     * If they send us a bigger MRU than what we asked, accept it, up to
     * the limit of the default MRU we'd get if we didn't negotiate.
     */
    if (go->neg_mru && go->mru != PPP_DEFMRU) {
    NAKCISHORT(CI_MRU, neg_mru,
           if (cishort <= wo->mru || cishort <= PPP_DEFMRU)
               try_.mru = cishort;
           );
    }

    /*
     * Add any characters they want to our (receive-side) asyncmap.
     */
    if (go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) {
    NAKCILONG(CI_ASYNCMAP, neg_asyncmap,
          try_.asyncmap = go->asyncmap | cilong;
          );
    }

    /*
     * If they've nak'd our authentication-protocol, check whether
     * they are proposing a different protocol, or a different
     * hash algorithm for CHAP.
     */
    if ((0
#if CHAP_SUPPORT
        || go->neg_chap
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
        || go->neg_upap
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
        || go->neg_eap
#endif /* EAP_SUPPORT */
        )
    && len >= CILEN_SHORT
    && p[0] == CI_AUTHTYPE && p[1] >= CILEN_SHORT && p[1] <= len) {
    cilen = p[1];
    len -= cilen;
#if CHAP_SUPPORT
    no.neg_chap = go->neg_chap;
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
    no.neg_upap = go->neg_upap;
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
    no.neg_eap = go->neg_eap;
#endif /* EAP_SUPPORT */
    INCPTR(2, p);
    GETSHORT(cishort, p);

#if PAP_SUPPORT
    if (cishort == PPP_PAP && cilen == CILEN_SHORT) {
#if EAP_SUPPORT
        /* If we were asking for EAP, then we need to stop that. */
        if (go->neg_eap)
        try_.neg_eap = 0;
        else
#endif /* EAP_SUPPORT */

#if CHAP_SUPPORT
        /* If we were asking for CHAP, then we need to stop that. */
        if (go->neg_chap)
        try_.neg_chap = 0;
        else
#endif /* CHAP_SUPPORT */

        /*
         * If we weren't asking for CHAP or EAP, then we were asking for
         * PAP, in which case this Nak is bad.
         */
        goto bad;
    } else
#endif /* PAP_SUPPORT */

#if CHAP_SUPPORT
    if (cishort == PPP_CHAP && cilen == CILEN_CHAP) {
        GETCHAR(cichar, p);
#if EAP_SUPPORT
        /* Stop asking for EAP, if we were. */
        if (go->neg_eap) {
        try_.neg_eap = 0;
        /* Try to set up to use their suggestion, if possible */
        if (CHAP_CANDIGEST(go->chap_mdtype, cichar))
            try_.chap_mdtype = CHAP_MDTYPE_D(cichar);
        } else
#endif /* EAP_SUPPORT */
        if (go->neg_chap) {
        /*
         * We were asking for our preferred algorithm, they must
         * want something different.
         */
        if (cichar != CHAP_DIGEST(go->chap_mdtype)) {
            if (CHAP_CANDIGEST(go->chap_mdtype, cichar)) {
            /* Use their suggestion if we support it ... */
            try_.chap_mdtype = CHAP_MDTYPE_D(cichar);
            } else {
            /* ... otherwise, try our next-preferred algorithm. */
            try_.chap_mdtype &= ~(CHAP_MDTYPE(try_.chap_mdtype));
            if (try_.chap_mdtype == MDTYPE_NONE) /* out of algos */
                try_.neg_chap = 0;
            }
        } else {
            /*
             * Whoops, they Nak'd our algorithm of choice
             * but then suggested it back to us.
             */
            goto bad;
        }
        } else {
        /*
         * Stop asking for PAP if we were asking for it.
         */
#if PAP_SUPPORT
        try_.neg_upap = 0;
#endif /* PAP_SUPPORT */
        }

    } else
#endif /* CHAP_SUPPORT */
    {

#if EAP_SUPPORT
        /*
         * If we were asking for EAP, and they're Conf-Naking EAP,
         * well, that's just strange.  Nobody should do that.
         */
        if (cishort == PPP_EAP && cilen == CILEN_SHORT && go->neg_eap)
        ppp_dbglog("Unexpected Conf-Nak for EAP");

        /*
         * We don't recognize what they're suggesting.
         * Stop asking for what we were asking for.
         */
        if (go->neg_eap)
        try_.neg_eap = 0;
        else
#endif /* EAP_SUPPORT */

#if CHAP_SUPPORT
        if (go->neg_chap)
        try_.neg_chap = 0;
        else
#endif /* CHAP_SUPPORT */

#if PAP_SUPPORT
        if(1)
        try_.neg_upap = 0;
        else
#endif /* PAP_SUPPORT */
        {}

        p += cilen - CILEN_SHORT;
    }
    }

#if LQR_SUPPORT
    /*
     * If they can't cope with our link quality protocol, we'll have
     * to stop asking for LQR.  We haven't got any other protocol.
     * If they Nak the reporting period, take their value XXX ?
     */
    NAKCILQR(CI_QUALITY, neg_lqr,
         if (cishort != PPP_LQR)
         try_.neg_lqr = 0;
         else
         try_.lqr_period = cilong;
         );
#endif /* LQR_SUPPORT */

    /*
     * Only implementing CBCP...not the rest of the callback options
     */
    NAKCICHAR(CI_CALLBACK, neg_cbcp,
              try_.neg_cbcp = 0;
              (void)cichar; /* if CHAP support is not compiled, cichar is set but not used, which makes some compilers complaining */
              );

    /*
     * Check for a looped-back line.
     */
    NAKCILONG(CI_MAGICNUMBER, neg_magicnumber,
          try_.magicnumber = magic();
          looped_back = 1;
          );

    /*
     * Peer shouldn't send Nak for protocol compression or
     * address/control compression requests; they should send
     * a Reject instead.  If they send a Nak, treat it as a Reject.
     */
    NAKCIVOID(CI_PCOMPRESSION, neg_pcompression);
    NAKCIVOID(CI_ACCOMPRESSION, neg_accompression);

#ifdef HAVE_MULTILINK
    /*
     * Nak for MRRU option - accept their value if it is smaller
     * than the one we want.
     */
    if (go->neg_mrru) {
    NAKCISHORT(CI_MRRU, neg_mrru,
           if (treat_as_reject)
               try_.neg_mrru = 0;
           else if (cishort <= wo->mrru)
               try_.mrru = cishort;
           );
    }
#else /* HAVE_MULTILINK */
    LWIP_UNUSED_ARG(treat_as_reject);
#endif /* HAVE_MULTILINK */

    /*
     * Nak for short sequence numbers shouldn't be sent, treat it
     * like a reject.
     */
    NAKCIVOID(CI_SSNHF, neg_ssnhf);

    /*
     * Nak of the endpoint discriminator option is not permitted,
     * treat it like a reject.
     */
    NAKCIENDP(CI_EPDISC, neg_endpoint);

    /*
     * There may be remaining CIs, if the peer is requesting negotiation
     * on an option that we didn't include in our request packet.
     * If we see an option that we requested, or one we've already seen
     * in this packet, then this packet is bad.
     * If we wanted to respond by starting to negotiate on the requested
     * option(s), we could, but we don't, because except for the
     * authentication type and quality protocol, if we are not negotiating
     * an option, it is because we were told not to.
     * For the authentication type, the Nak from the peer means
     * `let me authenticate myself with you' which is a bit pointless.
     * For the quality protocol, the Nak means `ask me to send you quality
     * reports', but if we didn't ask for them, we don't want them.
     * An option we don't recognize represents the peer asking to
     * negotiate some option we don't support, so ignore it.
     */
    while (len >= CILEN_VOID) {
    GETCHAR(citype, p);
    GETCHAR(cilen, p);
    if (cilen < CILEN_VOID || (len -= cilen) < 0)
        goto bad;
    next = p + cilen - 2;

    switch (citype) {
    case CI_MRU:
        if ((go->neg_mru && go->mru != PPP_DEFMRU)
        || no.neg_mru || cilen != CILEN_SHORT)
        goto bad;
        GETSHORT(cishort, p);
        if (cishort < PPP_DEFMRU) {
        try_.neg_mru = 1;
        try_.mru = cishort;
        }
        break;
    case CI_ASYNCMAP:
        if ((go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF)
        || no.neg_asyncmap || cilen != CILEN_LONG)
        goto bad;
        break;
    case CI_AUTHTYPE:
        if (0
#if CHAP_SUPPORT
                || go->neg_chap || no.neg_chap
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
                || go->neg_upap || no.neg_upap
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
        || go->neg_eap || no.neg_eap
#endif /* EAP_SUPPORT */
        )
        goto bad;
        break;
    case CI_MAGICNUMBER:
        if (go->neg_magicnumber || no.neg_magicnumber ||
        cilen != CILEN_LONG)
        goto bad;
        break;
    case CI_PCOMPRESSION:
        if (go->neg_pcompression || no.neg_pcompression
        || cilen != CILEN_VOID)
        goto bad;
        break;
    case CI_ACCOMPRESSION:
        if (go->neg_accompression || no.neg_accompression
        || cilen != CILEN_VOID)
        goto bad;
        break;
#if LQR_SUPPORT
    case CI_QUALITY:
        if (go->neg_lqr || no.neg_lqr || cilen != CILEN_LQR)
        goto bad;
        break;
#endif /* LQR_SUPPORT */
#ifdef HAVE_MULTILINK
    case CI_MRRU:
        if (go->neg_mrru || no.neg_mrru || cilen != CILEN_SHORT)
        goto bad;
        break;
#endif /* HAVE_MULTILINK */
    case CI_SSNHF:
        if (go->neg_ssnhf || no.neg_ssnhf || cilen != CILEN_VOID)
        goto bad;
        try_.neg_ssnhf = 1;
        break;
    case CI_EPDISC:
        if (go->neg_endpoint || no.neg_endpoint || cilen < CILEN_CHAR)
        goto bad;
        break;
    default:
        break;
    }
    p = next;
    }

    /*
     * OK, the Nak is good.  Now we can update state.
     * If there are any options left we ignore them.
     */
    if (f->state != PPP_FSM_OPENED) {
    if (looped_back) {
        if (++try_.numloops >= pcb->settings.lcp_loopbackfail) {
        ppp_notice("Serial line is looped back.");
        pcb->err_code = PPPERR_LOOPBACK;
        lcp_close(f->pcb, "Loopback detected");
        }
    } else
        try_.numloops = 0;
    *go = try_;
    }

    return 1;

bad:
    LCPDEBUG(("lcp_nakci: received bad Nak!"));
    return 0;
}


/*
 * lcp_rejci - Peer has Rejected some of our CIs.
 * This should not modify any state if the Reject is bad
 * or if LCP is in the OPENED state.
 *
 * Returns:
 *  0 - Reject was bad.
 *  1 - Reject was good.
 */
static int lcp_rejci(fsm *f, u_char *p, int len) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;
    u_char cichar;
    u_short cishort;
    u32_t cilong;
    lcp_options try_;       /* options to request next time */

    try_ = *go;

    /*
     * Any Rejected CIs must be in exactly the same order that we sent.
     * Check packet length and CI length at each step.
     * If we find any deviations, then this packet is bad.
     */
#define REJCIVOID(opt, neg) \
    if (go->neg && \
    len >= CILEN_VOID && \
    p[1] == CILEN_VOID && \
    p[0] == opt) { \
    len -= CILEN_VOID; \
    INCPTR(CILEN_VOID, p); \
    try_.neg = 0; \
    }
#define REJCISHORT(opt, neg, val) \
    if (go->neg && \
    len >= CILEN_SHORT && \
    p[1] == CILEN_SHORT && \
    p[0] == opt) { \
    len -= CILEN_SHORT; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    /* Check rejected value. */ \
    if (cishort != val) \
        goto bad; \
    try_.neg = 0; \
    }

#if CHAP_SUPPORT && EAP_SUPPORT && PAP_SUPPORT
#define REJCICHAP(opt, neg, val) \
    if (go->neg && \
    len >= CILEN_CHAP && \
    p[1] == CILEN_CHAP && \
    p[0] == opt) { \
    len -= CILEN_CHAP; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    GETCHAR(cichar, p); \
    /* Check rejected value. */ \
    if ((cishort != PPP_CHAP) || (cichar != (CHAP_DIGEST(val)))) \
        goto bad; \
    try_.neg = 0; \
    try_.neg_eap = try_.neg_upap = 0; \
    }
#endif /* CHAP_SUPPORT && EAP_SUPPORT && PAP_SUPPORT */

#if CHAP_SUPPORT && !EAP_SUPPORT && PAP_SUPPORT
#define REJCICHAP(opt, neg, val) \
    if (go->neg && \
    len >= CILEN_CHAP && \
    p[1] == CILEN_CHAP && \
    p[0] == opt) { \
    len -= CILEN_CHAP; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    GETCHAR(cichar, p); \
    /* Check rejected value. */ \
    if ((cishort != PPP_CHAP) || (cichar != (CHAP_DIGEST(val)))) \
        goto bad; \
    try_.neg = 0; \
    try_.neg_upap = 0; \
    }
#endif /* CHAP_SUPPORT && !EAP_SUPPORT && PAP_SUPPORT */

#if CHAP_SUPPORT && EAP_SUPPORT && !PAP_SUPPORT
#define REJCICHAP(opt, neg, val) \
    if (go->neg && \
    len >= CILEN_CHAP && \
    p[1] == CILEN_CHAP && \
    p[0] == opt) { \
    len -= CILEN_CHAP; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    GETCHAR(cichar, p); \
    /* Check rejected value. */ \
    if ((cishort != PPP_CHAP) || (cichar != (CHAP_DIGEST(val)))) \
        goto bad; \
    try_.neg = 0; \
    try_.neg_eap = 0; \
    }
#endif /* CHAP_SUPPORT && EAP_SUPPORT && !PAP_SUPPORT */

#if CHAP_SUPPORT && !EAP_SUPPORT && !PAP_SUPPORT
#define REJCICHAP(opt, neg, val) \
    if (go->neg && \
    len >= CILEN_CHAP && \
    p[1] == CILEN_CHAP && \
    p[0] == opt) { \
    len -= CILEN_CHAP; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    GETCHAR(cichar, p); \
    /* Check rejected value. */ \
    if ((cishort != PPP_CHAP) || (cichar != (CHAP_DIGEST(val)))) \
        goto bad; \
    try_.neg = 0; \
    }
#endif /* CHAP_SUPPORT && !EAP_SUPPORT && !PAP_SUPPORT */

#define REJCILONG(opt, neg, val) \
    if (go->neg && \
    len >= CILEN_LONG && \
    p[1] == CILEN_LONG && \
    p[0] == opt) { \
    len -= CILEN_LONG; \
    INCPTR(2, p); \
    GETLONG(cilong, p); \
    /* Check rejected value. */ \
    if (cilong != val) \
        goto bad; \
    try_.neg = 0; \
    }
#if LQR_SUPPORT
#define REJCILQR(opt, neg, val) \
    if (go->neg && \
    len >= CILEN_LQR && \
    p[1] == CILEN_LQR && \
    p[0] == opt) { \
    len -= CILEN_LQR; \
    INCPTR(2, p); \
    GETSHORT(cishort, p); \
    GETLONG(cilong, p); \
    /* Check rejected value. */ \
    if (cishort != PPP_LQR || cilong != val) \
        goto bad; \
    try_.neg = 0; \
    }
#endif /* LQR_SUPPORT */
#define REJCICBCP(opt, neg, val) \
    if (go->neg && \
    len >= CILEN_CBCP && \
    p[1] == CILEN_CBCP && \
    p[0] == opt) { \
    len -= CILEN_CBCP; \
    INCPTR(2, p); \
    GETCHAR(cichar, p); \
    /* Check rejected value. */ \
    if (cichar != val) \
        goto bad; \
    try_.neg = 0; \
    }
#define REJCIENDP(opt, neg, class, val, vlen) \
    if (go->neg && \
    len >= CILEN_CHAR + vlen && \
    p[0] == opt && \
    p[1] == CILEN_CHAR + vlen) { \
    int i; \
    len -= CILEN_CHAR + vlen; \
    INCPTR(2, p); \
    GETCHAR(cichar, p); \
    if (cichar != class) \
        goto bad; \
    for (i = 0; i < vlen; ++i) { \
        GETCHAR(cichar, p); \
        if (cichar != val[i]) \
        goto bad; \
    } \
    try_.neg = 0; \
    }

    REJCISHORT(CI_MRU, neg_mru, go->mru);
    REJCILONG(CI_ASYNCMAP, neg_asyncmap, go->asyncmap);
#if EAP_SUPPORT
    REJCISHORT(CI_AUTHTYPE, neg_eap, PPP_EAP);
    if (!go->neg_eap) {
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT
    REJCICHAP(CI_AUTHTYPE, neg_chap, go->chap_mdtype);
    if (!go->neg_chap) {
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
        REJCISHORT(CI_AUTHTYPE, neg_upap, PPP_PAP);
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
    }
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
    }
#endif /* EAP_SUPPORT */
#if LQR_SUPPORT
    REJCILQR(CI_QUALITY, neg_lqr, go->lqr_period);
#endif /* LQR_SUPPORT */
    REJCICBCP(CI_CALLBACK, neg_cbcp, CBCP_OPT);
    REJCILONG(CI_MAGICNUMBER, neg_magicnumber, go->magicnumber);
    REJCIVOID(CI_PCOMPRESSION, neg_pcompression);
    REJCIVOID(CI_ACCOMPRESSION, neg_accompression);
#ifdef HAVE_MULTILINK
    REJCISHORT(CI_MRRU, neg_mrru, go->mrru);
#endif /* HAVE_MULTILINK */
    REJCIVOID(CI_SSNHF, neg_ssnhf);
    REJCIENDP(CI_EPDISC, neg_endpoint, go->endpoint.class_,
          go->endpoint.value, go->endpoint.length);

    /*
     * If there are any remaining CIs, then this packet is bad.
     */
    if (len != 0)
    goto bad;
    /*
     * Now we can update state.
     */
    if (f->state != PPP_FSM_OPENED)
    *go = try_;
    return 1;

bad:
    LCPDEBUG(("lcp_rejci: received bad Reject!"));
    return 0;
}


/*
 * lcp_reqci - Check the peer's requested CIs and send appropriate response.
 *
 * Returns: CONFACK, CONFNAK or CONFREJ and input packet modified
 * appropriately.  If reject_if_disagree is non-zero, doesn't return
 * CONFNAK; returns CONFREJ if it can't return CONFACK.
 *
 * inp = Requested CIs
 * lenp = Length of requested CIs
 */
static int lcp_reqci(fsm *f, u_char *inp, int *lenp, int reject_if_disagree) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;
    lcp_options *ho = &pcb->lcp_hisoptions;
    lcp_options *ao = &pcb->lcp_allowoptions;
    u_char *cip, *next;     /* Pointer to current and next CIs */
    int cilen, citype, cichar;  /* Parsed len, type, char value */
    u_short cishort;        /* Parsed short value */
    u32_t cilong;       /* Parse long value */
    int rc = CONFACK;       /* Final packet return code */
    int orc;            /* Individual option return code */
    u_char *p;          /* Pointer to next char to parse */
    u_char *rejp;       /* Pointer to next char in reject frame */
    struct pbuf *nakp;          /* Nak buffer */
    u_char *nakoutp;        /* Pointer to next char in Nak frame */
    int l = *lenp;      /* Length left */

    /*
     * Reset all his options.
     */
    BZERO(ho, sizeof(*ho));

    /*
     * Process all his options.
     */
    next = inp;
    nakp = pbuf_alloc(PBUF_RAW, (u16_t)(PPP_CTRL_PBUF_MAX_SIZE), PPP_CTRL_PBUF_TYPE);
    if(NULL == nakp)
        return 0;
    if(nakp->tot_len != nakp->len) {
        pbuf_free(nakp);
        return 0;
    }

    nakoutp = (u_char*)nakp->payload;
    rejp = inp;
    while (l) {
    orc = CONFACK;          /* Assume success */
    cip = p = next;         /* Remember begining of CI */
    if (l < 2 ||            /* Not enough data for CI header or */
        p[1] < 2 ||         /*  CI length too small or */
        p[1] > l) {         /*  CI length too big? */
        LCPDEBUG(("lcp_reqci: bad CI length!"));
        orc = CONFREJ;      /* Reject bad CI */
        cilen = l;          /* Reject till end of packet */
        l = 0;          /* Don't loop again */
        citype = 0;
        goto endswitch;
    }
    GETCHAR(citype, p);     /* Parse CI type */
    GETCHAR(cilen, p);      /* Parse CI length */
    l -= cilen;         /* Adjust remaining length */
    next += cilen;          /* Step to next CI */

    switch (citype) {       /* Check CI type */
    case CI_MRU:
        if (!ao->neg_mru ||     /* Allow option? */
        cilen != CILEN_SHORT) { /* Check CI length */
        orc = CONFREJ;      /* Reject CI */
        break;
        }
        GETSHORT(cishort, p);   /* Parse MRU */

        /*
         * He must be able to receive at least our minimum.
         * No need to check a maximum.  If he sends a large number,
         * we'll just ignore it.
         */
        if (cishort < PPP_MINMRU) {
        orc = CONFNAK;      /* Nak CI */
        PUTCHAR(CI_MRU, nakoutp);
        PUTCHAR(CILEN_SHORT, nakoutp);
        PUTSHORT(PPP_MINMRU, nakoutp);  /* Give him a hint */
        break;
        }
        ho->neg_mru = 1;        /* Remember he sent MRU */
        ho->mru = cishort;      /* And remember value */
        break;

    case CI_ASYNCMAP:
        if (!ao->neg_asyncmap ||
        cilen != CILEN_LONG) {
        orc = CONFREJ;
        break;
        }
        GETLONG(cilong, p);

        /*
         * Asyncmap must have set at least the bits
         * which are set in lcp_allowoptions[unit].asyncmap.
         */
        if ((ao->asyncmap & ~cilong) != 0) {
        orc = CONFNAK;
        PUTCHAR(CI_ASYNCMAP, nakoutp);
        PUTCHAR(CILEN_LONG, nakoutp);
        PUTLONG(ao->asyncmap | cilong, nakoutp);
        break;
        }
        ho->neg_asyncmap = 1;
        ho->asyncmap = cilong;
        break;

    case CI_AUTHTYPE:
        if (cilen < CILEN_SHORT ||
        !(0
#if PAP_SUPPORT
        || ao->neg_upap
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
        || ao->neg_chap
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
        || ao->neg_eap
#endif /* EAP_SUPPORT */
        )) {
        /*
         * Reject the option if we're not willing to authenticate.
         */
        ppp_dbglog("No auth is possible");
        orc = CONFREJ;
        break;
        }
        GETSHORT(cishort, p);

        /*
         * Authtype must be PAP, CHAP, or EAP.
         *
         * Note: if more than one of ao->neg_upap, ao->neg_chap, and
         * ao->neg_eap are set, and the peer sends a Configure-Request
         * with two or more authenticate-protocol requests, then we will
         * reject the second request.
         * Whether we end up doing CHAP, UPAP, or EAP depends then on
         * the ordering of the CIs in the peer's Configure-Request.
             */

#if PAP_SUPPORT
        if (cishort == PPP_PAP) {
        /* we've already accepted CHAP or EAP */
        if (0
#if CHAP_SUPPORT
            || ho->neg_chap
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
            || ho->neg_eap
#endif /* EAP_SUPPORT */
            || cilen != CILEN_SHORT) {
            LCPDEBUG(("lcp_reqci: rcvd AUTHTYPE PAP, rejecting..."));
            orc = CONFREJ;
            break;
        }
        if (!ao->neg_upap) {    /* we don't want to do PAP */
            orc = CONFNAK;  /* NAK it and suggest CHAP or EAP */
            PUTCHAR(CI_AUTHTYPE, nakoutp);
#if EAP_SUPPORT
            if (ao->neg_eap) {
            PUTCHAR(CILEN_SHORT, nakoutp);
            PUTSHORT(PPP_EAP, nakoutp);
            } else {
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT
            PUTCHAR(CILEN_CHAP, nakoutp);
            PUTSHORT(PPP_CHAP, nakoutp);
            PUTCHAR(CHAP_DIGEST(ao->chap_mdtype), nakoutp);
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
            }
#endif /* EAP_SUPPORT */
            break;
        }
        ho->neg_upap = 1;
        break;
        }
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
        if (cishort == PPP_CHAP) {
        /* we've already accepted PAP or EAP */
        if (
#if PAP_SUPPORT
            ho->neg_upap ||
#endif /* PAP_SUPPORT */
#if EAP_SUPPORT
            ho->neg_eap ||
#endif /* EAP_SUPPORT */
            cilen != CILEN_CHAP) {
            LCPDEBUG(("lcp_reqci: rcvd AUTHTYPE CHAP, rejecting..."));
            orc = CONFREJ;
            break;
        }
        if (!ao->neg_chap) {    /* we don't want to do CHAP */
            orc = CONFNAK;  /* NAK it and suggest EAP or PAP */
            PUTCHAR(CI_AUTHTYPE, nakoutp);
            PUTCHAR(CILEN_SHORT, nakoutp);
#if EAP_SUPPORT
            if (ao->neg_eap) {
            PUTSHORT(PPP_EAP, nakoutp);
            } else
#endif /* EAP_SUPPORT */
#if PAP_SUPPORT
            if(1) {
            PUTSHORT(PPP_PAP, nakoutp);
            }
            else
#endif /* PAP_SUPPORT */
            {}
            break;
        }
        GETCHAR(cichar, p); /* get digest type */
        if (!(CHAP_CANDIGEST(ao->chap_mdtype, cichar))) {
            /*
             * We can't/won't do the requested type,
             * suggest something else.
             */
            orc = CONFNAK;
            PUTCHAR(CI_AUTHTYPE, nakoutp);
            PUTCHAR(CILEN_CHAP, nakoutp);
            PUTSHORT(PPP_CHAP, nakoutp);
            PUTCHAR(CHAP_DIGEST(ao->chap_mdtype), nakoutp);
            break;
        }
        ho->chap_mdtype = CHAP_MDTYPE_D(cichar); /* save md type */
        ho->neg_chap = 1;
        break;
        }
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
        if (cishort == PPP_EAP) {
        /* we've already accepted CHAP or PAP */
        if (
#if CHAP_SUPPORT
            ho->neg_chap ||
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
            ho->neg_upap ||
#endif /* PAP_SUPPORT */
            cilen != CILEN_SHORT) {
            LCPDEBUG(("lcp_reqci: rcvd AUTHTYPE EAP, rejecting..."));
            orc = CONFREJ;
            break;
        }
        if (!ao->neg_eap) { /* we don't want to do EAP */
            orc = CONFNAK;  /* NAK it and suggest CHAP or PAP */
            PUTCHAR(CI_AUTHTYPE, nakoutp);
#if CHAP_SUPPORT
            if (ao->neg_chap) {
            PUTCHAR(CILEN_CHAP, nakoutp);
            PUTSHORT(PPP_CHAP, nakoutp);
            PUTCHAR(CHAP_DIGEST(ao->chap_mdtype), nakoutp);
            } else
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
            if(1) {
            PUTCHAR(CILEN_SHORT, nakoutp);
            PUTSHORT(PPP_PAP, nakoutp);
            } else
#endif /* PAP_SUPPORT */
            {}
            break;
        }
        ho->neg_eap = 1;
        break;
        }
#endif /* EAP_SUPPORT */

        /*
         * We don't recognize the protocol they're asking for.
         * Nak it with something we're willing to do.
         * (At this point we know ao->neg_upap || ao->neg_chap ||
         * ao->neg_eap.)
         */
        orc = CONFNAK;
        PUTCHAR(CI_AUTHTYPE, nakoutp);

#if EAP_SUPPORT
        if (ao->neg_eap) {
        PUTCHAR(CILEN_SHORT, nakoutp);
        PUTSHORT(PPP_EAP, nakoutp);
        } else
#endif /* EAP_SUPPORT */
#if CHAP_SUPPORT
        if (ao->neg_chap) {
        PUTCHAR(CILEN_CHAP, nakoutp);
        PUTSHORT(PPP_CHAP, nakoutp);
        PUTCHAR(CHAP_DIGEST(ao->chap_mdtype), nakoutp);
        } else
#endif /* CHAP_SUPPORT */
#if PAP_SUPPORT
        if(1) {
        PUTCHAR(CILEN_SHORT, nakoutp);
        PUTSHORT(PPP_PAP, nakoutp);
        } else
#endif /* PAP_SUPPORT */
        {}
        break;

#if LQR_SUPPORT
    case CI_QUALITY:
        if (!ao->neg_lqr ||
        cilen != CILEN_LQR) {
        orc = CONFREJ;
        break;
        }

        GETSHORT(cishort, p);
        GETLONG(cilong, p);

        /*
         * Check the protocol and the reporting period.
         * XXX When should we Nak this, and what with?
         */
        if (cishort != PPP_LQR) {
        orc = CONFNAK;
        PUTCHAR(CI_QUALITY, nakoutp);
        PUTCHAR(CILEN_LQR, nakoutp);
        PUTSHORT(PPP_LQR, nakoutp);
        PUTLONG(ao->lqr_period, nakoutp);
        break;
        }
        break;
#endif /* LQR_SUPPORT */

    case CI_MAGICNUMBER:
        if (!(ao->neg_magicnumber || go->neg_magicnumber) ||
        cilen != CILEN_LONG) {
        orc = CONFREJ;
        break;
        }
        GETLONG(cilong, p);

        /*
         * He must have a different magic number.
         */
        if (go->neg_magicnumber &&
        cilong == go->magicnumber) {
        cilong = magic();   /* Don't put magic() inside macro! */
        orc = CONFNAK;
        PUTCHAR(CI_MAGICNUMBER, nakoutp);
        PUTCHAR(CILEN_LONG, nakoutp);
        PUTLONG(cilong, nakoutp);
        break;
        }
        ho->neg_magicnumber = 1;
        ho->magicnumber = cilong;
        break;


    case CI_PCOMPRESSION:
        if (!ao->neg_pcompression ||
        cilen != CILEN_VOID) {
        orc = CONFREJ;
        break;
        }
        ho->neg_pcompression = 1;
        break;

    case CI_ACCOMPRESSION:
        if (!ao->neg_accompression ||
        cilen != CILEN_VOID) {
        orc = CONFREJ;
        break;
        }
        ho->neg_accompression = 1;
        break;

#ifdef HAVE_MULTILINK
    case CI_MRRU:
        if (!ao->neg_mrru
        || !multilink
        || cilen != CILEN_SHORT) {
        orc = CONFREJ;
        break;
        }

        GETSHORT(cishort, p);
        /* possibly should insist on a minimum/maximum MRRU here */
        ho->neg_mrru = 1;
        ho->mrru = cishort;
        break;
#endif /* HAVE_MULTILINK */

    case CI_SSNHF:
        if (!ao->neg_ssnhf
#ifdef HAVE_MULTILINK
        || !multilink
#endif /* HAVE_MULTILINK */
        || cilen != CILEN_VOID) {
        orc = CONFREJ;
        break;
        }
        ho->neg_ssnhf = 1;
        break;

    case CI_EPDISC:
        if (!ao->neg_endpoint ||
        cilen < CILEN_CHAR ||
        cilen > CILEN_CHAR + MAX_ENDP_LEN) {
        orc = CONFREJ;
        break;
        }
        GETCHAR(cichar, p);
        cilen -= CILEN_CHAR;
        ho->neg_endpoint = 1;
        ho->endpoint.class_ = cichar;
        ho->endpoint.length = cilen;
        MEMCPY(ho->endpoint.value, p, cilen);
        INCPTR(cilen, p);
        break;

    default:
        LCPDEBUG(("lcp_reqci: rcvd unknown option %d", citype));
        orc = CONFREJ;
        break;
    }

endswitch:
    if (orc == CONFACK &&       /* Good CI */
        rc != CONFACK)      /*  but prior CI wasnt? */
        continue;           /* Don't send this one */

    if (orc == CONFNAK) {       /* Nak this CI? */
        if (reject_if_disagree  /* Getting fed up with sending NAKs? */
        && citype != CI_MAGICNUMBER) {
        orc = CONFREJ;      /* Get tough if so */
        } else {
        if (rc == CONFREJ)  /* Rejecting prior CI? */
            continue;       /* Don't send this one */
        rc = CONFNAK;
        }
    }
    if (orc == CONFREJ) {       /* Reject this CI */
        rc = CONFREJ;
        if (cip != rejp)        /* Need to move rejected CI? */
        MEMCPY(rejp, cip, cilen); /* Move it */
        INCPTR(cilen, rejp);    /* Update output pointer */
    }
    }

    /*
     * If we wanted to send additional NAKs (for unsent CIs), the
     * code would go here.  The extra NAKs would go at *nakoutp.
     * At present there are no cases where we want to ask the
     * peer to negotiate an option.
     */

    switch (rc) {
    case CONFACK:
    *lenp = next - inp;
    break;
    case CONFNAK:
    /*
     * Copy the Nak'd options from the nak buffer to the caller's buffer.
     */
    *lenp = nakoutp - (u_char*)nakp->payload;
    MEMCPY(inp, nakp->payload, *lenp);
    break;
    case CONFREJ:
    *lenp = rejp - inp;
    break;
    default:
    break;
    }

    pbuf_free(nakp);
    LCPDEBUG(("lcp_reqci: returning CONF%s.", CODENAME(rc)));
    return (rc);            /* Return final code */
}


/*
 * lcp_up - LCP has come UP.
 */
static void lcp_up(fsm *f) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *wo = &pcb->lcp_wantoptions;
    lcp_options *ho = &pcb->lcp_hisoptions;
    lcp_options *go = &pcb->lcp_gotoptions;
    lcp_options *ao = &pcb->lcp_allowoptions;
    int mtu, mru;

    if (!go->neg_magicnumber)
    go->magicnumber = 0;
    if (!ho->neg_magicnumber)
    ho->magicnumber = 0;

    /*
     * Set our MTU to the smaller of the MTU we wanted and
     * the MRU our peer wanted.  If we negotiated an MRU,
     * set our MRU to the larger of value we wanted and
     * the value we got in the negotiation.
     * Note on the MTU: the link MTU can be the MRU the peer wanted,
     * the interface MTU is set to the lowest of that, the
     * MTU we want to use, and our link MRU.
     */
    mtu = ho->neg_mru? ho->mru: PPP_MRU;
    mru = go->neg_mru? LWIP_MAX(wo->mru, go->mru): PPP_MRU;
#ifdef HAVE_MULTILINK
    if (!(multilink && go->neg_mrru && ho->neg_mrru))
#endif /* HAVE_MULTILINK */
    netif_set_mtu(pcb, LWIP_MIN(LWIP_MIN(mtu, mru), ao->mru));
    ppp_send_config(pcb, mtu,
            (ho->neg_asyncmap? ho->asyncmap: 0xffffffff),
            ho->neg_pcompression, ho->neg_accompression);
    ppp_recv_config(pcb, mru,
            (pcb->settings.lax_recv? 0: go->neg_asyncmap? go->asyncmap: 0xffffffff),
            go->neg_pcompression, go->neg_accompression);

    if (ho->neg_mru)
    pcb->peer_mru = ho->mru;

    lcp_echo_lowerup(f->pcb);  /* Enable echo messages */

    link_established(pcb);
}


/*
 * lcp_down - LCP has gone DOWN.
 *
 * Alert other protocols.
 */
static void lcp_down(fsm *f) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;

    lcp_echo_lowerdown(f->pcb);

    link_down(pcb);

    ppp_send_config(pcb, PPP_MRU, 0xffffffff, 0, 0);
    ppp_recv_config(pcb, PPP_MRU,
            (go->neg_asyncmap? go->asyncmap: 0xffffffff),
            go->neg_pcompression, go->neg_accompression);
    pcb->peer_mru = PPP_MRU;
}


/*
 * lcp_starting - LCP needs the lower layer up.
 */
static void lcp_starting(fsm *f) {
    ppp_pcb *pcb = f->pcb;
    link_required(pcb);
}


/*
 * lcp_finished - LCP has finished with the lower layer.
 */
static void lcp_finished(fsm *f) {
    ppp_pcb *pcb = f->pcb;
    link_terminated(pcb);
}


#if PRINTPKT_SUPPORT
/*
 * lcp_printpkt - print the contents of an LCP packet.
 */
static const char* const lcp_codenames[] = {
    "ConfReq", "ConfAck", "ConfNak", "ConfRej",
    "TermReq", "TermAck", "CodeRej", "ProtRej",
    "EchoReq", "EchoRep", "DiscReq", "Ident",
    "TimeRem"
};

static int lcp_printpkt(const u_char *p, int plen,
        void (*printer) (void *, const char *, ...), void *arg) {
    int code, id, len, olen, i;
    const u_char *pstart, *optend;
    u_short cishort;
    u32_t cilong;

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

   if (code >= 1 && code <= (int)LWIP_ARRAYSIZE(lcp_codenames))
    printer(arg, " %s", lcp_codenames[code-1]);
    else
    printer(arg, " code=0x%x", code);
    printer(arg, " id=0x%x", id);
    len -= HEADERLEN;
    switch (code) {
    case CONFREQ:
    case CONFACK:
    case CONFNAK:
    case CONFREJ:
    /* print option list */
    while (len >= 2) {
        GETCHAR(code, p);
        GETCHAR(olen, p);
        p -= 2;
        if (olen < 2 || olen > len) {
        break;
        }
        printer(arg, " <");
        len -= olen;
        optend = p + olen;
        switch (code) {
        case CI_MRU:
        if (olen == CILEN_SHORT) {
            p += 2;
            GETSHORT(cishort, p);
            printer(arg, "mru %d", cishort);
        }
        break;
        case CI_ASYNCMAP:
        if (olen == CILEN_LONG) {
            p += 2;
            GETLONG(cilong, p);
            printer(arg, "asyncmap 0x%x", cilong);
        }
        break;
        case CI_AUTHTYPE:
        if (olen >= CILEN_SHORT) {
            p += 2;
            printer(arg, "auth ");
            GETSHORT(cishort, p);
            switch (cishort) {
#if PAP_SUPPORT
            case PPP_PAP:
            printer(arg, "pap");
            break;
#endif /* PAP_SUPPORT */
#if CHAP_SUPPORT
            case PPP_CHAP:
            printer(arg, "chap");
            if (p < optend) {
                switch (*p) {
                case CHAP_MD5:
                printer(arg, " MD5");
                ++p;
                break;
#if MSCHAP_SUPPORT
                case CHAP_MICROSOFT:
                printer(arg, " MS");
                ++p;
                break;

                case CHAP_MICROSOFT_V2:
                printer(arg, " MS-v2");
                ++p;
                break;
#endif /* MSCHAP_SUPPORT */
                default:
                break;
                }
            }
            break;
#endif /* CHAP_SUPPORT */
#if EAP_SUPPORT
            case PPP_EAP:
            printer(arg, "eap");
            break;
#endif /* EAP_SUPPORT */
            default:
            printer(arg, "0x%x", cishort);
            }
        }
        break;
#if LQR_SUPPORT
        case CI_QUALITY:
        if (olen >= CILEN_SHORT) {
            p += 2;
            printer(arg, "quality ");
            GETSHORT(cishort, p);
            switch (cishort) {
            case PPP_LQR:
            printer(arg, "lqr");
            break;
            default:
            printer(arg, "0x%x", cishort);
            }
        }
        break;
#endif /* LQR_SUPPORT */
        case CI_CALLBACK:
        if (olen >= CILEN_CHAR) {
            p += 2;
            printer(arg, "callback ");
            GETCHAR(cishort, p);
            switch (cishort) {
            case CBCP_OPT:
            printer(arg, "CBCP");
            break;
            default:
            printer(arg, "0x%x", cishort);
            }
        }
        break;
        case CI_MAGICNUMBER:
        if (olen == CILEN_LONG) {
            p += 2;
            GETLONG(cilong, p);
            printer(arg, "magic 0x%x", cilong);
        }
        break;
        case CI_PCOMPRESSION:
        if (olen == CILEN_VOID) {
            p += 2;
            printer(arg, "pcomp");
        }
        break;
        case CI_ACCOMPRESSION:
        if (olen == CILEN_VOID) {
            p += 2;
            printer(arg, "accomp");
        }
        break;
        case CI_MRRU:
        if (olen == CILEN_SHORT) {
            p += 2;
            GETSHORT(cishort, p);
            printer(arg, "mrru %d", cishort);
        }
        break;
        case CI_SSNHF:
        if (olen == CILEN_VOID) {
            p += 2;
            printer(arg, "ssnhf");
        }
        break;
        case CI_EPDISC:
#ifdef HAVE_MULTILINK
        if (olen >= CILEN_CHAR) {
            struct epdisc epd;
            p += 2;
            GETCHAR(epd.class, p);
            epd.length = olen - CILEN_CHAR;
            if (epd.length > MAX_ENDP_LEN)
            epd.length = MAX_ENDP_LEN;
            if (epd.length > 0) {
            MEMCPY(epd.value, p, epd.length);
            p += epd.length;
            }
            printer(arg, "endpoint [%s]", epdisc_to_str(&epd));
        }
#else
        printer(arg, "endpoint");
#endif
        break;
        default:
        break;
        }
        while (p < optend) {
        GETCHAR(code, p);
        printer(arg, " %.2x", code);
        }
        printer(arg, ">");
    }
    break;

    case TERMACK:
    case TERMREQ:
    if (len > 0 && *p >= ' ' && *p < 0x7f) {
        printer(arg, " ");
        ppp_print_string(p, len, printer, arg);
        p += len;
        len = 0;
    }
    break;

    case ECHOREQ:
    case ECHOREP:
    case DISCREQ:
    if (len >= 4) {
        GETLONG(cilong, p);
        printer(arg, " magic=0x%x", cilong);
        len -= 4;
    }
    break;

    case IDENTIF:
    case TIMEREM:
    if (len >= 4) {
        GETLONG(cilong, p);
        printer(arg, " magic=0x%x", cilong);
        len -= 4;
    }
    if (code == TIMEREM) {
        if (len < 4)
        break;
        GETLONG(cilong, p);
        printer(arg, " seconds=%u", cilong);
        len -= 4;
    }
    if (len > 0) {
        printer(arg, " ");
        ppp_print_string(p, len, printer, arg);
        p += len;
        len = 0;
    }
    break;
    default:
    break;
    }

    /* print the rest of the bytes in the packet */
    for (i = 0; i < len && i < 32; ++i) {
    GETCHAR(code, p);
    printer(arg, " %.2x", code);
    }
    if (i < len) {
    printer(arg, " ...");
    p += len - i;
    }

    return p - pstart;
}
#endif /* PRINTPKT_SUPPORT */

/*
 * Time to shut down the link because there is nothing out there.
 */

static void LcpLinkFailure(fsm *f) {
    ppp_pcb *pcb = f->pcb;
    if (f->state == PPP_FSM_OPENED) {
    ppp_info("No response to %d echo-requests", pcb->lcp_echos_pending);
        ppp_notice("Serial link appears to be disconnected.");
    pcb->err_code = PPPERR_PEERDEAD;
    lcp_close(pcb, "Peer not responding");
    }
}

/*
 * Timer expired for the LCP echo requests from this process.
 */

static void LcpEchoCheck(fsm *f) {
    ppp_pcb *pcb = f->pcb;

    LcpSendEchoRequest (f);
    if (f->state != PPP_FSM_OPENED)
    return;

    /*
     * Start the timer for the next interval.
     */
    if (pcb->lcp_echo_timer_running)
    ppp_warn("assertion lcp_echo_timer_running==0 failed");
    TIMEOUT (LcpEchoTimeout, f, pcb->settings.lcp_echo_interval);
    pcb->lcp_echo_timer_running = 1;
}

/*
 * LcpEchoTimeout - Timer expired on the LCP echo
 */

static void LcpEchoTimeout(void *arg) {
    fsm *f = (fsm*)arg;
    ppp_pcb *pcb = f->pcb;
    if (pcb->lcp_echo_timer_running != 0) {
        pcb->lcp_echo_timer_running = 0;
        LcpEchoCheck ((fsm *) arg);
    }
}

/*
 * LcpEchoReply - LCP has received a reply to the echo
 */

static void lcp_received_echo_reply(fsm *f, int id, u_char *inp, int len) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;
    u32_t magic_val;
    LWIP_UNUSED_ARG(id);

    /* Check the magic number - don't count replies from ourselves. */
    if (len < 4) {
    ppp_dbglog("lcp: received short Echo-Reply, length %d", len);
    return;
    }
    GETLONG(magic_val, inp);
    if (go->neg_magicnumber
    && magic_val == go->magicnumber) {
    ppp_warn("appear to have received our own echo-reply!");
    return;
    }

    /* Reset the number of outstanding echo frames */
    pcb->lcp_echos_pending = 0;
}

/*
 * LcpSendEchoRequest - Send an echo request frame to the peer
 */

static void LcpSendEchoRequest(fsm *f) {
    ppp_pcb *pcb = f->pcb;
    lcp_options *go = &pcb->lcp_gotoptions;
    u32_t lcp_magic;
    u_char pkt[4], *pktp;

    /*
     * Detect the failure of the peer at this point.
     */
    if (pcb->settings.lcp_echo_fails != 0) {
        if (pcb->lcp_echos_pending >= pcb->settings.lcp_echo_fails) {
            LcpLinkFailure(f);
            pcb->lcp_echos_pending = 0;
    }
    }

#if PPP_LCP_ADAPTIVE
    /*
     * If adaptive echos have been enabled, only send the echo request if
     * no traffic was received since the last one.
     */
    if (pcb->settings.lcp_echo_adaptive) {
    static unsigned int last_pkts_in = 0;

#if PPP_STATS_SUPPORT
    update_link_stats(f->unit);
    link_stats_valid = 0;
#endif /* PPP_STATS_SUPPORT */

    if (link_stats.pkts_in != last_pkts_in) {
        last_pkts_in = link_stats.pkts_in;
        return;
    }
    }
#endif

    /*
     * Make and send the echo request frame.
     */
    if (f->state == PPP_FSM_OPENED) {
        lcp_magic = go->magicnumber;
    pktp = pkt;
    PUTLONG(lcp_magic, pktp);
        fsm_sdata(f, ECHOREQ, pcb->lcp_echo_number++, pkt, pktp - pkt);
    ++pcb->lcp_echos_pending;
    }
}

/*
 * lcp_echo_lowerup - Start the timer for the LCP frame
 */

static void lcp_echo_lowerup(ppp_pcb *pcb) {
    fsm *f = &pcb->lcp_fsm;

    /* Clear the parameters for generating echo frames */
    pcb->lcp_echos_pending      = 0;
    pcb->lcp_echo_number        = 0;
    pcb->lcp_echo_timer_running = 0;
  
    /* If a timeout interval is specified then start the timer */
    if (pcb->settings.lcp_echo_interval != 0)
        LcpEchoCheck (f);
}

/*
 * lcp_echo_lowerdown - Stop the timer for the LCP frame
 */

static void lcp_echo_lowerdown(ppp_pcb *pcb) {
    fsm *f = &pcb->lcp_fsm;

    if (pcb->lcp_echo_timer_running != 0) {
        UNTIMEOUT (LcpEchoTimeout, f);
        pcb->lcp_echo_timer_running = 0;
    }
}

#endif /* PPP_SUPPORT */
