/*
 *  PPPIPCP.C	-- negotiate IP parameters
 *
 *	12-89	-- Katie Stevens (dkstevens@ucdavis.edu)
 *		   UC Davis, Computing Services
 *	PPP.04	02-90	[ks] make automatic route entry a private route
 *	PPP.07	04-90	[ks] make IP addr negotiation independent of
 *			     whether host is active or passive open
 *			     thanks to Brad Clements, bkc@omnigate.clarkson.edu
 *			     dont bring up IP unless both addrs are known
 *	PPP.08	05-90	[ks] IP compr: we request means we want to rcv compr;
 *			     remote requests means we should xmt compr TCP.
 *			     improve PPP trace reporting
 *	PPP.09	05-90	[ks] add UPAP auth protocol
 *	PPP.10	07-90	[ks] make ppp open/close/reset work properly
 *			     add peerID-to-IPaddr lookup table
 *			     add peer IP lookup pool
 *	PPP.13	08-90	[ks] add timestamp on PPP link open
 *	PPP.14	08-90	[ks] change UPAP to PAP for consistency with RFC1172
 *			     make IPCP timeout configurable
 *	PPP.15	09-90	[ks] update to KA9Q NOS v900828
 */

#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include "global.h"
#include "files.h"
#include "netuser.h"
#include "mbuf.h"
/*#include "proc.h"*/
#include "iface.h"
#include "pktdrvr.h"
#include "ip.h"
#include "tcp.h"
#include "slcompre.h"
#include "ppp.h"
#include "slip.h"
#include "proc.h"

extern int32 Ip_addr;

/* Counter for PPP id field */
extern unsigned char Pppid;

/* PPP tracing */
extern int Ppptrace;

static void ipcp_open __ARGS((struct slip *sp));
static int ipcp_addr_idle __ARGS((int32 addr));
static int32 ipcp_lookuppeer __ARGS((char *peerid));
static int32 ipcp_poolnext __ARGS((struct ipcppool *poolp));
static void ipcp_reset_tcp __ARGS((struct slip *sp));

static int ipcp_sendreq __ARGS((struct slip *sp));
static struct mbuf *ipcp_makereq __ARGS((struct ipcpctl *ipcpiop));

static void ipcp_rcvack __ARGS((struct slip *sp, struct cnfhdr *rcnf,
			struct mbuf *data));
static void ipcp_rcvnak __ARGS((struct slip *sp, struct cnfhdr *rcnf,
			struct mbuf *data));
static void ipcp_rcvrej __ARGS((struct slip *sp, struct cnfhdr *rcnf,
			struct mbuf *data));
static void ipcp_rcvreq __ARGS((struct slip *sp, struct cnfhdr *rcnf,
			struct mbuf *data));
static void ipcp_rcvtermack __ARGS((struct slip *sp));
static void ipcp_rcvtermreq __ARGS((struct slip *sp, struct cnfhdr *rcnf));
static void ipcp_shutdown __ARGS((struct slip *sp));

static int ipcp_chkack __ARGS((struct slip *sp, struct cnfhdr *ackcnf,
			struct mbuf *data));
static int ipcp_chknak __ARGS((struct slip *sp, struct cnfhdr *nakcnf,
			struct mbuf *data));
static int ipcp_chkrej __ARGS((struct slip *sp, struct cnfhdr *rejcnf,
			struct mbuf *data));
static void ipcp_chkreq __ARGS((struct slip *sp, struct cnfhdr *reqcnf,
			struct mbuf *data));

static void ipcp_timeout __ARGS((void *vp));
static void ipcp_timer __ARGS((struct slip *sp));

static int ipcp_sendreply __ARGS((struct slip *sp, char code,
			unsigned char id, struct mbuf *data));

/* In PPPLCP.C */
/* Possible IPCP states same as possible LCP states */
extern char *LCPStates[];
extern char *LCPCodes[];

/****************************************************************************/

/* Initialize IP Control Protocol state machine for config exchange */
int
ipcp_start(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct lcpctl *lcpiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1, "ipcp_start()");

	pppiop = sp->pppio;
	lcpiop = &(pppiop->lcpio);
	ipcpiop = &(pppiop->ipcpio);

	/* Just finished LCP negotiation; prepare for IPCP negotiation */
	pppiop->state = PPP_IPCP;
	ipcp_reset(sp);
	/* Can set peer_addr with 'ppp peer' command */
	ipcpiop->attempt_dest = ipcpiop->peer_addr;
	/* If not already set, can lookup PAP peer ID in PPPHOSTS file */
	if ((ipcpiop->peer_addr == 0L) &&
	    (lcpiop->lclparm.auth_type == PAP_AUTH_TYPE)) {
		ipcpiop->attempt_dest = ipcp_lookuppeer(lcpiop->pap_user);
	}
	/* If still not set, can get next address from PPP pool */
	if ((ipcpiop->peer_addr == 0L) &&
	    (ipcpiop->peer_pool != NULLPOOL)) {
		ipcpiop->attempt_dest = ipcp_poolnext(ipcpiop->peer_pool);
	}
	if (ipcpiop->attempt_dest == 0L)
		ipcpiop->accept_addrs = 1;

	if (!ipcpiop->active) {
		/* Passive open; wait until remote host attempts connection */
		ipcpiop->ipcp_state = IPCP_LISTEN;
		return 0;
	}

	/* Active open; begin IPCP configuration negotiation */
	ipcpiop->ipcp_state = IPCP_CLOSED;	
	return(ipcp_sendreq(sp));
}

