/*
 *  PPPLCP.C	-- negotiate data link options
 *
 *	This implementation of PPP is declared to be in the public domain.
 *
 *	Jan 91	Bill_Simpson@um.cc.umich.edu
 *		Computer Systems Consulting Services
 *
 *	Acknowledgements and correction history may be found in PPP.C
 */

#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "iface.h"
#include "ppp.h"
#include "pppfsm.h"
#include "ppplcp.h"
#include "ppppap.h"
#include "cmdparse.h"
#include "devparam.h"


/* These defaults are defined in the PPP RFCs, and must not be changed */
static struct lcp_value_s lcp_default = {
	FALSE,		/* no need to negotiate defaults */

	LCP_MRU_DEFAULT,
	LCP_ACCM_DEFAULT,
	0,		/* no authentication */
	0,		/* no encryption */
	0L,		/* no magic number */
	0L,		/* no reporting period */
};

/* for test purposes, accept anything we understand in the NAK */
static int16 lcp_negotiate = LCP_N_MRU | LCP_N_ACCM | LCP_N_AUTHENT
		| LCP_N_PFC | LCP_N_ACFC | LCP_N_MAGIC;

static byte_t option_length[] = {
	 0,		/* unused */
	 4,		/* MRU */
	 6,		/* ACCM */
	 4,		/* authentication */
	 4,		/* encryption */
	 6,		/* magic number */
	 6,		/* monitor reporting period */
	 2,		/* Protocol compression */
	 2		/* Address/Control compression */
};


static int dolcp_local		__ARGS((int argc, char *argv[], void *p));
static int dolcp_open		__ARGS((int argc, char *argv[], void *p));
static int dolcp_remote		__ARGS((int argc, char *argv[], void *p));

static int dolcp_accm		__ARGS((int argc, char *argv[], void *p));
static int dolcp_acfc		__ARGS((int argc, char *argv[], void *p));
static int dolcp_auth		__ARGS((int argc, char *argv[], void *p));
static int dolcp_magic		__ARGS((int argc, char *argv[], void *p));
static int dolcp_mru		__ARGS((int argc, char *argv[], void *p));
static int dolcp_pfc		__ARGS((int argc, char *argv[], void *p));
static int dolcp_default	__ARGS((int argc, char *argv[], void *p));

static void lcp_option __ARGS((struct mbuf **bpp,
			struct lcp_value_s *value_p,
			byte_t o_type,
			byte_t o_length,
			struct mbuf **copy_bpp ));
static void lcp_makeoptions __ARGS((struct mbuf **bpp,
			struct lcp_value_s *value_p,
			int16 negotiating));
static struct mbuf *lcp_makereq __ARGS((struct fsm_s *fsm_p));

static int lcp_check __ARGS((struct mbuf **bpp,
			struct lcp_s *lcp_p,
			struct lcp_side_s *side_p,
			struct option_hdr *option_p,
			int request));

static int lcp_request	__ARGS((struct fsm_s *fsm_p,
			struct config_hdr *config,
			struct mbuf *data));
static int lcp_ack	__ARGS((struct fsm_s *fsm_p,
			struct config_hdr *ackcnf,
			struct mbuf *data));
static int lcp_nak	__ARGS((struct fsm_s *fsm_p,
			struct config_hdr *nakcnf,
			struct mbuf *data));
static int lcp_reject	__ARGS((struct fsm_s *fsm_p,
			struct config_hdr *rejcnf,
			struct mbuf *data));

static void lcp_reset	__ARGS((struct fsm_s *fsm_p));
static void lcp_starting __ARGS((struct fsm_s *fsm_p));
static void lcp_stopping __ARGS((struct fsm_s *fsm_p));

static void lcp_closing __ARGS((struct fsm_s *fsm_p));
static void lcp_opening __ARGS((struct fsm_s *fsm_p));

static void lcp_free	__ARGS((struct fsm_s *fsm_p));
static void lcp_init	__ARGS((struct ppp_s *ppp_p));


