/*
 *  PPPLCP.C	-- negotiate data link options
 *
 *	REVISIONS:
 *	12-89	-- Katie Stevens (dkstevens@ucdavis.edu)
 *		   UC Davis, Computing Services
 *	PPP.01	01-90	[ks] must echo requested magic# in REJ
 *	PPP.08	05-90	[ks] improve PPP trace reporting
 *	PPP.09	05-90	[ks] add auth type negotiation
 *	PPP.10	07-90	[ks] make ppp open/close/reset work properly
 *	PPP.14	08-90	[ks] change UPAP to PAP for consistency
 *			     with RFC1172
 *			     make LCP timeout configurable
 *	PPP.15	09-90	[ks] update to KA9Q NOS v900828
 */

#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "slip.h"
#include "ppp.h"

/* Counter for PPP id field */
extern unsigned char Pppid;

/* PPP tracing */
extern int Ppptrace;

static void lcp_open __ARGS((struct slip *sp));

static int lcp_sendreq __ARGS((struct slip *sp));
static struct mbuf *lcp_makereq __ARGS((struct lcpparm *localp));

static void lcp_rcvack __ARGS((struct slip *sp, struct cnfhdr *rcnf,
			struct mbuf *data));
static void lcp_rcvnak __ARGS((struct slip *sp, struct cnfhdr *rcnf,
			struct mbuf *data));
static void lcp_rcvrej __ARGS((struct slip *sp, struct cnfhdr *rcnf,
			struct mbuf *data));
static void lcp_rcvreq __ARGS((struct slip *sp, struct cnfhdr *rcnf,
			struct mbuf *data));
static void lcp_rcvtermack __ARGS((struct slip *sp));
static void lcp_rcvtermreq __ARGS((struct slip *sp, struct cnfhdr *rcnf));

static int lcp_chkack __ARGS((struct slip *sp, struct cnfhdr *ackcnf,
			struct mbuf *data));
static int lcp_chknak __ARGS((struct slip *sp, struct cnfhdr *nakcnf,
			struct mbuf *data));
static int lcp_chkrej __ARGS((struct slip *sp, struct cnfhdr *rejcnf,
			struct mbuf *data));
static void lcp_chkreq __ARGS((struct slip *sp, struct cnfhdr *reqcnf,
			struct mbuf *data));

static void lcp_timeout __ARGS((void *vp));
static void lcp_timer __ARGS((struct slip *sp));

static int lcp_sendreply __ARGS((struct slip *sp, char code,
			unsigned char id, struct mbuf *data));

/* Possible LCP states */
char *LCPStates[] = {
	"Closed",
	"Listen",
	"Req Sent",
	"Ack Rcvd",
	"Ack Sent",
	"Open",
	"Terminate"
};

/* Possible LCP/IPCP packet types */
char *LCPCodes[] = {
	NULLCHAR,
	"Config Req",
	"Config Ack",
	"Config Nak",
	"Config Rej",
	"Terminate Req",
	"Terminate Ack",
	"Code Rej",
	"Protocol Rej",
	"Echo Req",
	"Echo Reply",
	"Discard Req",
};

/* LCP default options, local */
/* Attempt: 0=dont negotiate; 1=attempt negotiation */
/* NOTE: this sructure is used to initialize the local parameter side */
/* of PPP connections; options which will not be negotiated must be   */
/* initialized to default LCP values specified in the PPP RFC         */
static struct lcpparm attempt_parms = {
	0, DEF_MRU,
	0, DEF_CTL_MAP,
	0, DEF_AUTH_TYPE,
	0, DEF_ENCR_TYPE,
	0, DEF_MAGIC_NUM,
	0, DEF_LINK_QUAL,
	0, DEF_PROT_COMPR,
	0, DEF_AC_COMPR
};
/* LCP default options, remote */
/* Accept: 0=reject negotiation; 1=negotiation okay */
static struct lcpparm accept_parms = {
	1, DEF_MRU,
	1, DEF_CTL_MAP,
	1, DEF_AUTH_TYPE,
	0, DEF_ENCR_TYPE,
	0, DEF_MAGIC_NUM,
	0, DEF_LINK_QUAL,
	1, DEF_PROT_COMPR,
	1, DEF_AC_COMPR
};

/****************************************************************************/

/* Convert LCP/IPCP header in host form to network form */
struct mbuf *
htoncnf(cnf, data)
struct cnfhdr *cnf;
struct mbuf *data;
{
    struct mbuf *bp;
    register char *cp;

    /* Prepend bytes for LCP/IPCP header */
    if ((bp = pushdown(data, CNF_HDRLEN)) == NULLBUF)
	return NULLBUF;

    /* Load header with proper values */
    cp = bp->data;
    *cp++ = cnf->code;
    *cp++ = cnf->id;
    put16(cp, cnf->len);

    return bp;
}

/* Extract LCP/IPCP header from incoming packet */
int
ntohcnf(cnf, bpp)
struct cnfhdr *cnf;
struct mbuf **bpp;
{
    cnf->code = pullchar(bpp);
    cnf->id = pullchar(bpp);
    cnf->len = pull16(bpp);
    return(CNF_HDRLEN);
}

/*******************************************/

/* Convert LCP/IPCP configuration option header in host form to network mbuf */
struct mbuf *
htonopt(opt)
struct opthdr *opt;
{
	struct mbuf *bp;
	register char *cp;

	if((bp = alloc_mbuf(OPT_HDRLEN)) == NULLBUF)
		return NULLBUF;

	/* Load data into LCP/IPCP configuration option header */
	cp = bp->data;
	*cp++ = opt->type;
	*cp++ = opt->len;

	bp->cnt = cp - bp->data;
	return bp;
}
/* Extract LCP/IPCP configuration option header */
int
ntohopt(opt,bpp)
struct opthdr *opt;
struct mbuf **bpp;
{
	if (opt== NULLOPTHDR || bpp == NULLBUFP)
		return -1;
	if (*bpp == NULLBUF)
		return -1;

	opt->type = pullchar(bpp);
	opt->len = pullchar(bpp);
	return 0;
}

/****************************************************************************/

/* Initialize Link Control Protocol state machine for config exchange */
int
lcp_start(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);

	if (Ppptrace > 5)
		log(-1, "lcp_start()");

	/* Leave things alone if not strictly closed */
	if ((pppiop->state != PPP_CLOSED)&&(lcpiop->lcp_state != LCP_LISTEN)
					 &&(lcpiop->lcp_state != LCP_CLOSED)) {
		return 0;
	}

	/* Prepare for Link Control negotiations */
	lcp_reset(pppiop);

	if (lcpiop->active == -1) {
		/* We have been strictly closed with 'ppp close' command */
		return 0;
	}

	/* Ready for Link Control negotiations */
	pppiop->state = PPP_LCP;
	if (lcpiop->active == 0) {
		/* Passive open; wait until remote host attempts connection */
		lcpiop->lcp_state = LCP_LISTEN;
		return 0;
	}

	/* Active open; begin LCP configuration negotiation */
	lcpiop->lcp_state = LCP_CLOSED;	
	return(lcp_sendreq(sp));
}