/*******************************************/

/* Close the IP connection from local side */
int
ipcp_close(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1,"ipcp_close()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);
	if ((ipcpiop->ipcp_state == IPCP_CLOSED) ||
	    (ipcpiop->ipcp_state == IPCP_LISTEN))   {
		/* Already closed */
		return 0;
	}

	if (Ppptrace > 1)
		log(-1,"%s: PPP/IPCP IP Closing",sp->iface->name);

	/* Reset IP connections on this interface */
	ipcp_reset_tcp(sp);
	/* Remove routing entry from route table */
	rt_drop(ipcpiop->attempt_dest, (unsigned int)32);

	/* Set a timer against our request to shutdown */
	ipcp_timer(sp);
	/* Ask remote host to shutdown */
	ipcpiop->ipcp_state = IPCP_TERMINATE;
	ipcpiop->ack_retry = 0;
	if (ipcp_sendreply(sp, TERMINATE_REQ, 0, NULLBUF) == 0)
		pwait(ipcpiop);
	return 0;
}

/* Initialize our IPCP configuration options to compiled default options */
void
ipcp_init(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;
	struct timer *t;

	if (Ppptrace > 5)
		log(-1, "ipcp_init()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);
	t = &(ipcpiop->ipcp_tm);
	ipcpiop->ipcp_state = IPCP_CLOSED;

	/* Set option parameters to first request defaults */
	ipcpiop->peer_addr = 0L;
	ipcpiop->neg_ip_compr = 0;
	ipcpiop->ip_compr_type = DEF_IP_COMPR;
	/* Initialize timer */
	set_timer(t,IPCP_TIMEOUT*1000L);
	ipcp_timer(sp);
	stop_timer(&(ipcpiop->ipcp_tm));

	ipcp_reset(sp);
}

/* IP Control configuration negotiation complete */
static void
ipcp_open(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* Mark IPCP layer as open */
	if (Ppptrace)
		log(-1,"%s: PPP/IPCP IP Open",sp->iface->name);
	ipcpiop->ipcp_state = IPCP_OPEN;

	/* Make sure we found out IP address at each end */
	if ((ipcpiop->attempt_src != ipcpiop->accept_dest)
		|| (ipcpiop->attempt_src == 0L)) {
		if (Ppptrace)
			log(-1,"Couldnt negotiate local IP addr for interface %s",
				sp->iface->name);
		ipcp_shutdown(sp);
		return;
	}
	if ((ipcpiop->attempt_dest != ipcpiop->accept_src)
		|| (ipcpiop->attempt_dest == 0L)) {
		if (Ppptrace)
			log(-1,"Couldnt negotiate remote peer IP addr for interface %s",
				sp->iface->name);
		ipcp_shutdown(sp);
		return;
	}

	/* Set IP compression to reflect negotiated option */
	if (ipcpiop->lcl_ip_compr == IPCP_VJCOMPR) {
		sp->escaped |= PPP_RCV_VJCOMPR;
		if (Ppptrace > 1)
			log(-1,"    Begin Van Jacobson TCP/IP header compression on incoming packets");
	} else {
		sp->escaped &= ~PPP_RCV_VJCOMPR;
	}
	if (ipcpiop->rem_ip_compr == IPCP_VJCOMPR) {
		sp->escaped |= PPP_XMT_VJCOMPR;
		if (Ppptrace > 1)
			log(-1,"    Begin Van Jacobson TCP/IP header compression on outgoing packets");
	} else {
		sp->escaped &= ~PPP_XMT_VJCOMPR;
	}
	if ((sp->escaped & (PPP_RCV_VJCOMPR | PPP_XMT_VJCOMPR)) != 0) {
		/* Initialize compression control structure */
		sp->slcomp = (struct slcompress *)mallocw(sizeof(struct slcompress));
		sl_compress_init(sp->slcomp);
	}

	/* Set our IP address to reflect negotiated option */
	if (ipcpiop->attempt_src != sp->iface->addr) {
		if (Ip_addr == 0L)
			Ip_addr = ipcpiop->attempt_src;
		sp->iface->addr = ipcpiop->attempt_src;
		if (Ppptrace > 1)
			log(-1,"    Saving new IP addr for interface %s: %s",
				sp->iface->name,inet_ntoa(sp->iface->addr));
	}

	/* Add point-to-point route to remote PPP host */
	rt_add(ipcpiop->attempt_dest, (unsigned int)32, (int32)0,
		sp->iface, (int32)1, (int32)0, (char)1);
	if (Ppptrace > 1)
		log(-1,"    Add route to peer (%s) on interface %s",
			inet_ntoa(ipcpiop->attempt_dest),
			sp->iface->name);

/* Allow any queued IP traffic */
	pppiop->state = PPP_OPEN;
	pppiop->upsince = time(0L);
}

/* Reset IPCP configuration options for initial request */
int
ipcp_reset(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1, "ipcp_reset()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);
	ipcpiop->ipcp_state = IPCP_CLOSED;
	ipcpiop->ack_retry = 0;
	ipcpiop->active = pppiop->lcpio.active;

	/* Set up for IP addr exchange; attempt values set in ipcp_start() */
	ipcpiop->attempt_addrs = 1;
	ipcpiop->attempt_src = Ip_addr;
	/* If we dont know our IP address, or if we dont know IP addr */
	/* of the remote host, accept address assignment from remote  */
	if ((ipcpiop->attempt_src == 0L)||(ipcpiop->attempt_dest == 0L)) {
		ipcpiop->accept_addrs = 1;
	} else {
		ipcpiop->accept_addrs = 0;
	}
	ipcpiop->accept_src = ipcpiop->attempt_dest;
	ipcpiop->accept_dest = ipcpiop->attempt_src;

	/* Reset IP header compression options to first request defaults */
	ipcpiop->attempt_ip_compr = ipcpiop->neg_ip_compr;
	ipcpiop->lcl_ip_compr = ipcpiop->ip_compr_type;
	ipcpiop->accept_ip_compr = 1;
	ipcpiop->rem_ip_compr = DEF_IP_COMPR;
	sp->escaped &= ~PPP_RCV_VJCOMPR;
	sp->escaped &= ~PPP_XMT_VJCOMPR;

	return 0;
}

/*******************************************/

static int
ipcp_addr_idle(addr)
int32 addr;
{
	struct iface *ifp;
	struct pppctl *pppiop;

	/* Check if peer IP address is already in use on another interface */
	for (ifp=Ifaces; ifp != NULLIF; ifp = ifp->next) {
		if ((Slip[ifp->xdev].iface == ifp) &&
		    (Slip[ifp->xdev].type == CL_PPP)) {
			pppiop = Slip[ifp->xdev].pppio;
			if ((pppiop->ipcpio.ipcp_state != IPCP_CLOSED) &&
			    (pppiop->ipcpio.attempt_dest == addr))        {
				return 0;
			}
		}
	}
	return 1;
}

/* Check if we have a specific IP address to assign to remote peer host */
static int32
ipcp_lookuppeer(peerid)
char *peerid;
{
	FILE *peerfp;
	char buf[128];
	char *cp;
	int32 peer_addr = 0L;

	if ((peerfp = fopen(Userfile, READ_TEXT)) == NULLFILE)
		return 0L;
	while (fgets(buf,128,peerfp) != NULLCHAR) {
		if(buf[0] == '#')
			continue;	/* Comment */
		if((cp = strchr(buf,' ')) == NULLCHAR)
			/* Bogus entry */
			continue;
		*cp++ = '\0';		/* Now points to password */
		if(stricmp(peerid,buf) == 0) {
			++cp;
			if ((cp = strrchr(cp,' ')) == NULLCHAR)
				/* No IP address given */
				break;
			++cp;
			peer_addr = resolve(cp);
			break;		/* Found user name */
		}
	}
	fclose(peerfp);
	return(peer_addr);
}

static int32
ipcp_poolnext(poolp)
struct ipcppool *poolp;
{
	int i;
	int32 nextaddr = 0L;

	for (i=0; i<=(poolp->peer_max - poolp->peer_min); ++i) {
		if (ipcp_addr_idle(poolp->peer_next))
			nextaddr = poolp->peer_next;

		if (poolp->peer_next == poolp->peer_max)
			poolp->peer_next = poolp->peer_min;
		else
			++poolp->peer_next;

		if (nextaddr != 0L)
			break;
	}
	return(nextaddr);
}

/* Close all TCP connections in preparation for PPP link shutdown */
static void
ipcp_reset_tcp(sp)
struct slip *sp;
{
	register struct iface *ifp;
	register int i;
	register struct tcb *tcb;
	register struct route *rp;

	ifp = sp->iface;

	for(i=0;i<NTCB;i++){
		for(tcb=Tcbs[i];tcb != NULLTCB;tcb = tcb->next) {
			if ((tcb->state == TCP_LISTEN) &&
			    (tcb->flags.clone == 1))      {
				/* Leave servers alone */
				continue;
			}
			rp = rt_lookup(tcb->conn.remote.address);
			if (rp->iface == ifp) {
				/* Reset active connections */
				reset_tcp(tcb);
			}
		}
	}
	/* Wait 1sec for any replys or static */
	pause(1000L);
}

/****************************************************************************/

/* Send our IPCP configuration request */
static int
ipcp_sendreq(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;
	struct mbuf *bp;

	if (Ppptrace > 5)
		log(-1,"ipcp_sendreq()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* Get a packet with our configuration request */
	bp = ipcp_makereq(ipcpiop);

	/* Start timer against wait for reply to our config request */
	ipcp_timer(sp);

	/* Send IPCP configuration request to remote host */
	pppiop->state = PPP_IPCP;
	if (ipcpiop->ipcp_state != IPCP_ACK_SENT)
		ipcpiop->ipcp_state = IPCP_REQ_SENT;
	return(ipcp_sendreply(sp, CONFIG_REQ, 0, bp));
}

/*******************************************/

static struct mbuf *
ipcp_makereq(ipcpiop)
struct ipcpctl *ipcpiop;
{
	register char *cp;
	struct mbuf *bp;
	struct mbuf *req_bp = NULLBUF;

	if (Ppptrace > 5)
		log(-1,"    ipcp_makereq()");

	/* Request our preferred IP control options */
	if (ipcpiop->attempt_addrs) {
		/* Attempt to negotiate IP addrs */
		if (Ppptrace > 5) {
			log(-1,"    asking for src addr: %s",
				inet_ntoa(ipcpiop->attempt_src));
			log(-1,"    asking for dest addr: %s",
				inet_ntoa(ipcpiop->attempt_dest));
		}
		if ((bp = alloc_mbuf(10)) == NULLBUF)
			return NULLBUF;
		cp = bp->data;
		*cp++ = IP_ADDRS;
		*cp++ = 10;
		cp = put32(cp, ipcpiop->attempt_src);
		cp = put32(cp, ipcpiop->attempt_dest);
		bp->cnt += 10;
		append(&req_bp, bp);
	}

	/* IP header compression */
	if (ipcpiop->attempt_ip_compr) {
		if (Ppptrace > 5) {
			log(-1,"    asking for IP compression: %x",
				ipcpiop->lcl_ip_compr);
		}

		/* Attempt to negotiate IP compression */
		if ((bp = alloc_mbuf(4)) == NULLBUF)
			return NULLBUF;
		cp = bp->data;
		*cp++ = IP_COMPR_TYPE;
		*cp++ = 4;
		put16(cp, ipcpiop->lcl_ip_compr);
		bp->cnt += 4;
		append(&req_bp, bp);
	}

	/* Return our config request */
	return(req_bp);
}

/****************************************************************************/

/* Remote host ACKed our configuration request */
static void
ipcp_rcvack(sp, rcnf, data)
struct slip *sp;
struct cnfhdr *rcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1, "ipcp_rcvack()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);
	stop_timer(&ipcpiop->ipcp_tm);

	switch(ipcpiop->ipcp_state) {
	case IPCP_REQ_SENT:
		/* Make sure ACK is proper */
		if (ipcp_chkack(sp, rcnf, data) != -1) {
			/* Remote host accepted our request */
			ipcpiop->ipcp_state = IPCP_ACK_RCVD;
		}

		/* Still need to settle request from remote host */
		ipcp_timer(sp);
		break;

	case IPCP_ACK_SENT:
		/* Make sure ACK is proper */
		if (ipcp_chkack(sp, rcnf, data) == -1) {
			/* Error in ACK from remote host */
			/* Wait for another ACK, then send another request */
			ipcp_timer(sp);
		} else {
			/* IPCP negotiation complete */
			ipcp_open(sp);
		}
		break;

	case IPCP_ACK_RCVD:
	case IPCP_OPEN:
		/* Something went wrong; restart negotiations */
		free_p(data);	
		ipcp_reset(sp);
		ipcp_sendreq(sp);
		break;
	case IPCP_TERMINATE:
		/* We are attempting to close connection; wait */
		/* for timeout to resend a Terminate Request */
		free_p(data);
		break;
	case IPCP_CLOSED:
	case IPCP_LISTEN:
	default:
		/* Confusion; shutdown the connection */
		free_p(data);
		ipcp_shutdown(sp);
		break;
	}
}

/* Remote host NAKed our configuration request */
static void
ipcp_rcvnak(sp, rcnf, data)
struct slip *sp;
struct cnfhdr *rcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1, "ipcp_rcvnak()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);
	stop_timer(&ipcpiop->ipcp_tm);

	switch(ipcpiop->ipcp_state) {
	case IPCP_REQ_SENT:
	case IPCP_ACK_SENT:
		/* Update our config request to reflect NAKed options */
		if (ipcp_chknak(sp, rcnf, data) == -1) {
			/* Bad NAK packet */
			/* Wait for another; resend request on timeout */
			ipcp_timer(sp);
		} else {
			/* Send updated config request */
			ipcp_sendreq(sp);
		}
		break;

	case IPCP_ACK_RCVD:
	case IPCP_OPEN:
		/* Something went wrong; restart negotiations */
		free_p(data);	
		ipcp_reset(sp);
		ipcp_sendreq(sp);
		break;
	case IPCP_TERMINATE:
		/* We are attempting to close connection; wait */
		/* for timeout to resend a Terminate Request */
		free_p(data);
		break;
	case IPCP_CLOSED:
	case IPCP_LISTEN:
	default:
		/* Confusion; shutdown the connection */
		free_p(data);
		ipcp_shutdown(sp);
		break;
	}
}