static struct fsm_constant_s lcp_constants = {
	"Lcp",
	PPP_LCP_PROTOCOL,
	0x0FFE,				/* codes 1-11 recognized */

	Lcp,
	LCP_REQ_TRY,
	LCP_NAK_TRY,
	LCP_TERM_TRY,
	LCP_TIMEOUT * 1000L,

	lcp_free,

	lcp_reset,
	lcp_starting,
	lcp_opening,
	lcp_closing,
	lcp_stopping,

	lcp_makereq,
	lcp_request,
	lcp_ack,
	lcp_nak,
	lcp_reject
};


/************************************************************************/

/* "ppp <iface> lcp" subcommands */
static struct cmds Lcpcmds[] = {
	"close",	doppp_close,	0,	0,	NULLCHAR,
	"listen",	doppp_passive,	0,	0,	NULLCHAR,
	"local",	dolcp_local,	0,	0,	NULLCHAR,
	"open",		dolcp_open,	0,	0,	NULLCHAR,
	"remote",	dolcp_remote,	0,	0,	NULLCHAR,
	"timeout",	doppp_timeout,	0,	0,	NULLCHAR,
	"try",		doppp_try,	0,	0,	NULLCHAR,
	NULLCHAR,
};

/* "ppp <iface> lcp [local | remote]" subcommands */
static struct cmds Lcpside_cmds[] = {
	"accm",		dolcp_accm,	0,	0,	NULLCHAR,
	"acfc",		dolcp_acfc,	0,	0,	NULLCHAR,
	"authenticate",	dolcp_auth,	0,	0,	NULLCHAR,
	"magic",	dolcp_magic,	0,	0,	NULLCHAR,
	"mru",		dolcp_mru,	0,	0,	NULLCHAR,
	"pfc",		dolcp_pfc,	0,	0,	NULLCHAR,
	"default",	dolcp_default,	0,	0,	NULLCHAR,
	NULLCHAR,
};


int
doppp_lcp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct iface *ifp = p;
	register struct ppp_s *ppp_p = ifp->extension;

	lcp_init(ppp_p);
	return subcmd(Lcpcmds, argc, argv, &(ppp_p->fsm[Lcp]));
}


static int
dolcp_local(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct fsm_s *fsm_p = p;
	struct lcp_s *lcp_p = fsm_p->pdv;
	return subcmd(Lcpside_cmds, argc, argv, &(lcp_p->local));
}


static int
dolcp_open(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct fsm_s *fsm_p = p;

	doppp_active( argc, argv, p );

	if ( fsm_p->ppp_p->phase >= pppLCP ) {
		fsm_start( fsm_p );
	}
	return 0;
}


static int
dolcp_remote(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct fsm_s *fsm_p = p;
	struct lcp_s *lcp_p = fsm_p->pdv;
	return subcmd(Lcpside_cmds, argc, argv, &(lcp_p->remote));
}

/************************************************************************/

static int
dolcp_accm(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct lcp_side_s *side_p = p;

	if (argc < 2) {
		tprintf("0x%08lx\n",side_p->want.accm);
	} else if (stricmp(argv[1],"allow") == 0) {
		return bit16cmd(&(side_p->will_negotiate),LCP_N_ACCM,
			"Allow ACCM", --argc, &argv[1] );
	} else {
		side_p->want.accm = strtoul(argv[1], NULLCHARP, 0);
		if ( side_p->want.accm != LCP_ACCM_DEFAULT )
			side_p->want.negotiate |= LCP_N_ACCM;
		else
			side_p->want.negotiate &= ~LCP_N_ACCM;
	}
	return 0;
}