/*******************************************/

/* Close the PPP connection from local side */
int
lcp_close(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1,"lcp_close()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	ipcpiop = &(pppiop->ipcpio);
	if ((lcpiop->lcp_state == LCP_CLOSED) ||
	    (lcpiop->lcp_state == LCP_LISTEN))   {
		/* Already closed */
		return 0;
	}

	/* Need to shutdown PAP session if waiting for password */
	psignal(&(lcpiop->pap_user),0);

	/* Need to inform upper layers */
	if ((ipcpiop->ipcp_state != IPCP_CLOSED) &&
	    (ipcpiop->ipcp_state != IPCP_LISTEN))   {
		/* Wait for the IP layer to close */
		ipcp_close(sp);
		pwait(ipcpiop);
	}

	/* Set a timer against our request to shutdown */
	lcp_timer(sp);
	/* Ask remote host to shutdown */
	lcpiop->lcp_state = LCP_TERMINATE;
	lcpiop->ack_retry = 0;
	if (lcp_sendreply(sp, TERMINATE_REQ, 0, NULLBUF) == 0)
		pwait(lcpiop);

	pppiop->state = PPP_CLOSED;
	return 0;
}

/* Initialize our LCP configuration options to compiled default options */
void
lcp_init(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	struct timer *t;

	if (Ppptrace > 5)
		log(-1, "lcp_init()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	t = &(lcpiop->lcp_tm);

	/* One time initialization */
	lcpiop->lcp_state = LCP_CLOSED;
	lcpiop->active = 0;
	memcpy(&(lcpiop->initparm), &attempt_parms, sizeof(struct lcpparm));
	set_timer(t,LCP_TIMEOUT*1000L);
	lcp_timer(sp);			/* Set timer strictly OFF */
	stop_timer(&(lcpiop->lcp_tm));

	/* Initialization for each LCP open/close */
	lcp_reset(pppiop);
	return;
}

/* Link Control configuration negotiation complete */
static void
lcp_open(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);

	/* Check on Auth Protocol */
	if (lcpiop->initparm.auth_type != 0x0000) {
		/* We require authentication from the remote host */
		if (lcpiop->lclparm.auth_type != lcpiop->initparm.auth_type) {
			/* Couldnt agree on auth, shut down the link */
			if (Ppptrace)
				log(-1,"%s: Couldn't negotiate Authenticate Protocol type",sp->iface->name);
			lcp_shutdown(sp);
			return;
		}
	}

	/* Mark LCP layer as open */
	if (Ppptrace)
		log(-1,"%s: PPP/LCP Connection Open",sp->iface->name);
	lcpiop->lcp_state = LCP_OPEN;

	/* Set flags in PPP control struct to reflect accepted LCP options */
	if (sp->iface->mtu > lcpiop->remparm.mru) {
		/* Set new Max Transmission Unit for outgoing packets */
		sp->iface->mtu = lcpiop->remparm.mru;
		if (Ppptrace > 1)
			log(-1,"    Set new MTU for outgoing packets: %d",
				sp->iface->mtu);
	}

	/* Update async control character map for outgoing packets */
	if (pppiop->ctlmap != lcpiop->remparm.ctl_map) {
		pppiop->ctlmap = lcpiop->remparm.ctl_map;
		if (Ppptrace > 1)
			log(-1,"    Set new control map for outgoing packets: 0x%08lx",
				pppiop->ctlmap);
	}

	/* Update for protocol compression for outgoing/incoming packets */
	if (lcpiop->remparm.prot_compress) {
		sp->escaped |= PPP_XMT_PRCOMP;
		if (Ppptrace > 1)
			log(-1,"    Begin protocol compression on outgoing packets");
	}
	if (lcpiop->lclparm.prot_compress) {
		sp->escaped |= PPP_RCV_PRCOMP;
		if (Ppptrace > 1)
			log(-1,"    Begin protocol compression on incoming packets");
	}

	/* Update for addr/ctl compression for outgoing/incoming packets */
	if (lcpiop->remparm.ac_compress) {
		sp->escaped |= PPP_XMT_ACCOMP;
		if (Ppptrace > 1)
			log(-1,"    Begin addr/ctl compression on outgoing packets");
	}
	if (lcpiop->lclparm.ac_compress) {
		sp->escaped |= PPP_RCV_ACCOMP;
		if (Ppptrace > 1)
			log(-1,"    Begin addr/ctl compression on incoming packets");
	}

	/* LCP is open; switch to next phase */
	if ((lcpiop->lclparm.auth_type == PAP_AUTH_TYPE) ||
	    (lcpiop->remparm.auth_type == PAP_AUTH_TYPE))  {
		/* Startup PAP negotiations (do IPCP when done with PAP) */
		pap_start(sp);
	} else {
		/* Startup IPCP negotiations */
		ipcp_start(sp);
	}
	return;
}

/* Reset LCP configuration options for initial request */
int
lcp_reset(pppiop)
struct pppctl *pppiop;
{
	struct lcpctl *lcpiop;

	if (Ppptrace > 5)
		log(-1, "lcp_reset()");

	lcpiop = &(pppiop->lcpio);
	pppiop->ctlmap = DEF_CTL_MAP;
	if (lcpiop->active == 0)
		lcpiop->lcp_state = LCP_LISTEN;
	else
		lcpiop->lcp_state = LCP_CLOSED;
	lcpiop->ack_retry = 0;

	/* Reset our config request prarmeters to our preferred values */
	memcpy(&(lcpiop->lclparm),&(lcpiop->initparm),
				sizeof(struct lcpparm));
	memcpy(&(lcpiop->remparm),&accept_parms,
				sizeof(struct lcpparm));

	/* Need to shutdown PAP session if waiting for password */
	psignal(&(lcpiop->pap_user),0);

	return 0;
}

/****************************************************************************/