/* Remote host rejected our configuration request */
static void
ipcp_rcvrej(sp, rcnf, data)
struct slip *sp;
struct cnfhdr *rcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1, "ipcp_rcvrej()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);
	stop_timer(&ipcpiop->ipcp_tm);

	switch(ipcpiop->ipcp_state) {
	case IPCP_REQ_SENT:
	case IPCP_ACK_SENT:
		/* Update our config request to reflect rejected options */
		if (ipcp_chkrej(sp, rcnf, data) == -1) {
			/* Bad reject packet */
			/* Wait for another; resend request on timeout */
			ipcp_timer(sp);
		} else {
			/* Send updated config request */
			ipcp_sendreq(sp);
		}
		break;

	case IPCP_ACK_RCVD:
	case IPCP_OPEN:
		/* Something went wrong; restart negotiations */
		free_p(data);	
		ipcp_reset(sp);
		ipcp_sendreq(sp);
		break;
	case IPCP_TERMINATE:
		/* We are attempting to close connection; wait */
		/* for timeout to resend a Terminate Request */
		free_p(data);
		break;
	case IPCP_CLOSED:
	case IPCP_LISTEN:
	default:
		/* Confusion; shutdown the connection */
		free_p(data);
		ipcp_shutdown(sp);
		break;
	}
}