static int
dolcp_acfc(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct lcp_side_s *side_p = p;

	if (stricmp(argv[1],"allow") == 0) {
		return bit16cmd(&(side_p->will_negotiate),LCP_N_ACFC,
			"Allow Address/Control Field Compression", --argc, &argv[1] );
	}
	return bit16cmd( &(side_p->want.negotiate), LCP_N_ACFC,
		"Address/Control Field Compression", argc, argv );
}


static int
dolcp_auth(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct lcp_side_s *side_p = p;

	if (argc < 2) {
		if ( side_p->want.negotiate & LCP_N_AUTHENT ) {
			switch ( side_p->want.authentication ) {
			case PPP_PAP_PROTOCOL:
				tprintf("Pap\n");
				break;
			default:
				tprintf("0x%04x\n", side_p->want.authentication);
				break;
			};
		} else {
			tprintf("None\n");
		}
	} else if (stricmp(argv[1],"allow") == 0) {
		return bit16cmd(&(side_p->will_negotiate),LCP_N_AUTHENT,
			"Allow Authentication", --argc, &argv[1] );
	} else if (stricmp(argv[1],"pap") == 0) {
		side_p->want.negotiate |= LCP_N_AUTHENT;
		side_p->want.authentication = PPP_PAP_PROTOCOL;
	} else if (stricmp(argv[1],"none") == 0) {
		side_p->want.negotiate &= ~LCP_N_AUTHENT;
	} else {
		tprintf("allow pap none\n");
		return 1;
	}
	return 0;
}


static int
dolcp_magic(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct lcp_side_s *side_p = p;
	int result = 0;

	if (argc < 2) {
		tprintf("%d\n",side_p->want.magic_number);
	} else if (stricmp(argv[1],"allow") == 0) {
		return bit16cmd(&(side_p->will_negotiate),LCP_N_MAGIC,
			"Allow Magic Number", --argc, &argv[1] );
	} else {
		register int32 x = strtoul(argv[1], NULLCHARP, 0);

		if ( !x ) {
			int test;

			/* Check for keyword */
			result = setbool( &test, "Magic Number", argc, argv );

			if ( test ) {
				/* Make a non-zero random number */
				x = Clock | 0x80000000L;
			}
		}
		if ( x ) {
			side_p->want.negotiate |= LCP_N_MAGIC;
		} else {
			side_p->want.negotiate &= ~LCP_N_MAGIC;
		}
		side_p->want.magic_number = x;
	}
	return result;
}


static int
dolcp_mru(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct lcp_side_s *side_p = p;

	if (argc < 2) {
		tprintf("%d\n",side_p->want.mru);
	} else if (stricmp(argv[1],"allow") == 0) {
		return bit16cmd(&(side_p->will_negotiate),LCP_N_MRU,
			"Allow MRU", --argc, &argv[1] );
	} else {
		register int x = (int)strtol( argv[1], NULLCHARP, 0 );

		if (x < LCP_MRU_LO || x > LCP_MRU_HI) {
			tprintf("MRU %s (%d) out of range %d thru %d\n",
				argv[1], x, LCP_MRU_LO, LCP_MRU_HI);
			return -1;
		} else if ( x != LCP_MRU_DEFAULT ) {
			side_p->want.negotiate |= LCP_N_MRU;
		} else {
			side_p->want.negotiate &= ~LCP_N_MRU;
		}
		side_p->want.mru = x;
	}
	return 0;
}


static int
dolcp_pfc(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct lcp_side_s *side_p = p;

	if (stricmp(argv[1],"allow") == 0) {
		return bit16cmd(&(side_p->will_negotiate),LCP_N_PFC,
			"Allow Protocol Field Compression", --argc, &argv[1] );
	}
	return bit16cmd( &(side_p->want.negotiate), LCP_N_PFC,
		"Protocol Field Compression", argc, argv );
}


static int
dolcp_default(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct lcp_side_s *side_p = p;

	ASSIGN( side_p->want, lcp_default );
	return 0;
}


/************************************************************************/
/*			E V E N T   P R O C E S S I N G			*/
/************************************************************************/