/* Send our LCP configuration request */
static int
lcp_sendreq(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	struct mbuf *bp;

	if (Ppptrace > 5)
		log(-1,"lcp_sendreq()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);

	/* Get a packet with our configuration request */
	bp = lcp_makereq(&(lcpiop->lclparm));

	/* Start timer against wait for reply to our config request */
	lcp_timer(sp);

	/* Send LCP configuration request to remote host */
	pppiop->state = PPP_LCP;
	if (lcpiop->lcp_state != LCP_ACK_SENT)
		lcpiop->lcp_state = LCP_REQ_SENT;
	return(lcp_sendreply(sp, CONFIG_REQ, 0, bp));
}

/*******************************************/

static struct mbuf *
lcp_makereq(localp)
struct lcpparm *localp;
{
	register char *cp;
	struct mbuf *bp;
	struct mbuf *req_bp = NULLBUF;

	if (Ppptrace > 5)
		log(-1,"    lcp_makereq()");

	/* Maximum Receive Unit */
	if (localp->neg_mru) {
		if (Ppptrace > 5)
			log(-1,"    asking for MRU: %d (0x%02x)",
				localp->mru,localp->mru);

		/* Attempt to negotiate Maximum Receive Unit */
		if ((bp = alloc_mbuf(4)) == NULLBUF)
			return NULLBUF;
		cp = bp->data;
		*cp++ = MAX_RCV_UNIT;
		*cp++ = 4;
		put16(cp, localp->mru);
		bp->cnt += 4;
		append(&req_bp, bp);
	}
	/* Async Control Map */
	if (localp->neg_ctl_map) {
		if (Ppptrace > 5)
			log(-1,"    asking for Async Control Map: 0x%08lx",
				localp->ctl_map);

		/* Attempt to negotiate Async Control Map */
		if ((bp = alloc_mbuf(6)) == NULLBUF)
			return NULLBUF;
		cp = bp->data;
		*cp++ = ASYNC_CTL_MAP;
		*cp++ = 6;
		put32(cp, localp->ctl_map);
		bp->cnt += 6;
		append(&req_bp, bp);
	}
	/* Authentication type */
	if ((localp->neg_auth_type)&&(localp->auth_type != 0x0000)) {
		if (Ppptrace > 5)
			log(-1,"    asking for Auth Protocol: 0x%04x",
				localp->auth_type);
		/* Attempt to negotiate Authentication Protocol */
		if ((bp = alloc_mbuf(4)) == NULLBUF)
			return NULLBUF;
		cp = bp->data;
		*cp++ = AUTH_TYPE;
		*cp++ = 4;
		put16(cp, localp->auth_type);
		bp->cnt += 4;
		append(&req_bp, bp);
	}
	/* Magic Number */
	if (localp->neg_magic_num) {
		if (Ppptrace > 5)
			log(-1,"    asking for Magic Number: %0x08lx",
				localp->magic_num);

		/* Attempt to negotiate Magic Number */
		if ((bp = alloc_mbuf(6)) == NULLBUF)
			return NULLBUF;
		cp = bp->data;
		*cp++ = MAGIC_NUMBER;
		*cp++ = 6;
		put32(cp, localp->magic_num);
		bp->cnt += 6;
		append(&req_bp, bp);
	}
	/* Protocol Field Compression */
	if (localp->neg_prot_compress) {
		if (Ppptrace > 5)
			log(-1,"    asking for Protcol compression");

		/* Attempt to negotiate protocol field compression */
		if ((bp = alloc_mbuf(2)) == NULLBUF)
			return NULLBUF;
		cp = bp->data;
		*cp++ = PROT_COMPRESS;
		*cp++ = 2;
		bp->cnt += 2;
		append(&req_bp, bp);
	}
	/* Address and Control Field Compression */
	if (localp->neg_ac_compress) {
		if (Ppptrace > 5)
			log(-1,"    asking for Addr/Ctl compression");

		/* Attempt to negotiate protocol field compression */
		if ((bp = alloc_mbuf(2)) == NULLBUF)
			return NULLBUF;
		cp = bp->data;
		*cp++ = AC_COMPRESS;
		*cp++ = 2;
		bp->cnt += 2;
		append(&req_bp, bp);
	}
	/* Encryption type not implemented */
	/* Link Quality Monitoring not implemented */

	return(req_bp);
}

/****************************************************************************/

/* Remote host ACKed our configuration request */
static void
lcp_rcvack(sp, rcnf, data)
struct slip *sp;
struct cnfhdr *rcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	if (Ppptrace > 5)
		log(-1, "lcp_rcvack()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	stop_timer(&lcpiop->lcp_tm);

	switch(lcpiop->lcp_state) {
	case LCP_REQ_SENT:
		/* Make sure ACK is proper */
		if (lcp_chkack(sp, rcnf, data) != -1) {
			/* Remote host accepted our request */
			lcpiop->lcp_state = LCP_ACK_RCVD;
		}

		/* Still need to settle request from remote host */
		lcp_timer(sp);
		break;

	case LCP_ACK_SENT:
		/* Make sure ACK is proper */
		if (lcp_chkack(sp, rcnf, data) == -1) {
			/* Error in ACK from remote host */
			/* Wait for another ACK, then send another request */
			lcp_timer(sp);
		} else {
			/* LCP negotiation complete */
			lcp_open(sp);
		}
		break;

	case LCP_ACK_RCVD:
	case LCP_OPEN:
		/* Something went wrong; restart negotiations */
		free_p(data);	
		lcp_reset(pppiop);
		lcp_sendreq(sp);
		break;
	case LCP_TERMINATE:
		/* We are attempting to close connection; wait */
		/* for timeout to resend a Terminate Request */
		free_p(data);
		break;
	case LCP_CLOSED:
	case LCP_LISTEN:
	default:
		/* Confusion; shutdown the connection */
		free_p(data);
		lcp_shutdown(sp);
		break;
	}
	return;
}

/* Remote host NAKed our configuration request */
static void
lcp_rcvnak(sp, rcnf, data)
struct slip *sp;
struct cnfhdr *rcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	if (Ppptrace > 5)
		log(-1, "lcp_rcvnak()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	stop_timer(&lcpiop->lcp_tm);

	switch(lcpiop->lcp_state) {
	case LCP_REQ_SENT:
	case LCP_ACK_SENT:
		/* Update our config request to reflect NAKed options */
		if (lcp_chknak(sp, rcnf, data) == -1) {
			/* Bad NAK packet */
			/* Wait for another; resend request on timeout */
			lcp_timer(sp);
		} else {
			/* Send updated config request */
			lcp_sendreq(sp);
		}
		break;

	case LCP_ACK_RCVD:
	case LCP_OPEN:
		/* Something went wrong; restart negotiations */
		free_p(data);	
		lcp_reset(pppiop);
		lcp_sendreq(sp);
		break;
	case LCP_TERMINATE:
		/* We are attempting to close connection; wait */
		/* for timeout to resend a Terminate Request */
		free_p(data);
		break;
	case LCP_CLOSED:
	case LCP_LISTEN:
	default:
		/* Confusion; shutdown the connection */
		free_p(data);
		lcp_shutdown(sp);
		break;
	}
	return;
}

/* Remote host rejected our configuration request */
static void
lcp_rcvrej(sp, rcnf, data)
struct slip *sp;
struct cnfhdr *rcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	if (Ppptrace > 5)
		log(-1, "lcp_rcvrej()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	stop_timer(&lcpiop->lcp_tm);

	switch(lcpiop->lcp_state) {
	case LCP_REQ_SENT:
	case LCP_ACK_SENT:
		/* Update our config request to reflect rejected options */
		if (lcp_chkrej(sp, rcnf, data) == -1) {
			/* Bad reject packet */
			/* Wait for another; resend request on timeout */
			lcp_timer(sp);
		} else {
			/* Send updated config request */
			lcp_sendreq(sp);
		}
		break;

	case LCP_ACK_RCVD:
	case LCP_OPEN:
		/* Something went wrong; restart negotiations */
		free_p(data);	
		lcp_reset(pppiop);
		lcp_sendreq(sp);
		break;
	case LCP_TERMINATE:
		/* We are attempting to close connection; wait */
		/* for timeout to resend a Terminate Request */
		free_p(data);
		break;
	case LCP_CLOSED:
	case LCP_LISTEN:
	default:
		/* Confusion; shutdown the connection */
		free_p(data);
		lcp_shutdown(sp);
		break;
	}
	return;
}

/* Process configuration request sent by remote host */
static void
lcp_rcvreq(sp, rcnf, data)
struct slip *sp;
struct cnfhdr *rcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	if (Ppptrace > 5)
		log(-1, "lcp_rcvreq()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);

	switch(lcpiop->lcp_state) {
	case LCP_LISTEN:	/* Normal event */
	case LCP_ACK_SENT:	/* Unexpected event */
	case LCP_OPEN:		/* Unexpected event */
		/* Reset LCP state machine for configuration negotiation */
		lcp_reset(pppiop);
		/* Send our configuration request */	
		lcp_sendreq(sp);
		/* Evaluate configuration request from remote host */
		lcp_chkreq(sp, rcnf, data);
		break;

	case LCP_ACK_RCVD:
		/* Stop timer against wait for config request */
		stop_timer(&(lcpiop->lcp_tm));
	case LCP_REQ_SENT:
		/* Evaluate configuration request from remote host */
		lcp_chkreq(sp, rcnf, data);
		break;

	case LCP_TERMINATE:
		/* We are attempting to close connection; wait */
		/* for timeout to resend a Terminate Request   */
		free_p(data);
		break;

	case LCP_CLOSED:
	default:
		/* We are closed; dont accept any connections */
		free_p(data);
		lcp_shutdown(sp);
		break;
	}
	return;
}

/* Remote host closed connection */
static void
lcp_rcvtermack(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	if (Ppptrace > 5)
		log(-1,"lcp_rcvtermack()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	stop_timer(&(lcpiop->lcp_tm));

	switch(lcpiop->lcp_state) {
	case LCP_OPEN:
		/* Remote host has abruptly closed connection */
/* Need to inform upper levels */
	case LCP_TERMINATE:
		/* Remote host has responded to our terminate request */
		if (Ppptrace)
			log(-1,"%s: PPP/LCP Connection Closed",sp->iface->name);
		pppiop->state = PPP_CLOSED;
		if (lcpiop->active == 0)
			lcpiop->lcp_state = LCP_LISTEN;
		else
			lcpiop->lcp_state = LCP_CLOSED;
		/* Prepare for next open */
		lcp_reset(pppiop);
		psignal(lcpiop,0);
		break;
	case LCP_REQ_SENT:
		/* Wait for timeout to restart attempt */
		break;
	case LCP_ACK_SENT:
	case LCP_ACK_RCVD:
		/* Something went wrong; restart negotiations */
		lcp_reset(pppiop);
		lcp_sendreq(sp);
		break;
	case LCP_CLOSED:
	case LCP_LISTEN:
	default:
		/* Unexpected, but no action needed */
		break;
	}

	return;
}

/* Remote peer requested that we close the PPP connection */
void
lcp_rcvtermreq(sp, rcnf)
struct slip *sp;
struct cnfhdr *rcnf;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	if (Ppptrace > 5)
		log(-1, "lcp_termreq()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);

	if (Ppptrace)
		log(-1,"%s: PPP/LCP Peer requested close",sp->iface->name);
	lcpiop->active = 0;
	lcpiop->lcp_state = LCP_LISTEN;
/* Need to inform upper layers */

	lcp_sendreply(sp, TERMINATE_ACK, rcnf->id, NULLBUF);
	lcp_reset(pppiop);
	pppiop->state = PPP_CLOSED;
	return;
}

/* Shutdown the LCP connection */
void
lcp_shutdown(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	if (Ppptrace > 5)
		log(-1, "lcp_shutdown()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);

	if (Ppptrace)
		log(-1,"%s: PPP/LCP Connection Shutdown",sp->iface->name);
	if (lcpiop->active == 0)
		lcpiop->lcp_state = LCP_LISTEN;
	else
		lcpiop->lcp_state = LCP_CLOSED;
/* Need to inform upper layers */

	lcp_sendreply(sp,TERMINATE_ACK,0,NULLBUF);
	lcp_reset(pppiop);
	pppiop->state = PPP_CLOSED;
	return;
}

/*******************************************/

/* Process configuration ACK send by remote host */
static int
lcp_chkack(sp, ackcnf, data)
struct slip *sp;
struct cnfhdr *ackcnf;
struct mbuf *data;
{
	int ackerr = 0;
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	struct mbuf *req_bp;
	struct opthdr reqopt;
	struct opthdr ackopt;
	int16 reqi16, acki16;
	int32 reqi32, acki32;

	if (Ppptrace > 5)
		log(-1,"lcp_chkack()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);

	/* LCP ID field must match last request we sent */
	if (ackcnf->id != lcpiop->lastid) {
		if (Ppptrace > 1)
			log(-1,"improper LCP ACK; bad ID");
		free_p(data);
		return -1;
	}

	/* Get a copy of last request we sent */
	req_bp = lcp_makereq(&(lcpiop->lclparm));

	/* Overall buffer length should match */
	if (ackcnf->len != len_p(req_bp)) {
		ackerr = 1;
	}

	/* ACK must echo all options we requested in the order requested */
	while ((ntohopt(&reqopt, &req_bp) != -1) && (!ackerr)) {
		/* Get config option from ACK packet */
		if (ntohopt(&ackopt, &data) == -1) {
			/* Must have as many acked options as requested */
			ackerr = 1;
			break;
		}

		/* Config option headers must match */
		if ((ackopt.type != reqopt.type)
		   ||(ackopt.len != reqopt.len)) {
			ackerr = 1;
			break;
		}

		/* Config option values must match */
		switch(reqopt.type) {
		case MAX_RCV_UNIT:	/* Maximum Receive Unit */
		case AUTH_TYPE:		/* Authentication Protocol Type */
			acki16 = pull16(&data);
			reqi16 = pull16(&req_bp);
			if (acki16 != reqi16) {
				ackerr = 1;
			}
			break;
		case MAGIC_NUMBER:	/* Magic Number */
		case ASYNC_CTL_MAP:	/* Async Control Map */
			acki32 = pull32(&data);
			reqi32 = pull32(&req_bp);
			if (acki32 != reqi32) {
				ackerr = 1;
			}
			break;
		case PROT_COMPRESS:	/* Protocol Compression */
		case AC_COMPRESS:	/* Addr/Control Compression */
			/* Option ON if requested */
			break;

		case ENCR_TYPE:		/* Encryption type not implemented */
		case LINK_QUALITY:	/* Link Qual Monitor not implemented */
		default:		/* Shouldnt happen */
			ackerr = 1;
			break;
		}
	}

	free_p(req_bp);
	free_p(data);

	if (ackerr) {
		/* Error in configuration ACK */
		if (Ppptrace > 5)
			log(-1,"improper LCP ACK echo");
		return -1;
	}

	/* ACK matches last request we made */
	if (Ppptrace > 5)
		log(-1,"valid LCP ACK echo");
	return 0;
}

/* Process configuration NAK send by remote host */
static int
lcp_chknak(sp, nakcnf, data)
struct slip *sp;
struct cnfhdr *nakcnf;
struct mbuf *data;
{
	int nakerr = 0;
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	struct lcpparm *localp;
	struct mbuf *req_bp;
	struct opthdr reqopt;
	struct opthdr nakopt;
	int16 naki16;
	int32 naki32;

	if (Ppptrace > 5)
		log(-1,"lcp_chknak()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	localp = &(lcpiop->lclparm);

	/* LCP ID field must match last request we sent */
	if (nakcnf->id != lcpiop->lastid) {
		if (Ppptrace > 1)
			log(-1,"improper LCP NAK; bad ID");
		free_p(data);
		return -1;
	}

	/* Get a copy of last request we sent */
	req_bp = lcp_makereq(&(lcpiop->lclparm));

	/* Check overall buffer length */
	if (nakcnf->len > len_p(req_bp)) {
		/* Remote cant NAK more options than we requested */
		nakerr = 1;
	}

	/* NAKed options must be same order as our original request */
	while ((ntohopt(&nakopt, &data) != -1) && (!nakerr)) {
		/* Get config option from our request */
		if (ntohopt(&reqopt, &req_bp) == -1) {
			/* Must find match to each NAKed option */
			nakerr = 1;
			break;
		}

		/* Maybe not all options were NAKed; look */
		/* for matching option in our request     */
		while (reqopt.type != nakopt.type) {
			/* This option not NAKed; eat rest   */
			/* of option from the request packet */
			reqopt.len -= 2;
			while (reqopt.len--)
				pullchar(&req_bp);

			/* Get next config option from our request */
			if (ntohopt(&reqopt, &req_bp) == -1) {
				/* Must find match to each NAKed option */
				reqopt.type = 0;
				nakerr = 1;
				break;
			}
		}

		/* Config option headers must match */
		if ((nakopt.type != reqopt.type)
		   ||(nakopt.len != reqopt.len)) {
			nakerr = 1;
			break;
		}

		/* Remote host replaced our request with new suggestion */
		switch(reqopt.type) {
		case MAX_RCV_UNIT:	/* Maximum Receive Unit */
			/* Get suggested MRU */
			naki16 = pull16(&data);
			/* Eat MRU from our request packet */
			pull16(&req_bp);

			if ((naki16 < MIN_MRU)||(naki16 > DEF_MRU))
				naki16 = DEF_MRU;

			/* Save new request value */
			localp->mru = naki16;
			break;
		case ASYNC_CTL_MAP:	/* Async Control Map */
			/* Get suggested control map */
			naki32 = pull32(&data);
			/* Eat control map from our request packet */
			pull32(&req_bp);

			/* Remote host may ask to escape more control  */
			/* characters than we require, but must escape */
			/* at least the control chars that we require. */
			if (naki32 != (naki32 | lcpiop->initparm.ctl_map))
				naki32 = lcpiop->initparm.ctl_map;

			/* Save new request value */
			localp->ctl_map = naki32;
			break;
		case AUTH_TYPE:		/* Authentication Protocol Type */
			/* Get suggested Auth Protocol */
			naki16 = pull16(&data);
			/* Eat auth type from our request packet */
			pull16(&req_bp);

			/* Can only accept auth protocols we know */
			if (naki16 != PAP_AUTH_TYPE) {
				/* Abandon request for auth type */
				/* Wont open LCP layer without auth protocol */
				naki16 = DEF_AUTH_TYPE;
			}

			/* Save new request value */
			localp->mru = naki16;
			break;
		case MAGIC_NUMBER:	/* Magic Number */
			/* Get suggested magic number */
			naki32 = pull32(&data);
			/* Eat magic number from our request packet */
			pull32(&req_bp);

/* Try to prevent matching magic numbers */

			/* Save new request value */
			localp->magic_num = naki32;
			break;
		case PROT_COMPRESS:
			/* Remote host says no protocol compression */
			localp->neg_prot_compress = 0;
			localp->prot_compress = DEF_PROT_COMPR;
			break;
		case AC_COMPRESS:
			/* Remote host says no addr/ctl compression */
			localp->neg_ac_compress = 0;
			localp->ac_compress = DEF_AC_COMPR;
			break;

		case ENCR_TYPE:		/* Encryption type not implemented */
		case LINK_QUALITY:	/* Link Qual Monitor not implemented */
		default:		/* Shouldnt happen */
			nakerr = 1;
			break;
		}
	}

	free_p(req_bp);
	free_p(data);

	if (nakerr) {
		/* Error in configuration NAK */
		if (Ppptrace > 5)
			log(-1,"improper LCP NAK echo");
		return -1;
	}

	/* NAK matches last request we made */
	if (Ppptrace > 5)
		log(-1,"valid LCP NAK echo");
	return 0;
}

/* Process configuration reject send by remote host */
static int
lcp_chkrej(sp, rejcnf, data)
struct slip *sp;
struct cnfhdr *rejcnf;
struct mbuf *data;
{
	int rejerr = 0;
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	struct lcpparm *localp;
	struct mbuf *req_bp;
	struct opthdr reqopt;
	struct opthdr rejopt;

	if (Ppptrace > 5)
		log(-1,"lcp_chkrej()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	localp = &(lcpiop->lclparm);

	/* LCP ID field must match last request we sent */
	if (rejcnf->id != lcpiop->lastid) {
		if (Ppptrace > 1)
			log(-1,"improper LCP REJ; bad ID");
		free_p(data);
		return -1;
	}

	/* Get a copy of last request we sent */
	req_bp = lcp_makereq(&(lcpiop->lclparm));

	/* Check overall buffer length */
	if (rejcnf->len > len_p(req_bp)) {
		/* Remote cant NAK more options than we requested */
		rejerr = 1;
	}

	/* Rejected options must be same order as our original request */
	while ((ntohopt(&rejopt, &data) != -1) && (!rejerr)) {
		/* Get config option from our request */
		if (ntohopt(&reqopt, &req_bp) == -1) {
			/* Must find match to each NAKed option */
			rejerr = 1;
			break;
		}

		/* Maybe not all options were NAKed; look */
		/* for matching option in our request     */
		while (reqopt.type != rejopt.type) {
			/* This option not NAKed; eat rest   */
			/* of option from the request packet */
			reqopt.len -= 2;
			while (reqopt.len--)
				pullchar(&req_bp);

			/* Get next config option from our request */
			if (ntohopt(&reqopt, &req_bp) == -1) {
				/* Must find match to each NAKed option */
				reqopt.type = 0;
				rejerr = 1;
				break;
			}
		}

		/* Config option headers must match */
		if ((rejopt.type != reqopt.type)
		   ||(rejopt.len != reqopt.len)) {
			rejerr = 1;
			break;
		}

		/* Remote host wont negotiate this option */
		switch(reqopt.type) {
		case MAX_RCV_UNIT:	/* Maximum Receive Unit */
			/* Eat MRU value from config packets */
			pull16(&data);
			pull16(&req_bp);

			/* Dont attempt to negotiate MRU */
			localp->neg_mru = 0;
			localp->mru = DEF_MRU;
			break;
		case ASYNC_CTL_MAP:	/* Async Control Map */
			/* Eat control map value from config packets */
			pull32(&data);
			pull32(&req_bp);

			/* Dont attempt to negotiate control map */
			localp->neg_ctl_map = 0;
			localp->ctl_map = DEF_CTL_MAP;
			break;
		case AUTH_TYPE:		/* Authentication Protocol Type */
			/* Eat auth type value from config packets */
			pull16(&data);
			pull16(&req_bp);

			/* Dont attempt to negotiate auth type */
			/* Wont open LCP layer without auth protocol */
			localp->neg_auth_type = 0;
			localp->auth_type = DEF_AUTH_TYPE;
			break;
		case MAGIC_NUMBER:	/* Magic Number */
			/* Eat magic number value from config packets */
			pull32(&data);
			pull32(&req_bp);

			/* Dont attempt to negotiate control map */
			localp->neg_magic_num = 0;
			localp->magic_num = DEF_MAGIC_NUM;
			break;
		case PROT_COMPRESS:	/* Protocol Compression */
			/* Abandon attempt to use protocol compression */
			localp->neg_prot_compress = 0;
			localp->prot_compress = DEF_PROT_COMPR;
			break;
		case AC_COMPRESS:	/* Addr/Ctl Compression */
			/* Abandon attempt to use addr/ctl compression */
			localp->neg_ac_compress = 0;
			localp->ac_compress = DEF_AC_COMPR;
			break;

		case ENCR_TYPE:		/* Encryption type not implemented */
		case LINK_QUALITY:	/* Link Qual Monitor not implemented */
		default:		/* Shouldnt happen */
			rejerr = 1;
			break;
		}
	}

	free_p(req_bp);
	free_p(data);

	if (rejerr) {
		/* Error in configuration reject */
		if (Ppptrace > 5)
			log(-1,"improper LCP REJ echo");
		return -1;
	}

	/* Reject matches last request we made */
	if (Ppptrace > 5)
		log(-1,"valid LCP REJ echo");
	return 0;
}

/* Check Link Control options requested by the remote host */
static void
lcp_chkreq(sp, reqcnf, data)
struct slip *sp;
struct cnfhdr *reqcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	int ilen;
	int16 i16;
	int32 i32;
	register char *cp;
	char cnf_accept = CONFIG_ACK;		/* Overall reply to request */
	char opt_accept;			/* Per option reply */
	struct opthdr reqopt;			/* Per option header storage */
	struct opthdr replyopt;			/* For building reply */
	struct lcpparm *remotep;		/* Ptr to remote options */
	struct lcpparm *initp;			/* Ptr to initial lcl opts */
	struct mbuf *bp;			/* Ptr for building reply */
	struct mbuf *reply_bp = NULLBUF;	/* Actual reply packet */

	if (Ppptrace > 5)
		log(-1, "lcp_chkreq()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	remotep = &(lcpiop->remparm);
	initp = &(lcpiop->initparm);

	/* Make sure length in LCP config header is realistic */
	i16 = len_p(data);
	if (i16 < reqcnf->len)
		reqcnf->len = i16;

	/* Process options requested by remote host */
	while (reqcnf->len > 0) {
		/* Get header for next option */
		if (ntohopt(&reqopt, &data) == -1)
			break;

		reqcnf->len -= reqopt.len;	/* Count bytes this option */
		reqopt.len -= 2;		/* Get data len this option */
		opt_accept = CONFIG_ACK;	/* Assume will accept option */

		switch(reqopt.type) {
		case MAX_RCV_UNIT:
			if (Ppptrace > 5)
				log(-1, "remote asking for MRU");

			/* Max Receive Unit is a 16bit field */
			ilen = 2;
			if (reqopt.len < ilen) {
				/* Short option data; reject packet */
				opt_accept = CONFIG_REJ;
				i16 = DEF_MRU;
				break;
			}
			/* Get proposed value from packet */
			i16 = pull16(&data);
			if ((remotep->neg_mru == 0) && (i16 != DEF_MRU)) {
				/* Not open for negotiation */
				opt_accept = CONFIG_REJ;
				break;
			}
			/* Check if new value is appropriate */
			if (i16 < MIN_MRU) {
				/* Too small, ask for minimum instead */
				opt_accept = CONFIG_NAK;
				i16 = MIN_MRU;
			} else if (i16 > DEF_MRU) {
				/* Too big, tell largest value we can use */
				opt_accept = CONFIG_NAK;
				i16 = DEF_MRU;
			} else {
				/* Value is acceptable */
				opt_accept = CONFIG_ACK;
			}
			remotep->mru = i16;
			break;
		case ASYNC_CTL_MAP:	/* Async Control Map */
			if (Ppptrace > 5)
				log(-1, "remote asking for async control map");
			/* Async control map is a 32bit field */
			ilen = 4;
			if (reqopt.len < ilen) {
				/* Short option data; reject packet */
				opt_accept = CONFIG_REJ;
				i32 = DEF_CTL_MAP;
				break;
			}
			/* Get proposed value from packet */
			i32 = pull32(&data);
			if ((remotep->neg_ctl_map == 0)
			    && (i32 != DEF_CTL_MAP)) {
				/* Not open for negotiation */
				opt_accept = CONFIG_REJ;
				break;
			}

			/* Record new async control character map */
			opt_accept = CONFIG_ACK;
			remotep->ctl_map = i32;
			break;
		case AUTH_TYPE:
			if (Ppptrace > 5)
				log(-1, "remote asking for Auth Protocol type");

			/* Auth Protocol Type is a 16bit field */
			ilen = 2;
			if (reqopt.len < ilen) {
				/* Short option data; reject packet */
				opt_accept = CONFIG_REJ;
				i16 = DEF_MRU;
				break;
			}
			/* Get proposed value from packet */
			i16 = pull16(&data);
			if ((initp->neg_auth_type == 1)
				&& (initp->auth_type != DEF_AUTH_TYPE)) {
				/* We are requesting auth too;               */
				/* we only know PAP auth protocol           */
				/* PAP is a one-way authentication protocol */
				opt_accept = CONFIG_REJ;
				break;
			}
			if ((remotep->neg_auth_type == 0)
				&& (i16 != DEF_AUTH_TYPE)) {
				/* Not open for negotiation */
				opt_accept = CONFIG_REJ;
				break;
			}
			/* Check if new value is appropriate */
			if (i16 == PAP_AUTH_TYPE) {
				/* Yes, we know Password Auth Protocol */
				opt_accept = CONFIG_ACK;
			} else {
				/* We dont know this protocol */
				opt_accept = CONFIG_NAK;
				i16 = PAP_AUTH_TYPE;
			}
			remotep->auth_type = i16;
			break;
		case MAGIC_NUMBER:	/* Magic Number */
			if (Ppptrace > 5)
				log(-1, "remote asking for magic number");
			/* Magic number is a 32bit field */
			ilen = 4;
			if (reqopt.len < ilen) {
				/* Short option data; reject packet */
				opt_accept = CONFIG_REJ;
				i32 = 0L;
				break;
			}
			/* Get proposed value from packet */
			i32 = pull32(&data);
			if ((remotep->neg_magic_num == 0)
			    || (i32 == 0L)) {
				/* Not open for negotiation */
				opt_accept = CONFIG_REJ;
				break;
			}

			/* Remote host cant use same magic number as us */
			if (i32 == lcpiop->lclparm.magic_num) {
				opt_accept = CONFIG_NAK;
				++i32;
				break;
			}

			/* Record new async control character map */
			opt_accept = CONFIG_ACK;
			remotep->magic_num = i32;
			break;
		case PROT_COMPRESS:
			if (Ppptrace > 5)
				log(-1, "remote asking for protocol compression");
			/* Presence of option means do protcol compression */
			ilen = 0;
			if ((remotep->neg_prot_compress == 0)
			    && (!DEF_PROT_COMPR)) {
				/* We wont do protocol compression */
				opt_accept = CONFIG_REJ;
				break;
			}

			/* Accept request for protocol compression */
			opt_accept = CONFIG_ACK;
			remotep->prot_compress = 1;
			break;
		case AC_COMPRESS:
			if (Ppptrace > 5)
				log(-1, "remote asking for addr/ctl compression");
			/* Presence of option means do addr/ctl compression */
			ilen = 0;
			if ((remotep->neg_ac_compress == 0)
			    && (!DEF_AC_COMPR)) {
				/* We wont do addr/ctl compression */
				opt_accept = CONFIG_REJ;
				break;
			}

			/* Accept request for addr/ctl compression */
			opt_accept = CONFIG_ACK;
			remotep->ac_compress = 1;
			break;

		case ENCR_TYPE:		/* Encryption type not implemented */
		case LINK_QUALITY:	/* Link Qual Monitor not implemented */
		default:		/* Unknown option */
			if (Ppptrace > 5)
				log(-1, "remote asking for unimplemented option: %x   len: %d",
					reqopt.type, reqopt.len);
			opt_accept = CONFIG_REJ;
			ilen = reqopt.len;
			if (len_p(data) < ilen)
				ilen = len_p(data);
			/* Look for standard sizes */
			if (ilen == 2)
				i16 = pull16(&data);
			else if (ilen == 4) {
				i32 = pull32(&data);
			}
			/* Other lengths dequeued at echo time */

			break;
		}
		if ((opt_accept == CONFIG_ACK) &&
		    (cnf_accept != CONFIG_ACK))
			/* This option was good, but a previous      */
			/* option was not. Return only those options */
			/* which are being nacked/rejected.          */
			continue;

		if (opt_accept == CONFIG_NAK) {
			if (cnf_accept == CONFIG_REJ)
				/* Return only those options */
				/* which are being rejected. */
				continue;
			if (cnf_accept == CONFIG_ACK) {
				/* Discard current list of good options */
				free_p(reply_bp);
				reply_bp = NULLBUF;
				/* Send a list of nacked options */
				cnf_accept = CONFIG_NAK;
			}
		}

		if (opt_accept == CONFIG_REJ) {
			if (cnf_accept != CONFIG_REJ) {
				/* Discard current list of good options */
				free_p(reply_bp);
				reply_bp = NULLBUF;
				/* Send a list of rejected options */
				cnf_accept = CONFIG_REJ;
			}
		}

		/* Add option response to the return list */
		replyopt.type = reqopt.type;
		replyopt.len = ilen + OPT_HDRLEN;
		if ((bp = htonopt(&replyopt)) == NULLBUF)
			break;
		append(&reply_bp,bp);
		if (ilen) {
			if ((bp = alloc_mbuf(ilen)) == NULLBUF)
				break;
			cp = bp->data;
			switch(ilen) {
			case 2:
				put16(cp,i16);
				bp->cnt += 2;
				break;
			case 4:
				put32(cp,i32);
				bp->cnt += 4;
				break;
			default:
				while (ilen--)
					bp->data[bp->cnt++] = pullchar(&data);
			}
			append(&reply_bp,bp);
		}
	}

	/* Send ACK/NAK/REJ to remote host */
	if (cnf_accept == CONFIG_ACK) {
		if (Ppptrace > 1)
			log(-1, "accept all options requested by remote peer");

		/* Accept configuration requested by remote host */
		lcp_sendreply(sp, CONFIG_ACK, reqcnf->id, reply_bp);
		if (lcpiop->lcp_state == LCP_REQ_SENT) {
			lcpiop->lcp_state = LCP_ACK_SENT;
		} else {
			/* LCP link now open */
			lcp_open(sp);
		}
	} else {
		if (Ppptrace > 1)
			log(-1,"options requested by remote peer not accepted: %s",
				((cnf_accept==CONFIG_NAK) ? "NAK" : "REJ"));

		/* NAK/REJ config request made by remote host */
		lcp_sendreply(sp, cnf_accept, reqcnf->id, reply_bp);

		/* Start timer against wait for amended config request */
		if (lcpiop->lcp_state == LCP_ACK_RCVD)
			lcp_timer(sp);
	}
	free_p(data);
	return;
}

/****************************************************************************/

/* Timeout while waiting for reply from remote host */
static void
lcp_timeout(vp)
void *vp;
{
	struct slip *sp;
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;

	if (Ppptrace > 1)
		log(-1, "lcp_timeout()");

	/* Load pointers to interface that timed-out */
	sp = (struct slip *)vp;
	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);

	/* Attempt to get things going again */
	switch(lcpiop->lcp_state) {
	case LCP_ACK_SENT:
		/* Remote host isnt listening to our request */
		lcp_reset(pppiop);
	case LCP_REQ_SENT:
	case LCP_ACK_RCVD:
		if (lcpiop->active == 0) {
			/* If passive open, we got a CONFIG_REQ from remote */
			/* host but no ACK in response to our CONFIG_REQ    */
			if (++lcpiop->ack_retry > LCP_RETRY_MAX) {
				/* Remote host doesnt seem to be listening */
				lcp_shutdown(sp);
				break;
			}
		}
		/* Timeout waiting for ACK to our request,         */
		/* or timeout waiting for request from remote host */
		lcp_sendreq(sp);
		break;

	case LCP_TERMINATE:
		/* Timeout waiting for terminate ACK; send another request */
		if (++lcpiop->ack_retry > LCP_TERM_RETRY) {
			/* No response to our polite request; give it up */

			if (Ppptrace)
				log(-1,"%s: PPP/LCP Connection Closed without reply from remote peer",sp->iface->name);
			if (lcpiop->active == 0)
				lcpiop->lcp_state = LCP_LISTEN;
			else
				lcpiop->lcp_state = LCP_CLOSED;

			lcp_sendreply(sp,TERMINATE_ACK,0,NULLBUF);
			lcp_reset(pppiop);
			tprintf("Still no response; marking LCP layer as closed\n");
			pppiop->state = PPP_CLOSED;
			psignal(lcpiop,0);
		} else {
			/* Request remote host to close data link */
			tprintf("Timeout waiting for response to our LCP terminate request\n");
			lcp_timer(sp);
			lcp_sendreply(sp, TERMINATE_REQ, 0, NULLBUF);
		}
		break;

	case LCP_CLOSED:
	case LCP_LISTEN:
	case LCP_OPEN:
	default:
		/* Confusion; shutdown the connection */
		lcp_shutdown(sp);
		break;
	}
	return;
}

/* Set a timer in case an expected event does not occur */
static void
lcp_timer(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	struct timer *t;

	if (Ppptrace > 5)
		log(-1,"lcp_timer()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	t = &(lcpiop->lcp_tm);
	t->func = (void (*)())lcp_timeout;
	t->arg = (void *)sp;
	start_timer(t);
	return;
}

/****************************************************************************/

/* Send an LCP packet to the remote host */
static int
lcp_sendreply(sp,code,id,data)
struct slip *sp;
char code;
unsigned char id;
struct mbuf *data;
{
	struct iface *iface;
	struct cnfhdr hdr;

	/* Load LCP header values */
	hdr.code = code;
	switch(code) {
	case CONFIG_REQ:
	case TERMINATE_REQ:
	case ECHO_REQ:
		/* Save ID field for match aginst replies from remote host */
		sp->pppio->lcpio.lastid = Pppid;
	case PROT_REJ:
	case DISCARD_REQ:
		/* Use a unique ID field value */
		hdr.id = Pppid++;
		break;

	case CONFIG_ACK:
	case CONFIG_NAK:
	case CONFIG_REJ:
	case TERMINATE_ACK:
	case CODE_REJ:
	case ECHO_REPLY:
		/* Use ID sent by remote host */
		hdr.id = id;
		break;

	default:
		/* Shouldnt happen */
		if (Ppptrace)
			log(-1, "bogus code: %x", code);
		return -1;
	}
	hdr.len = len_p(data) + CNF_HDRLEN;
	if (Ppptrace > 1) {
		log(-1, "%s: PPP/LCP Send: current state: %s   LCP option: %s   id: %d   len: %d",
			sp->iface->name,
			LCPStates[sp->pppio->lcpio.lcp_state],
			LCPCodes[code],hdr.id,hdr.len);
	}

	/* Prepend LCP header to packet data */
	if ((data = htoncnf(&hdr,data)) == NULLBUF)
		return -1;

	/* Send LCP packet to remote host */
	sp->pppio->sndlcp++;
	iface = sp->iface;
	return( (*iface->output)
		(iface, NULLCHAR, NULLCHAR, PPP_LCP_TYPE, data) );
}

/* Process incoming LCP packet */
void
lcpproc(iface,bp)
struct iface *iface;
struct mbuf *bp;
{
	struct slip *sp;
	struct cnfhdr hdr;

	sp = &Slip[iface->xdev];

	/* Extract LCP header */
	ntohcnf(&hdr, &bp);
	hdr.len -= CNF_HDRLEN;			/* Length includes envelope */
	trim_mbuf(&bp, hdr.len);		/* Trim off FCS bytes */

	if (Ppptrace > 1)
		log(-1,	"%s: PPP/LCP Recv: current state: %s:   LCP option: %s  id: %d len: %d",
			iface->name,
			LCPStates[sp->pppio->lcpio.lcp_state],
			LCPCodes[hdr.code], hdr.id, hdr.len);

	/* Process LCP packet data */
	switch(hdr.code) {
	case CONFIG_REQ:			/* Request of remote host */
		lcp_rcvreq(sp, &hdr, bp);
		break;
	case CONFIG_ACK:			/* Remote accepted our req */
		lcp_rcvack(sp, &hdr, bp);
		break;
	case CONFIG_NAK:			/* Remote adjusted our req */
		lcp_rcvnak(sp, &hdr, bp);
		break;
	case CONFIG_REJ:			/* Remote rejected our req */
		lcp_rcvrej(sp, &hdr, bp);
		break;
	case TERMINATE_REQ:			/* Remote request to close */
		lcp_rcvtermreq(sp, &hdr);
		break;
	case TERMINATE_ACK:			/* Remote closed on request */
		lcp_rcvtermack(sp);
		break;

	case CODE_REJ:
	case PROT_REJ:
	case ECHO_REQ:
	case ECHO_REPLY:
	case DISCARD_REQ:
		if (Ppptrace)
			log(-1,"Unimplemented LCP packet type: %x; dropping packet",hdr.code);
		free_p(bp);
		break;
	default:
		if (Ppptrace)
			log(-1,"Unknown LCP packet type: %x; dropping packet",hdr.code);
		free_p(bp);
		break;
	}
	return;
}