/* Process configuration request sent by remote host */
static void
ipcp_rcvreq(sp, rcnf, data)
struct slip *sp;
struct cnfhdr *rcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1, "ipcp_rcvreq()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	switch(ipcpiop->ipcp_state) {
	case IPCP_LISTEN:	/* Normal event */
	case IPCP_ACK_SENT:	/* Unexpected event */
	case IPCP_OPEN:		/* Unexpected event */
		/* Reset IPCP state machine for configuration negotiation */
		ipcp_reset(sp);
		/* Send our configuration request */	
		ipcp_sendreq(sp);
		/* Evaluate configuration request from remote host */
		ipcp_chkreq(sp, rcnf, data);
		break;

	case IPCP_ACK_RCVD:
		/* Stop timer against wait for config request */
		stop_timer(&(ipcpiop->ipcp_tm));
	case IPCP_REQ_SENT:
		/* Evaluate configuration request from remote host */
		ipcp_chkreq(sp, rcnf, data);
		break;

	case IPCP_TERMINATE:
		/* We are attempting to close connection; wait */
		/* for timeout to resend a Terminate Request   */
		free_p(data);
		break;

	case IPCP_CLOSED:
	default:
		/* We are closed; dont accept any connections */
		free_p(data);
		ipcp_shutdown(sp);
		break;
	}
}