static void
lcp_option( bpp, value_p, o_type, o_length, copy_bpp )
struct mbuf **bpp;
struct lcp_value_s *value_p;
byte_t o_type;
byte_t o_length;
struct mbuf **copy_bpp;
{
	struct mbuf *bp;
	register char *cp;
	register int toss = o_length - OPTION_HDR_LEN;

	if ((bp = alloc_mbuf(o_length)) == NULLBUF) {
		return;
	}
	cp = bp->data;
	*cp++ = o_type;
	*cp++ = o_length;

	switch ( o_type ) {
	case LCP_MRU:
		put16(cp, value_p->mru);
		toss -= 2;
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    making MRU: 0x%02x", value_p->mru);
#endif
		break;

	case LCP_ACCM:
		put32(cp, value_p->accm);
		toss -= 4;
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    making ACCM: 0x%08lx", value_p->accm);
#endif
		break;

	case LCP_AUTHENT:
		put16(cp, value_p->authentication);
		toss -= 2;
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    making Auth Protocol: 0x%04x",
		value_p->authentication);
#endif
		break;

	case LCP_MAGIC:
		put32(cp, value_p->magic_number);
		toss -= 4;
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    making Magic Number: 0x%08lx",
		value_p->magic_number);
#endif
		break;

	case LCP_PFC:
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    making Protocol compression");
#endif
		break;

	case LCP_ACFC:
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    making Addr/Ctl compression");
#endif
		break;

	case LCP_ENCRYPT:		/* not implemented */
	case LCP_QUALITY:		/* not implemented */
	default:
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    making unimplemented type %d", o_type);
#endif
		break;
	};

	while ( toss-- > 0 ) {
		*cp++ = pullchar(copy_bpp);
	}
	bp->cnt += o_length;
	append(bpp, bp);
}


/************************************************************************/
/* Build a list of options */
static void
lcp_makeoptions(bpp, value_p, negotiating)
struct mbuf **bpp;
struct lcp_value_s *value_p;
int16 negotiating;
{
	register int o_type;

	PPP_DEBUG_ROUTINES("lcp_makeoptions()");

	for ( o_type = 1; o_type <= LCP_OPTION_LIMIT; o_type++ ) {
		if (negotiating & (1 << o_type)) {
			lcp_option( bpp, value_p,
				o_type, option_length[ o_type ], NULLBUFP);
		}
	}
}


/************************************************************************/
/* Build a request to send to remote host */
static struct mbuf *
lcp_makereq(fsm_p)
struct fsm_s *fsm_p;
{
	struct lcp_s *lcp_p = fsm_p->pdv;
	struct mbuf *req_bp = NULLBUF;

	PPP_DEBUG_ROUTINES("lcp_makereq()");

	lcp_makeoptions( &req_bp, &(lcp_p->local.work),
				lcp_p->local.work.negotiate );
	return(req_bp);
}


/************************************************************************/
/* Check the options, updating the working values.
 * Returns -1 if ran out of data, ACK/NAK/REJ as appropriate.
 */
static int
lcp_check( bpp, lcp_p, side_p, option_p, request )
struct mbuf **bpp;
struct lcp_s *lcp_p;
struct lcp_side_s *side_p;
struct option_hdr *option_p;
int request;
{
	int toss = option_p->len - OPTION_HDR_LEN;
	int option_result = CONFIG_ACK;		/* Assume good values */

	switch(option_p->type) {
	case LCP_MRU:
		side_p->work.mru = pull16(bpp);
		toss -= 2;
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    checking MRU: 0x%02x", side_p->work.mru);
#endif
		/* Check if new value is appropriate */
		if (side_p->work.mru < LCP_MRU_LO) {
			side_p->work.mru = LCP_MRU_LO;
			option_result = CONFIG_NAK;
		} else if (side_p->work.mru > LCP_MRU_HI) {
			side_p->work.mru = LCP_MRU_HI;
			option_result = CONFIG_NAK;
		}
		if ( request && (side_p->want.negotiate & LCP_N_MRU)
		  && side_p->work.mru > side_p->want.mru ) {
			side_p->work.mru = side_p->want.mru;
			option_result = side_p->want.mru;
		}
		break;

	case LCP_ACCM:
		side_p->work.accm = pull32(bpp);
		toss -= 4;
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    checking ACCM: 0x%08lx", side_p->work.accm);
#endif
		/* Remote host may ask to escape more control  */
		/* characters than we require, but must escape */
		/* at least the control chars that we require. */
		if ( (!request || (side_p->want.negotiate & LCP_N_ACCM))
		  && side_p->work.accm !=
		       (side_p->work.accm | side_p->want.accm) ) {
			side_p->work.accm |= side_p->want.accm;
			option_result = CONFIG_NAK;
		}
		break;

	case LCP_AUTHENT:
		side_p->work.authentication = pull16(bpp);
		toss -= 2;
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    checking Auth Protocol: 0x%04x",
		side_p->work.authentication);
#endif
		/* Check if new value is appropriate */
		switch ( side_p->work.authentication ) {
		case PPP_PAP_PROTOCOL:
			/* Yes */
			break;
		default:
			side_p->work.authentication = PPP_PAP_PROTOCOL;
			option_result = CONFIG_NAK;
			break;
		};
		break;

	case LCP_MAGIC:
		side_p->work.magic_number = pull32(bpp);
		toss -= 4;
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    checking Magic Number: 0x%08lx",
		side_p->work.magic_number);
#endif

		/* Ensure that magic numbers are different */
		if (side_p->work.magic_number == 0L
		 || lcp_p->remote.work.magic_number == lcp_p->local.work.magic_number) {
			side_p->work.magic_number += Clock;
			option_result = CONFIG_NAK;
		}
		break;

	case LCP_PFC:
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    checking Protocol compression");
#endif
		break;

	case LCP_ACFC:
#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS)
	log(-1, "    checking Addr/Ctl compression");
#endif
		break;

	case LCP_ENCRYPT:		/* not implemented */
	case LCP_QUALITY:		/* not implemented */
	default:
		option_result = CONFIG_REJ;
		break;
	};

	if (option_p->type > LCP_OPTION_LIMIT
	 || !(side_p->will_negotiate & (1 << option_p->type))) {
		option_result = CONFIG_REJ;
	}

	if ( toss < 0 )
		return -1;

	if ( !request  &&  toss > 0 ) {
		/* toss extra bytes in option */
		while( toss-- > 0 ) {
			if ( pullchar(bpp) == -1 )
				return -1;
		}
	}

	return (option_result);
}


/************************************************************************/
/* Check Link Control options requested by the remote host */
static int
lcp_request(fsm_p, config, data)
struct fsm_s *fsm_p;
struct config_hdr *config;
struct mbuf *data;
{
	struct lcp_s *lcp_p = fsm_p->pdv;
	int32 signed_length = config->len;
	struct mbuf *reply_bp = NULLBUF;	/* reply packet */
	int reply_result = CONFIG_ACK;		/* reply to request */
	int16 desired;				/* desired to negotiate */
	struct option_hdr option;		/* option header storage */
	int option_result;			/* option reply */

	PPP_DEBUG_ROUTINES("lcp_request()");
	lcp_p->remote.work.negotiate = FALSE;	/* clear flags */