/* Remote host closed connection */
static void
ipcp_rcvtermack(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1,"ipcp_rcvtermack()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);
	stop_timer(&(ipcpiop->ipcp_tm));

	switch(ipcpiop->ipcp_state) {
	case IPCP_OPEN:
		/* Remote host has abruptly closed connection */
		/* Reset IP connections on this interface */
		ipcp_reset_tcp(sp);
		/* Remove routing entry from route table */
		rt_drop(ipcpiop->attempt_dest, (unsigned int)32);
		/* Fall through */
	case IPCP_TERMINATE:
		/* Remote host has responded to our terminate request */
		if (Ppptrace)
			log(-1,"%s: PPP/IPCP IP Closed",sp->iface->name);
		if (ipcpiop->active)
			ipcpiop->ipcp_state = IPCP_CLOSED;
		else
			ipcpiop->ipcp_state = IPCP_LISTEN;
		/* Prepare for next open */
		ipcp_reset(sp);
		/* We are done; signal anyone waiting for us to close */
		psignal(ipcpiop,0);
		break;
	case IPCP_REQ_SENT:
		/* Wait for timeout to restart attempt */
		break;
	case IPCP_ACK_SENT:
	case IPCP_ACK_RCVD:
		/* Something went wrong; restart negotiations */
		ipcp_reset(sp);
		ipcp_sendreq(sp);
		break;
	case IPCP_CLOSED:
	case IPCP_LISTEN:
	default:
		/* Unexpected, but no action needed */
		break;
	}
}

/* Remote peer requested that we close the IP layer */
static void
ipcp_rcvtermreq(sp, rcnf)
struct slip *sp;
struct cnfhdr *rcnf;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1, "ipcp_shutdown()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* Reset IP connections on this interface */
	ipcp_reset_tcp(sp);
	/* Remove routing entry from route table */
	rt_drop(ipcpiop->attempt_dest, (unsigned int)32);

	if (Ppptrace)
		log(-1,"%s: PPP/IPCP Peer requested close",sp->iface->name);
	ipcpiop->active = 0;
	ipcpiop->ipcp_state = IPCP_LISTEN;

	ipcp_sendreply(sp, TERMINATE_ACK, rcnf->id, NULLBUF);
	ipcp_reset(sp);
}

/* Shutdown the IPCP connection */
static void
ipcp_shutdown(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 5)
		log(-1, "ipcp_shutdown()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* Reset IP connections on this interface */
	ipcp_reset_tcp(sp);
	/* Remove routing entry from route table */
	rt_drop(ipcpiop->attempt_dest, (unsigned int)32);

	if (Ppptrace)
		log(-1,"%s: PPP/IPCP IP Shutdown",sp->iface->name);
	if (ipcpiop->active)
		ipcpiop->ipcp_state = IPCP_CLOSED;
	else
		ipcpiop->ipcp_state = IPCP_LISTEN;

	ipcp_sendreply(sp,TERMINATE_ACK,0,NULLBUF);
	ipcp_reset(sp);
}

/*******************************************/

/* Process configuration ACK send by remote host */
static int
ipcp_chkack(sp, ackcnf, data)
struct slip *sp;
struct cnfhdr *ackcnf;
struct mbuf *data;
{
	int ackerr = 0;
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;
	struct mbuf *req_bp;
	struct opthdr reqopt;
	struct opthdr ackopt;
	int16 reqi16, acki16;
	int32 reqsrc_ip, reqdest_ip, acksrc_ip, ackdest_ip;

	if (Ppptrace > 5)
		log(-1,"ipcp_chkack()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* IPCP ID field must match last request we sent */
	if (ackcnf->id != ipcpiop->lastid) {
		if (Ppptrace > 1)
			log(-1,"improper IPCP ACK; bad ID");
		free_p(data);
		return -1;
	}

	/* Get a copy of last request we sent */
	req_bp = ipcp_makereq(ipcpiop);

	/* 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 IP_ADDRS:		/* IP address */
			/* Echoed values must match our request */
			reqsrc_ip = pull32(&req_bp);
			reqdest_ip = pull32(&req_bp);
			acksrc_ip = pull32(&data);
			ackdest_ip = pull32(&data);

			if (acksrc_ip != reqsrc_ip) {
				ackerr = 1;
				break;
			}
			if (ackdest_ip != reqdest_ip) {
				ackerr = 1;
				break;
			}
			break;

		case IP_COMPR_TYPE:	/* IP header compr */
			/* Echoed values must match our request */
			reqi16 = pull16(&req_bp);
			acki16 = pull16(&data);

			if (reqi16 != acki16) {
				ackerr = 1;
				break;
			}
			break;
		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 IPCP ACK echo");
		return -1;
	}

	/* ACK matches last request we made */
	if (Ppptrace > 5)
		log(-1,"valid IPCP ACK echo");
	return 0;
}