	/* Process options requested by remote host */
	while (signed_length > 0  &&  ntohopt(&option, &data) != -1) {
		if ((signed_length -= option.len) < 0) {
			PPP_DEBUG_CHECKS("LCP REQ: bad header length");
			free_p(data);
			free_p(reply_bp);
			return -1;
		}

		if ( ( option_result = lcp_check( &data, lcp_p,
				&(lcp_p->remote), &option, TRUE ) ) == -1 ) {
			PPP_DEBUG_CHECKS("LCP REQ: ran out of data");
			free_p(data);
			free_p(reply_bp);
			return -1;
		}

#ifdef PPP_DEBUG_OPTIONS
if (PPPtrace & PPP_DEBUG_OPTIONS) {
	log(-1, "LCP REQ: result %s, option %d, length %d",
		fsmCodes[option_result],
		option.type,
		option.len);
}
#endif
		if ( option_result < reply_result ) {
			continue;
		} else if ( option_result > reply_result ) {
			/* Discard current list of replies */
			free_p(reply_bp);
			reply_bp = NULLBUF;
			reply_result = option_result;
		}

		/* remember that we processed option */
		if ( option_result != CONFIG_REJ
		  && option.type <= LCP_OPTION_LIMIT ) {
			lcp_p->remote.work.negotiate |= (1 << option.type);
		}

		/* Add option response to the return list */
		lcp_option( &reply_bp, &(lcp_p->remote.work),
			option.type, option.len, &data );
	}

	/* Now check for any missing options which are desired */
	if ( fsm_p->retry_nak > 0
	 &&  (desired = lcp_p->remote.want.negotiate
		       & ~lcp_p->remote.work.negotiate) != 0 ) {
		switch ( reply_result ) {
		case CONFIG_ACK:
			free_p(reply_bp);
			reply_bp = NULLBUF;
			reply_result = CONFIG_NAK;
			/* fallthru */
		case CONFIG_NAK:
			lcp_makeoptions( &reply_bp, &(lcp_p->remote.want),
				desired );
			fsm_p->retry_nak--;
			break;
		case CONFIG_REJ:
			/* do nothing */
			break;
		};
	} else if ( reply_result == CONFIG_NAK ) {
		/* if too many NAKs, reject instead */
		if ( fsm_p->retry_nak > 0 )
			fsm_p->retry_nak--;
		else
			reply_result = CONFIG_REJ;
	}

	/* Send ACK/NAK/REJ to remote host */
	fsm_send(fsm_p, reply_result, config->id, reply_bp);
	free_p(data);
	return (reply_result != CONFIG_ACK);
}


/************************************************************************/
/* Process configuration ACK sent by remote host */
static int
lcp_ack(fsm_p, config, data)
struct fsm_s *fsm_p;
struct config_hdr *config;
struct mbuf *data;
{
	struct mbuf *req_bp;
	int error = FALSE;

	PPP_DEBUG_ROUTINES("lcp_ack()");

	/* ID field must match last request we sent */
	if (config->id != fsm_p->lastid) {
		PPP_DEBUG_CHECKS("LCP ACK: wrong ID");
		free_p(data);
		return -1;
	}

	/* Get a copy of last request we sent */
	req_bp = lcp_makereq(fsm_p);

	/* Overall buffer length should match */
	if (config->len != len_p(req_bp)) {
		PPP_DEBUG_CHECKS("LCP ACK: buffer length mismatch");
		error = TRUE;
	} else {
		register int req_char;
		register int ack_char;

		/* Each byte should match */
		while ((req_char = pullchar(&req_bp)) != -1) {
			if ((ack_char = pullchar(&data)) == -1
			 || ack_char != req_char ) {
				PPP_DEBUG_CHECKS("LCP ACK: data mismatch");
				log (-1, "req=%02X, ack=%02X", req_char, ack_char);
				error = TRUE;
				break;
			}
		}
	}
	free_p(req_bp);
	free_p(data);

	if (error) {
		return -1;
	}

	PPP_DEBUG_CHECKS("LCP ACK: valid");
	return 0;
}


/************************************************************************/
/* Process configuration NAK sent by remote host */
static int
lcp_nak(fsm_p, config, data)
struct fsm_s *fsm_p;
struct config_hdr *config;
struct mbuf *data;
{
	struct lcp_s *lcp_p = fsm_p->pdv;
	struct lcp_side_s *local_p = &(lcp_p->local);
	int32 signed_length = config->len;
	struct option_hdr option;
	int last_option = 0;
	int result;