/* Process configuration NAK send by remote host */
static int
ipcp_chknak(sp, nakcnf, data)
struct slip *sp;
struct cnfhdr *nakcnf;
struct mbuf *data;
{
	int nakerr = 0;
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;
	struct mbuf *req_bp;
	struct opthdr reqopt;
	struct opthdr nakopt;
	int16 naki16;
	int32 naksrc_ip, nakdest_ip;

	if (Ppptrace > 5)
		log(-1,"ipcp_chknak()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* IPCP ID field must match last request we sent */
	if (nakcnf->id != ipcpiop->lastid) {
		if (Ppptrace > 1)
			log(-1,"improper IPCP NAK; bad ID");
		free_p(data);
		return -1;
	}

	/* Get a copy of last request we sent */
	req_bp = ipcp_makereq(ipcpiop);

	/* 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 IP_ADDRS:		/* IP address */
			/* Get replacement value from NAK packet */
			naksrc_ip = pull32(&data);
			nakdest_ip = pull32(&data);
			/* Eat option value from our request packet */
			pull32(&req_bp);
			pull32(&req_bp);

			/* Ignore remote if we want to control addrs */
			if (ipcpiop->accept_addrs != 0) {
				/* We asked remote for our addresses */
				if (ipcpiop->attempt_src == 0L)
					ipcpiop->attempt_src = naksrc_ip;
				if (ipcpiop->attempt_dest == 0L)
					ipcpiop->attempt_dest = nakdest_ip;
			}
			break;
		case IP_COMPR_TYPE:	/* IP header compr */
			/* Get replacement value from NAK packet */
			naki16 = pull16(&data);
			/* Eat option value from our request packet */
			pull16(&req_bp);

			/* See if we can do this type of IP compression */
			if (naki16 == IPCP_VJCOMPR) {
				/* Ask for Van Jacobson TCP compression */
				ipcpiop->lcl_ip_compr = IPCP_VJCOMPR;
			} else {
				/* Cant do that type of compr, ask for none */
				ipcpiop->lcl_ip_compr = DEF_IP_COMPR;
			}
			break;
		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 IPCP NAK echo");
		return -1;
	}

	/* NAK matches last request we made */
	if (Ppptrace > 5)
		log(-1,"valid IPCP NAK echo");
	return 0;
}

/* Process configuration reject send by remote host */
static int
ipcp_chkrej(sp, rejcnf, data)
struct slip *sp;
struct cnfhdr *rejcnf;
struct mbuf *data;
{
	int rejerr = 0;
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;
	struct mbuf *req_bp;
	struct opthdr reqopt;
	struct opthdr rejopt;

	if (Ppptrace > 5)
		log(-1,"ipcp_chkrej()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* IPCP ID field must match last request we sent */
	if (rejcnf->id != ipcpiop->lastid) {
		if (Ppptrace > 1)
			log(-1,"improper IPCP REJ; bad ID");
		free_p(data);
		return -1;
	}

	/* Get a copy of last request we sent */
	req_bp = ipcp_makereq(ipcpiop);

	/* 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 IP_ADDRS:		/* IP address */
			/* Eat option values from each packet */
			pull32(&req_bp);
			pull32(&req_bp);
			pull32(&data);
			pull32(&data);

			/* Abandon attempt to negotiate IP addrs */
			ipcpiop->attempt_addrs = 0;
			break;
		case IP_COMPR_TYPE:	/* IP header compression */
			/* Eat option values from each packet */
			pull16(&req_bp);
			pull16(&data);

			/* Abandon attempt to negotiate IP compression */
			ipcpiop->attempt_ip_compr = 0;
			ipcpiop->lcl_ip_compr = DEF_IP_COMPR;
			break;
		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 IPCP REJ echo");
		return -1;
	}

	/* Reject matches last request we made */
	if (Ppptrace > 5)
		log(-1,"valid IPCP REJ echo");
	return 0;
}

/* Check IP Control options requested by the remote host */
static void
ipcp_chkreq(sp, reqcnf, data)
struct slip *sp;
struct cnfhdr *reqcnf;
struct mbuf *data;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;
	int ilen;
	int16 i16;
	int32 reqsrc_ip, reqdest_ip;
	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 mbuf *bp;			/* Ptr for building reply */
	struct mbuf *reply_bp = NULLBUF;	/* Actual reply packet */

	if (Ppptrace > 5)
		log(-1, "ipcp_chkreq()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* Make sure length in IPCP config header is realistic */
	ilen = len_p(data);
	if (ilen < reqcnf->len)
		reqcnf->len = ilen;

	/* 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 IP_ADDRS:		/* IP address */
			if (Ppptrace > 5)
				log(-1, "remote asking to negotiate IP addrs");

			/* IP Addr is 2 32bit fields */
			ilen = 8;
			if (reqopt.len < ilen) {
				/* Short option data; reject packet */
				opt_accept = CONFIG_REJ;
				break;
			}

			/* Get proposed value from packet */
			reqsrc_ip = pull32(&data);
			reqdest_ip = pull32(&data);
			if (Ppptrace > 5) {
				log(-1,	"src IP addr: %s",
					inet_ntoa(reqsrc_ip));
				log(-1,	"dest IP addr: %s",
					inet_ntoa(reqdest_ip));

			}

			/* Check requested IP addresses */
			if (ipcpiop->accept_addrs == 0) {
				/* Request okay if it matches what we want */
				if ((reqsrc_ip == ipcpiop->attempt_dest)
				    && (reqdest_ip == ipcpiop->attempt_src)) {
					opt_accept = CONFIG_ACK;	
					/* Save these values for later */
					ipcpiop->accept_src = reqsrc_ip;
					ipcpiop->accept_dest = reqdest_ip;
					break;
				}
				if ((reqsrc_ip == ipcpiop->accept_src)
				    && (reqdest_ip == ipcpiop->accept_dest)) {
					/* We already NAKed these once */
					opt_accept = CONFIG_REJ;
					break;
				}

				/* Cant accept suggestion of remote host */
				opt_accept = CONFIG_NAK;
				/* Save these values in case remote insists */
				ipcpiop->accept_src = reqsrc_ip;
				ipcpiop->accept_dest = reqdest_ip;
				/* Tell remote what to request instead */
				reqsrc_ip = ipcpiop->attempt_dest;
				reqdest_ip = ipcpiop->attempt_src;
				break;
			}

			/* Make sure remote is not repeating itself */
			if ((reqsrc_ip == ipcpiop->accept_src)
			    && (reqdest_ip == ipcpiop->accept_dest)) {
				/* We already NAKed these once */
				opt_accept = CONFIG_REJ;
				break;
			}

			/* Save these values for later */
			ipcpiop->accept_src = reqsrc_ip;
			ipcpiop->accept_dest = reqdest_ip;
			/* Remote host may request IP addrs */
			if(reqsrc_ip == 0L) {
				/* Remote host request its IP addr */
				if (ipcpiop->attempt_dest == 0L) {
					/* We dont have an addr for remote */
					opt_accept = CONFIG_REJ;
					break;
				} else {
					/* Give IP addr to remote peer */
					opt_accept = CONFIG_NAK;
					reqsrc_ip = ipcpiop->attempt_dest;
				}
			} else {
				/* Remote host gave us its IP addr */
				if (ipcpiop->attempt_dest == 0L) {
					/* We need to know peer IP addr */
					ipcpiop->attempt_dest = reqsrc_ip;
				} else {
					/* We already know peer IP addr */
					if (reqsrc_ip != ipcpiop->attempt_dest) {
						opt_accept = CONFIG_NAK;
						reqsrc_ip = ipcpiop->attempt_dest;
					}
				}
			}
			if (reqdest_ip == 0L) {
				/* Remote host requested our IP addr */
				if (ipcpiop->attempt_src == 0L) {
					/* We dont know our IP addr */
					opt_accept = CONFIG_REJ;
					break;
				} else {
					/* Tell remote what our IP addr is */
					opt_accept = CONFIG_NAK;
					reqdest_ip = ipcpiop->attempt_src;
				}
			} else {
				/* Remote host gave us our IP addr */
				if (ipcpiop->attempt_src == 0L) {
					/* We need to know our IP addr */
					ipcpiop->attempt_src = reqdest_ip;
				} else {
					/* We already know our IP addr */
					if (reqdest_ip != ipcpiop->attempt_src) {
						opt_accept = CONFIG_NAK;
						reqdest_ip = ipcpiop->attempt_src;
					}
				}
			}
			break;

		case IP_COMPR_TYPE:	/* IP header compression */
			if (Ppptrace > 5) {
				log(-1, "remote asking to negotiate IP compression");
			}

			/* IP compression type is a 16 bit field */
			ilen = 2;
			if (reqopt.len < ilen) {
				/* Short option; reject packet */
				opt_accept = CONFIG_REJ;
				break;
			}

			/* Get proposed value from packet */
			i16 = pull16(&data);
			if ((ipcpiop->accept_ip_compr == 0)
				&& (i16 != DEF_IP_COMPR)) {
				/* Not open for negotiation */
				opt_accept = CONFIG_REJ;
				break;
			}

			/* Check if requested compr type is acceptable */
			if (i16 == IPCP_VJCOMPR) {
				/* We can do Van Jacobson TCP header compr */
				opt_accept = CONFIG_ACK;
			} else {
				/* Dont know this compr type;
				 * suggest Van Jacobson TCP header compr
				 */
				opt_accept = CONFIG_NAK;
				i16 = IPCP_VJCOMPR;
			}
			ipcpiop->rem_ip_compr = i16;
			break;

		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);
			/* Data 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 8:
				cp = put32(cp, reqsrc_ip);
				cp = put32(cp, reqdest_ip);
				bp->cnt += 8;
				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 */
		ipcp_sendreply(sp, CONFIG_ACK, reqcnf->id, reply_bp);
		if (ipcpiop->ipcp_state == IPCP_REQ_SENT) {
			ipcpiop->ipcp_state = IPCP_ACK_SENT;
		} else {
			/* PPP data link now ready for IP traffic */
			ipcp_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 */
		ipcp_sendreply(sp, cnf_accept, reqcnf->id, reply_bp);

		/* Start timer against wait for amended config request */
		if (ipcpiop->ipcp_state == IPCP_ACK_RCVD)
			ipcp_timer(sp);
	}
	free_p(data);
}

/****************************************************************************/