	PPP_DEBUG_ROUTINES("lcp_nak()");

	/* ID field must match last request we sent */
	if (config->id != fsm_p->lastid) {
		PPP_DEBUG_CHECKS("LCP NAK: wrong ID");
		free_p(data);
		return -1;
	}

	/* First, process in order.  Then, process extra "important" options */
	while (signed_length > 0  &&  ntohopt(&option, &data) != -1) {
		if ((signed_length -= option.len) < 0) {
			PPP_DEBUG_CHECKS("LCP NAK: bad header length");
			free_p(data);
			return -1;
		}
		if ( option.type > LCP_OPTION_LIMIT ) {
			PPP_DEBUG_CHECKS("LCP NAK: option out of range");
		} else if ( option.type < last_option
		  || !(local_p->work.negotiate & (1 << option.type)) ) {
			if (local_p->work.negotiate & (1 << option.type)) {
				PPP_DEBUG_CHECKS("LCP NAK: option out of order");
				free_p(data);
				return -1;		/* was requested */
			}
			local_p->work.negotiate |= (1 << option.type);
			last_option = LCP_OPTION_LIMIT + 1;
		} else {
			last_option = option.type;
		}
		if ( ( result = lcp_check( &data, lcp_p,
				local_p, &option, FALSE ) ) == -1 ) {
			PPP_DEBUG_CHECKS("LCP NAK: ran out of data");
			free_p(data);
			return -1;
		}
		/* update the negotiation status */
		if ( result == CONFIG_REJ
		  && option.type <= LCP_OPTION_LIMIT ) {
			local_p->work.negotiate &= ~(1 << option.type);
		}
	}
	PPP_DEBUG_CHECKS("LCP NAK: valid");
	free_p(data);
	return 0;
}


/************************************************************************/
/* Process configuration reject sent by remote host */
static int
lcp_reject(fsm_p, config, data)
struct fsm_s *fsm_p;
struct config_hdr *config;
struct mbuf *data;
{
	struct lcp_s *lcp_p = fsm_p->pdv;
	struct lcp_side_s *local_p = &(lcp_p->local);
	int32 signed_length = config->len;
	struct option_hdr option;
	int last_option = 0;

	PPP_DEBUG_ROUTINES("lcp_reject()");

	/* ID field must match last request we sent */
	if (config->id != fsm_p->lastid) {
		PPP_DEBUG_CHECKS("LCP REJ: wrong ID");
		free_p(data);
		return -1;
	}

	/* Process in order, checking for errors */
	while (signed_length > 0  &&  ntohopt(&option, &data) != -1) {
		register int k;

		if ((signed_length -= option.len) < 0) {
			PPP_DEBUG_CHECKS("LCP REJ: bad header length");
			free_p(data);
			return -1;
		}
		if ( option.type > LCP_OPTION_LIMIT ) {
			PPP_DEBUG_CHECKS("LCP REJ: option out of range");
		} else if ( option.type < last_option
		 || !(local_p->work.negotiate & (1 << option.type))) {
			PPP_DEBUG_CHECKS("LCP REJ: option out of order");
			free_p(data);
			return -1;
		}
		for ( k = option.len - OPTION_HDR_LEN; k-- > 0; ) {
			if ( pullchar(&data) == -1 ) {
				PPP_DEBUG_CHECKS("LCP REJ: ran out of data");
				free_p(data);
				return -1;
			}
		}
		last_option = option.type;

		if ( option.type <= LCP_OPTION_LIMIT ) {
			local_p->work.negotiate &= ~(1 << option.type);
		}
	}
	PPP_DEBUG_CHECKS("LCP REJ: valid");
	free_p(data);
	return 0;
}


/************************************************************************/
/*			I N I T I A L I Z A T I O N			*/
/************************************************************************/