/* Timeout while waiting for reply from remote host */
static void
ipcp_timeout(vp)
void *vp;
{
	struct slip *sp;
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;

	if (Ppptrace > 1)
		log(-1, "ipcp_timeout()");

	/* Load pointers to interface that timed-out */
	sp = (struct slip *)vp;
	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);

	/* Attempt to get things going again */
	switch(ipcpiop->ipcp_state) {
	case IPCP_ACK_SENT:
		/* Remote host isnt listening to our request */
		ipcp_reset(sp);
	case IPCP_REQ_SENT:
	case IPCP_ACK_RCVD:
		if (!ipcpiop->active) {
			/* If passive open, we got a CONFIG_REQ from remote */
			/* host but no ACK in response to our CONFIG_REQ    */
			if (++ipcpiop->ack_retry > IPCP_RETRY_MAX) {
				/* Remote host doesnt seem to be listening */
				ipcp_shutdown(sp);
				break;
			}
		}
		/* Timeout waiting for ACK to our request,         */
		/* or timeout waiting for request from remote host */
		ipcp_sendreq(sp);
		break;

	case IPCP_TERMINATE:
		/* Timeout waiting for terminate ACK; send another request */
		if (++ipcpiop->ack_retry > IPCP_TERM_RETRY) {
			/* No response to our polite request; give it up */
			if (Ppptrace)
				log(-1,"%s: PPP/IPCP IP Closed without reply from remote peer",sp->iface->name);
			if (ipcpiop->active)
				ipcpiop->ipcp_state = IPCP_CLOSED;
			else
				ipcpiop->ipcp_state = IPCP_LISTEN;

			ipcp_sendreply(sp,TERMINATE_ACK,0,NULLBUF);
			ipcp_reset(sp);
			tprintf("Still no response; marking IPCP layer as closed\n");
			psignal(ipcpiop,0);
		} else {
			/* Request remote host to close IP */
			tprintf("Timeout waiting for response to our IPCP terminate request\n");
			ipcp_timer(sp);
			ipcp_sendreply(sp, TERMINATE_REQ, 0, NULLBUF);
		}
		break;

	case IPCP_CLOSED:
	case IPCP_LISTEN:
	case IPCP_OPEN:
	default:
		/* Confusion; shutdown the connection */
		ipcp_shutdown(sp);
		break;
	}
}

/* Set a timer in case an expected event does not occur */
static void
ipcp_timer(sp)
struct slip *sp;
{
	struct pppctl *pppiop;
	struct ipcpctl *ipcpiop;
	struct timer *t;

	if (Ppptrace > 5)
		log(-1,"ipcp_timer()");

	pppiop = sp->pppio;
	ipcpiop = &(pppiop->ipcpio);
	t = &(ipcpiop->ipcp_tm);
	t->func = (void (*)())ipcp_timeout;
	t->arg = (void *)sp;
	start_timer(t);
}

/****************************************************************************/

/* Send an IPCP packet to the remote host */
static int
ipcp_sendreply(sp,code,id,data)
struct slip *sp;
char code;
unsigned char id;
struct mbuf *data;
{
	struct iface *iface;
	struct cnfhdr hdr;

	/* Load IPCP header values */
	hdr.code = code;
	switch(code) {
	case CONFIG_REQ:
	case TERMINATE_REQ:
		/* Save ID field for match aginst replies from remote host */
		sp->pppio->ipcpio.lastid = Pppid;
		/* 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:
		/* Use ID sent by remote host */
		hdr.id = id;
		break;

	default:
		/* Shouldnt happen */
		if (Ppptrace)
			log(-1, "bogus code: %x\n", code);
		return -1;
	}
	hdr.len = len_p(data) + CNF_HDRLEN;

	/* Prepend IPCP header to packet data */
	if ((data = htoncnf(&hdr,data)) == NULLBUF)
		return -1;

	if (Ppptrace > 1)
		log(-1, "%s: PPP/IPCP Send: current state: %s   IPCP option: %s  id: %d  len: %d",
			sp->iface->name,
			LCPStates[sp->pppio->ipcpio.ipcp_state],
			LCPCodes[code],hdr.id,hdr.len);

	/* Send IPCP packet to remote host */
	sp->pppio->sndipcp++;
	iface = sp->iface;
	return( (*iface->output)
		(iface, NULLCHAR, NULLCHAR, PPP_IPCP_TYPE, data) );
}

/* Process incoming IPCP packet */
void
ipcpproc(iface,bp)
struct iface *iface;
struct mbuf *bp;
{
	struct slip *sp;
	struct cnfhdr hdr;

	sp = &Slip[iface->xdev];

	/* Extract IPCP 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/IPCP Recv: current state: %s   IPCP option: %s    id: %d   len: %d",
			iface->name,
			LCPStates[sp->pppio->ipcpio.ipcp_state],
			LCPCodes[hdr.code], hdr.id, hdr.len);

	/* Process IPCP packet data */
	switch(hdr.code) {
	case CONFIG_REQ:			/* Request of remote host */
		ipcp_rcvreq(sp, &hdr, bp);
		break;
	case CONFIG_ACK:			/* Remote accepted our req */
		ipcp_rcvack(sp, &hdr, bp);
		break;
	case CONFIG_NAK:			/* Remote adjusted our req */
		ipcp_rcvnak(sp, &hdr, bp);
		break;
	case CONFIG_REJ:			/* Remote rejected our req */
		ipcp_rcvrej(sp, &hdr, bp);
		break;
	case TERMINATE_REQ:			/* Remote request to close */
		ipcp_rcvtermreq(sp, &hdr);
		break;
	case TERMINATE_ACK:			/* Remote closed on request */
		ipcp_rcvtermack(sp);
		break;

	case CODE_REJ:
		if (Ppptrace)
			log(-1,"Unimplemented IPCP packet type: %x; dropping packet",hdr.code);
		free_p(bp);
		break;
	default:
		if (Ppptrace)
			log(-1,"Unknown IPCP packet type: %x; dropping packet",hdr.code);
		free_p(bp);
		break;
	}
}