/* Reset configuration options before request */
static void
lcp_reset(fsm_p)
struct fsm_s *fsm_p;
{
	struct lcp_s *lcp_p = 	fsm_p->pdv;

	PPP_DEBUG_ROUTINES("lcp_reset()");

	if ( lcp_p->local.want.negotiate & LCP_N_MAGIC ) {
		lcp_p->local.want.magic_number += Clock;
	}

	ASSIGN( lcp_p->local.work, lcp_p->local.want );
	lcp_p->local.will_negotiate |= lcp_p->local.want.negotiate;

	lcp_p->remote.work.negotiate = FALSE;
	lcp_p->remote.will_negotiate |= lcp_p->remote.want.negotiate;
}


/************************************************************************/
/* Prepare to begin configuration exchange */
static void
lcp_starting(fsm_p)
struct fsm_s *fsm_p;
{
	PPP_DEBUG_ROUTINES("lcp_starting()");
}


/************************************************************************/
/* After termination */
static void
lcp_stopping(fsm_p)
struct fsm_s *fsm_p;
{
	struct iface *ifp = fsm_p->ppp_p->iface;

	PPP_DEBUG_ROUTINES("lcp_stopping()");

	/* Now, tell the device to go down.
	 * In turn, it should tell our IO status
	 * when it has gone down.
	 */
	ifp->ioctl(ifp,PARAM_DOWN,TRUE,0L);
}


/************************************************************************/
/* Close higher levels in preparation for link shutdown */
static void
lcp_closing(fsm_p)
struct fsm_s *fsm_p;
{
	struct ppp_s *ppp_p = fsm_p->ppp_p;

	switch ( ppp_p->phase ) {
	case pppREADY:
		fsm_down( &(ppp_p->fsm[IPcp]) );
		break;
	case pppAP:
		pap_down( &(ppp_p->fsm[Pap]) );
		break;
	default:
		/* nothing to do */
		break;
	};

	ppp_p->phase = pppTERMINATE;
	psignal(ppp_p, 0);
}


/************************************************************************/
/* configuration negotiation complete */
static void
lcp_opening(fsm_p)
struct fsm_s *fsm_p;
{
	struct lcp_s *lcp_p = 	fsm_p->pdv;
	struct iface *ifp = 	fsm_p->ppp_p->iface;

	if (ifp->mtu > lcp_p->remote.work.mru) {
		/* Set new Max Transmission Unit for outgoing packets */
		ifp->mtu = lcp_p->remote.work.mru;
		if (PPPtrace > 1)
			log(-1,"    Set new MTU for outgoing packets: %d",
				ifp->mtu);
	}

	fsm_p->ppp_p->phase = pppAP;
	psignal(fsm_p->ppp_p, 0);
}


/************************************************************************/
static void
lcp_free(fsm_p)
struct fsm_s *fsm_p;
{
	/* nothing to do */
}


/* Initialize configuration structure */
static void
lcp_init(ppp_p)
struct ppp_s *ppp_p;
{
	struct fsm_s *fsm_p;
	struct lcp_s *lcp_p;

	PPPtrace = ppp_p->trace;

	PPP_DEBUG_ROUTINES("lcp_init()");

	if (ppp_p->fsm[Lcp].pdv != NULL)
		return;		/* already initialized */

	fsm_p = &(ppp_p->fsm[Lcp]);
	fsm_p->ppp_p = ppp_p;
	fsm_p->pdc = &lcp_constants;
	fsm_p->pdv =
	lcp_p = callocw(1,sizeof(struct lcp_s));

	/* Set option parameters to first request defaults */
	ASSIGN( lcp_p->local.want, lcp_default );
	lcp_p->local.will_negotiate = lcp_negotiate;

	ASSIGN( lcp_p->remote.want, lcp_default );
	ASSIGN( lcp_p->remote.work, lcp_default );
	lcp_p->remote.will_negotiate = lcp_negotiate;

	fsm_init(fsm_p);
}


