/*
 * ipcp.c - PPP IP Control Protocol.
 *
 * Copyright (c) 1989 Carnegie Mellon University.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Carnegie Mellon University.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/*
 * TODO:
 * Fix IP address negotiation (wantoptions or hisoptions).
 * Don't set zero IP addresses.
 * Send NAKs for unsent CIs.
 * VJ compression.
 */

#include <stdio.h>
#include <syslog.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/stream.h>
#include "ppp.h"
#include "fsm.h"
#include "ipcp.h"

/* global vars */
ipcp_options ipcp_wantoptions[NPPP]; /* Options that we want to request */
ipcp_options ipcp_gotoptions[NPPP]; /* Options that peer ack'd */
ipcp_options ipcp_allowoptions[NPPP]; /* Options that we allow peer to
					 request */
ipcp_options ipcp_hisoptions[NPPP]; /* Options that we ack'd */

int	ipcp_negaddr_mode = IPCP_NEGADDR_NEW; /* how we do addrs */

/* local vars */

/*
 * VJ compression protocol mode for negotiation. See ipcp.h for a 
 * description of each mode.
 */
static int vj_mode = IPCP_VJMODE_CURRENT;

static int vj_opt_len = 4;	/* holds length in octets for valid vj */
				/* compression frame depending on mode */

static int vj_opt_val = IPCP_VJ_COMP;
				/* compression negotiation frames */
				/* depending on vj_mode */

static void ipcp_resetci __ARGS((fsm *));	/* Reset our Configuration Information */
static int ipcp_cilen __ARGS((fsm *));	        /* Return length of our CI */
static void ipcp_addci __ARGS((fsm *, u_char *));	/* Add our CIs */
static int ipcp_ackci __ARGS((fsm *, u_char *, int));	/* Ack some CIs */
static void ipcp_nakci __ARGS((fsm *, u_char *, int));	/* Nak some CIs */
static void ipcp_rejci __ARGS((fsm *, u_char *, int));	/* Reject some CIs */
static u_char ipcp_reqci __ARGS((fsm *, u_char *, int *));	/* Check the requested CIs */
static void ipcp_up __ARGS((fsm *));		/* We're UP */
static void ipcp_down __ARGS((fsm *));	/* We're DOWN */


static fsm ipcp_fsm[NPPP];	/* IPCP fsm structure */

static fsm_callbacks ipcp_callbacks = { /* IPCP callback routines */
    ipcp_resetci,		/* Reset our Configuration Information */
    ipcp_cilen,			/* Length of our Configuration Information */
    ipcp_addci,			/* Add our Configuration Information */
    ipcp_ackci,			/* ACK our Configuration Information */
    ipcp_nakci,			/* NAK our Configuration Information */
    ipcp_rejci,			/* Reject our Configuration Information */
    ipcp_reqci,			/* Request peer's Configuration Information */
    ipcp_up,			/* Called when fsm reaches OPEN state */
    ipcp_down,			/* Called when fsm leaves OPEN state */
    NULL,			/* Called when fsm reaches CLOSED state */
    NULL,			/* Called when Protocol-Reject received */
    NULL			/* Retransmission is necessary */
};


/*
 * ipcp_init - Initialize IPCP.
 */
void
  ipcp_init(unit)
int unit;
{
    fsm *f = &ipcp_fsm[unit];
    ipcp_options *wo = &ipcp_wantoptions[unit];
    ipcp_options *ao = &ipcp_allowoptions[unit];

    f->unit = unit;
    f->protocol = IPCP;
    f->timeouttime = DEFTIMEOUT;
    f->maxtermtransmits = DEFMAXTERMTRANSMITS;
    f->maxnakloops = DEFMAXNAKLOOPS;
    f->callbacks = &ipcp_callbacks;

    wo->neg_addrs = ipcp_negaddr_mode;
    wo->ouraddr = 0;

    wo->hisaddr = 0;
    wo->neg_vj = 1;
    wo->maxslotindex = MAX_STATES - 1; /* really max index */
    wo->cflag = 1;

    /* max slots and slot-id compression are currently hardwired in */
    /* ppp_if.c to 16 and 1, this needs to be changed (among other */
    /* things) gmc */

    ao->neg_addrs = ipcp_negaddr_mode;
    ao->neg_vj = 1;
    ao->maxslotindex = MAX_STATES - 1;
    ao->cflag = 1;
    fsm_init(&ipcp_fsm[unit]);
}

/*
 * ipcp_vj_setmode - set option length and option value for vj
 * compression negotiation frames depending on mode
 */

void
  ipcp_vj_setmode(mode)
int mode;    
{
  vj_mode = mode;

  switch (vj_mode) {

  case IPCP_VJMODE_OLD:
    vj_opt_len = 4;
    vj_opt_val = IPCP_VJ_COMP_OLD;
    break;

  case IPCP_VJMODE_CURRENT:
    vj_opt_len = 4;
    vj_opt_val = IPCP_VJ_COMP;
    break;

  case IPCP_VJMODE_DRAFT:     /* draft mode vj compression */          
    vj_opt_len = 6;	      /* negotiation includes values for */    
                              /* maxslot and slot number compression */
    vj_opt_val = IPCP_VJ_COMP;
    break;

  default:
    IPCPDEBUG((LOG_WARNING, "Unknown vj compression mode %d.  Please report \
this error.", vj_mode))
    break;
  }

}
/*
 * ipcp_activeopen - Actively open IPCP.
 */
void
  ipcp_activeopen(unit)
int unit;
{
    fsm_activeopen(&ipcp_fsm[unit]);
}


/*
 * ipcp_passiveopen - Passively open IPCP.
 */
void ipcp_passiveopen(unit)
    int unit;
{
    fsm_passiveopen(&ipcp_fsm[unit]);
}


/*
 * ipcp_close - Close IPCP.
 */
void
  ipcp_close(unit)
int unit;
{
    fsm_close(&ipcp_fsm[unit]);
}


/*
 * ipcp_lowerup - The lower layer is up.
 */
void
  ipcp_lowerup(unit)
int unit;
{
    fsm_lowerup(&ipcp_fsm[unit]);
}


/*
 * ipcp_lowerdown - The lower layer is down.
 */
void
  ipcp_lowerdown(unit)
int unit;
{
    fsm_lowerdown(&ipcp_fsm[unit]);
}


/*
 * ipcp_input - Input IPCP packet.
 */
void
  ipcp_input(unit, p, len)
int unit;
u_char *p;
int len;
{
    fsm_input(&ipcp_fsm[unit], p, len);
}


/*
 * ipcp_protrej - A Protocol-Reject was received for IPCP.
 *
 * Simply pretend that LCP went down.
 */
void
  ipcp_protrej(unit)
int unit;
{
    fsm_lowerdown(&ipcp_fsm[unit]);
}


/*
 * ipcp_resetci - Reset our CI.
 */
static void
  ipcp_resetci(f)
fsm *f;
{
    ipcp_gotoptions[f->unit] = ipcp_wantoptions[f->unit];
}


/*
 * ipcp_cilen - Return length of our CI.
 */
static int
  ipcp_cilen(f)
fsm *f;
{
    ipcp_options *go = &ipcp_gotoptions[f->unit];


#define LENCIADDR(neg)  (neg ? 6 : 0)

#define LENCISHORT(neg)  (neg ? vj_opt_len : 0)

#define LENCIADDRS(neg)  (neg ? 10 : 0)

    return (LENCIADDR((go->neg_addrs == IPCP_NEGADDR_NEW)) +
	    LENCISHORT(go->neg_vj) +
	    LENCIADDRS((go->neg_addrs == IPCP_NEGADDR_OLD)));
}


/*
 * ipcp_addci - Add our desired CIs to a packet.
 */
static void
  ipcp_addci(f, ucp)
fsm *f;
u_char *ucp;
{
    ipcp_options *go = &ipcp_gotoptions[f->unit];


#define ADDCISHORT(opt, neg, val, maxslotindex, cflag) \
    if (neg) { \
	PUTCHAR(opt, ucp); \
	PUTCHAR(vj_opt_len, ucp); \
	PUTSHORT(val, ucp); \
	if (vj_mode == IPCP_VJMODE_DRAFT) { \
	   PUTCHAR(maxslotindex, ucp); \
	   PUTCHAR(cflag, ucp); \
	} \
    }

#define ADDCIADDR(opt, neg, addr) \
    if (neg) { \
	u_long l; \
	PUTCHAR(opt, ucp); \
	PUTCHAR(2 + sizeof (long), ucp); \
	l = ntohl(addr); \
	PUTLONG(l, ucp); \
    }

#define ADDCIADDRS(opt, neg, val1, val2) \
    if (neg) { \
	u_long l; \
	PUTCHAR(opt, ucp); \
	PUTCHAR(2 + 2 * sizeof (long), ucp); \
	l = ntohl(val1); \
	PUTLONG(l, ucp); \
	l = ntohl(val2); \
	PUTLONG(l, ucp); \
    }

    ADDCIADDR(CI_ADDR, (go->neg_addrs == IPCP_NEGADDR_NEW), go->ouraddr)

    ADDCISHORT(CI_COMPRESSTYPE, go->neg_vj, vj_opt_val, go->maxslotindex, go->cflag)

    ADDCIADDRS(CI_ADDRS, (go->neg_addrs == IPCP_NEGADDR_OLD), go->ouraddr, go->hisaddr)

}


/*
 * ipcp_ackci - Ack our CIs.
 *
 * Returns:
 *	0 - Ack was bad.
 *	1 - Ack was good.
 */
static int
  ipcp_ackci(f, p, len)
fsm *f;
u_char *p;
int len;
{
    ipcp_options *go = &ipcp_gotoptions[f->unit];
    u_short cilen, citype, cishort;
    u_long cilong;
    u_char cimaxslotindex, cicflag;
    /*
     * CIs must be in exactly the same order that we sent...
     * Check packet length and CI length at each step.
     * If we find any deviations, then this packet is bad.
     */
#define ACKCISHORT(opt, neg, val, maxslotindex, cflag) \
    if (neg) { \
	if ((len -= vj_opt_len) < 0) \
	    goto bad; \
	GETCHAR(citype, p); \
	GETCHAR(cilen, p); \
	if (cilen != vj_opt_len || \
	    citype != opt)  \
	    goto bad; \
	GETSHORT(cishort, p); \
	if (cishort != val) \
	    goto bad; \
	if (vj_mode == IPCP_VJMODE_DRAFT) { \
	  GETCHAR(cimaxslotindex, p); \
	  if (cimaxslotindex > maxslotindex) \
	    goto bad; \
	  GETCHAR(cicflag, p); \
	  if (cicflag != cflag) \
	    goto bad; \
	} \
    }

#define ACKCIADDR(opt, neg, addr) \
    if (neg) { \
	u_long l; \
	if ((len -= 2 + sizeof (long)) < 0) \
	    goto bad; \
	GETCHAR(citype, p); \
	GETCHAR(cilen, p); \
	if (cilen != 2 + sizeof (long) || \
	    citype != opt) \
	    goto bad; \
	GETLONG(l, p); \
	cilong = htonl(l); \
	if (addr) { \
	    if (addr != cilong) \
		goto bad; \
	} \
	else \
	    addr = cilong; \
    }

#define ACKCIADDRS(opt, neg, val1, val2) \
    if (neg) { \
	u_long l; \
	if ((len -= 2 + 2 * sizeof (long)) < 0) \
	    goto bad; \
	GETCHAR(citype, p); \
	GETCHAR(cilen, p); \
	if (cilen != 2 + 2 * sizeof (long) || \
	    citype != opt) \
	    goto bad; \
	GETLONG(l, p); \
	cilong = htonl(l); \
	if (val1) { \
	    if (val1 != cilong) \
		goto bad; \
	} \
	else \
	    val1 = cilong; \
	GETLONG(l, p); \
	cilong = htonl(l); \
	if (val2) { \
	    if (val2 != cilong) \
		goto bad; \
	} \
	else \
	    val2 = cilong; \
    }

    ACKCIADDR(CI_ADDR, (go->neg_addrs == IPCP_NEGADDR_NEW), go->ouraddr)
    ACKCISHORT(CI_COMPRESSTYPE, go->neg_vj, vj_opt_val, go->maxslotindex, go->cflag)
    ACKCIADDRS(CI_ADDRS, (go->neg_addrs == IPCP_NEGADDR_OLD), go->ouraddr, go->hisaddr)
    /*
     * If there are any remaining CIs, then this packet is bad.
     */
    if (len != 0)
	goto bad;
    return (1);
bad:
    IPCPDEBUG((LOG_INFO, "ipcp_ackci: received bad Ack!"))

    if (vj_mode == IPCP_VJMODE_DRAFT )
      IPCPDEBUG((LOG_INFO, "ipcp_ackci: citype %d, cilen %l",
		 citype, cilen))

    if (citype == CI_COMPRESSTYPE)  {
      IPCPDEBUG((LOG_INFO, "ipcp_ackci: compress_type %d", cishort))
      if (vj_mode == IPCP_VJMODE_DRAFT)
	IPCPDEBUG((LOG_INFO, ", maxslotindex %d, cflag %d", cishort, cimaxslotindex, cicflag))
    }
    return (0);
}

/*
 * ipcp_nakci - NAK some of our CIs.
 *
 * Returns:
 *	0 - Nak was bad.
 *	1 - Nak was good.
 */
static void
  ipcp_nakci(f, p, len)
fsm *f;
u_char *p;
int len;
{
    ipcp_options *go = &ipcp_gotoptions[f->unit];
    u_char cimaxslotindex, cicflag;
    u_short cishort;
    u_long ciaddr1, ciaddr2;

    /*
     * Any Nak'd CIs must be in exactly the same order that we sent.
     * Check packet length and CI length at each step.
     * If we find any deviations, then this packet is bad.
     */
#define NAKCISHORT(opt, neg, code) \
    if (neg && \
	len >= vj_opt_len && \
	p[1] == vj_opt_len && \
	p[0] == opt) { \
	  len -= vj_opt_len; \
	  INCPTR(2, p); \
	  GETSHORT(cishort, p); \
	  if (vj_mode == IPCP_VJMODE_DRAFT) { \
	     GETCHAR(cimaxslotindex, p); \
             GETCHAR(cicflag, p); \
          } \
          code \
    }

#define NAKCIADDR(opt, neg, code) \
    if (neg && \
	len >= 2 + sizeof (long) && \
	p[1] == 2 + sizeof (long) && \
	p[0] == opt) { \
	u_long l; \
	len -= 2 + sizeof (long); \
	INCPTR(2, p); \
	GETLONG(l, p); \
	ciaddr1 = htonl(l); \
	code \
    }

#define NAKCIADDRS(opt, neg, code) \
    if (neg && \
	len >= 2 + 2 * sizeof (long) && \
	p[1] == 2 + 2 * sizeof (long) && \
	p[0] == opt) { \
	u_long l; \
	len -= 2 + 2 * sizeof (long); \
	INCPTR(2, p); \
	GETLONG(l, p); \
	ciaddr1 = htonl(l); \
	GETLONG(l, p); \
	ciaddr2 = htonl(l); \
	code \
    }

    NAKCIADDR(CI_ADDR, (go->neg_addrs == IPCP_NEGADDR_NEW),
	       if (!go->ouraddr)	/* Didn't know our address? */
		   go->ouraddr = ciaddr1;
	       )
    NAKCISHORT(CI_COMPRESSTYPE, go->neg_vj,
       	       if (cishort != vj_opt_val)
	          goto bad;
	       go->maxslotindex = cimaxslotindex; /* this is what it */
	       go->cflag = cicflag;		  /* wants  */

	       )
    /* this may be an appended nak option, so we check for it in either case */
    NAKCIADDRS(CI_ADDRS, go->neg_addrs,
	       if (!go->ouraddr)	/* Didn't know our address? */
		   go->ouraddr = ciaddr1;
	       if (ciaddr2)		/* Does he know his? */
	           go->hisaddr = ciaddr2;
	       go->neg_addrs = IPCP_NEGADDR_OLD; /* he wants the old way */
	       )
    /*
     * If there are any remaining CIs, then this packet is bad.
     */
    if (len == 0)
	return;
bad:
    IPCPDEBUG((LOG_INFO, "ipcp_nakci: received bad Nak!"))
}


/*
 * ipcp_rejci - Reject some of our CIs.
 */
static void
  ipcp_rejci(f, p, len)
fsm *f;
u_char *p;
int len;
{
    ipcp_options *go = &ipcp_gotoptions[f->unit];
    u_char cimaxslotindex, ciflag;
    u_short cishort;
    u_long cilong;

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

#define REJCIADDR(opt, neg, addr) \
    if (neg && \
	len >= 2 + sizeof (long) && \
	p[1] == 2 + sizeof (long) && \
	p[0] == opt) { \
	u_long l; \
	len -= 2 + sizeof (long); \
	INCPTR(2, p); \
	GETLONG(l, p); \
	cilong = htonl(l); \
	/* Check rejected value. */ \
	if (cilong != addr) \
	    goto bad; \
	neg = IPCP_NEGADDR_OLD; \
    }

#define REJCIADDRS(opt, neg, val1, val2) \
    if (neg && \
	len >= 2 + 2 * sizeof (long) && \
	p[1] == 2 + 2 * sizeof (long) && \
	p[0] == opt) { \
	u_long l; \
	len -= 2 + 2 * sizeof (long); \
	INCPTR(2, p); \
	GETLONG(l, p); \
	cilong = htonl(l); \
	/* Check rejected value. */ \
	if (cilong != val2) \
	    goto bad; \
	GETLONG(l, p); \
	cilong = htonl(l); \
	/* Check rejected value. */ \
	if (cilong != val1) \
	    goto bad; \
	neg = 0; \
    }

    REJCIADDR(CI_ADDR, go->neg_addrs, go->ouraddr)
    REJCISHORT(CI_COMPRESSTYPE, go->neg_vj, vj_opt_val, go->maxslotindex, go->cflag)
    REJCIADDRS(CI_ADDRS, go->neg_addrs, go->ouraddr, go->hisaddr)

    /*
     * If there are any remaining CIs, then this packet is bad.
     */
    if (len == 0)
	return;
bad:
    IPCPDEBUG((LOG_INFO, "ipcp_rejci: received bad Reject!"))
}


/*
 * ipcp_reqci - Check the peer's requested CIs and send appropriate response.
 *
 * Returns: CONFACK, CONFNAK or CONFREJ and input packet modified
 * appropriately.
 */
static u_char
  ipcp_reqci(f, inp, len)
fsm *f;
u_char *inp;		/* Requested CIs */
int *len;			/* Length of requested CIs */
{
    ipcp_options *wo = &ipcp_wantoptions[f->unit];
    ipcp_options *ho = &ipcp_hisoptions[f->unit];
    ipcp_options *ao = &ipcp_allowoptions[f->unit];
    u_char *cip;		/* Pointer to Current CI */
    u_short cilen, citype;	/* Parsed len, type */
    u_short cishort;		/* Parsed short value */
    u_long tl, ciaddr1, ciaddr2;	/* Parsed address values */
    int rc = CONFACK;		/* Final packet return code */
    int orc;			/* Individual option return code */
    u_char *p = inp;		/* Pointer to next char to parse */
    u_char *ucp = inp;		/* Pointer to current output char */
    int l = *len;		/* Length left */
    u_char maxslotindex, cflag;

    /*
     * Reset all his options.
     */
    ho->neg_addrs = 0;
    ho->neg_vj = 0;
    ho->maxslotindex = 0;
    ho->cflag = 0;
    
    /*
     * Process all his options.
     */
    while (l) {
	orc = CONFACK;			/* Assume success */
	cip = p;			/* Remember begining of CI */
	if (l < 2 ||			/* Not enough data for CI header or */
	    p[1] < 2 ||			/*  CI length too small or */
	    (int)p[1] > l) {			/*  CI length too big? */
	    IPCPDEBUG((LOG_INFO, "ipcp_reqci: bad CI length!"))
	    orc = CONFREJ;		/* Reject bad CI */
	    cilen = l;			/* Reject till end of packet */
	    l = 0;			/* Don't loop again */
	    goto endswitch;
	}
	GETCHAR(citype, p);		/* Parse CI type */
	GETCHAR(cilen, p);		/* Parse CI length */
	l -= cilen;			/* Adjust remaining length */
	cilen -= 2;			/* Adjust cilen to just data */

	switch (citype) {		/* Check CI type */
	  case CI_ADDR:
	    IPCPDEBUG((LOG_INFO, "ipcp_reqci: rcvd ADDR"))
	    if (!ao->neg_addrs ||
		cilen != sizeof (long)) { /* Check CI length */
		INCPTR(cilen, p); 	/* Skip rest of CI */
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: rejecting ADDR"))
		orc = CONFREJ;		/* Reject CI */
		break;
	    }

	    /*
	     * If he has no address, or if we both have his address but
	     * disagree about it, then NAK it with our idea.
	     * In particular, if we don't know his address, but he does,
	     * then accept it.
	     */
	    GETLONG(tl, p);		/* Parse source address (his) */
	    ciaddr1 = htonl(tl);
	    if (!ciaddr1 ||
		(wo->neg_addrs && wo->hisaddr && ciaddr1 != wo->hisaddr)) {
		orc = CONFNAK;
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: naking ADDR want=%x got=%x",
			wo->hisaddr, ciaddr1))
		DECPTR(sizeof (long), p);
		tl = wo->neg_addrs ? ntohl(wo->hisaddr) : 0;
		PUTLONG(tl, p);
	    }

	    if (orc == CONFNAK)
		break;

	    /* XXX ho or go? */
	    ho->neg_addrs = IPCP_NEGADDR_NEW;
	    ho->hisaddr = ciaddr1;
	    break;

	  case CI_ADDRS:
	    IPCPDEBUG((LOG_INFO, "ipcp_reqci: rcvd ADDRS"))
	    if (!ao->neg_addrs ||
		cilen != 2 * sizeof (long)) { /* Check CI length */
		INCPTR(cilen, p); 	/* Skip rest of CI */
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: rejecting ADDRS"))
		orc = CONFREJ;		/* Reject CI */
		break;
	    }

	    /*
	     * If he has no address, or if we both have his address but
	     * disagree about it, then NAK it with our idea.
	     * In particular, if we don't know his address, but he does,
	     * then accept it.
	     */
	    GETLONG(tl, p);		/* Parse source address (his) */
	    ciaddr1 = htonl(tl);
	    if (!ciaddr1 ||
		(wo->neg_addrs && wo->hisaddr && ciaddr1 != wo->hisaddr)) {
		orc = CONFNAK;
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: naking ADDRS"))
		DECPTR(sizeof (long), p);
		tl = wo->neg_addrs ? ntohl(wo->hisaddr) : 0;
		PUTLONG(tl, p);
	    }

	    /*
	     * If he doesn't know our address, or if we both have our address
	     * but disagree about it, then NAK it with our idea.
	     */
	    GETLONG(tl, p);		/* Parse desination address (ours) */
	    ciaddr2 = htonl(tl);
	    LCPDEBUG((LOG_INFO, "got addrs (%08lx:%08lx)", ciaddr1, ciaddr2));
	    if (!ciaddr2 ||
		(wo->neg_addrs && wo->ouraddr && ciaddr2 != wo->ouraddr)) {
		orc = CONFNAK;
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: naking ADDRS"))
		DECPTR(sizeof (long), p);
		tl = ntohl(wo->ouraddr);
		PUTLONG(tl, p);
	    }
	    if (orc == CONFNAK)
		break;

	    /* XXX ho or go? */
	    ho->neg_addrs = IPCP_NEGADDR_OLD;
	    ho->hisaddr = ciaddr1;
	    ho->ouraddr = ciaddr2;
	    break;

	  case CI_COMPRESSTYPE:
	    IPCPDEBUG((LOG_INFO, "ipcp_reqci: rcvd COMPRESSTYPE"))
	    if (!ao->neg_vj ||
		cilen != (vj_opt_len  - 2)) {
		INCPTR(cilen, p);
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: rejecting COMPRESSTYPE"))
		orc = CONFREJ;
		break;
	    }
	    GETSHORT(cishort, p);
	    IPCPDEBUG((LOG_INFO, "(%d)", cishort))
	    /*
	     * Compresstype must be vj_opt_val.
	     */
	    if (cishort != vj_opt_val) {
		DECPTR(sizeof (short), p);
		orc = CONFNAK;
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: naking COMPRESSTYPE"))
		PUTSHORT(vj_opt_val, p);
		break;
	    }
	    ho->neg_vj = 1;
	    if (vj_mode == IPCP_VJMODE_DRAFT) {
	      GETCHAR(maxslotindex, p);
	      IPCPDEBUG((LOG_INFO, "(maxslot his=%d, ours=%d)", maxslotindex,
			wo->maxslotindex))
	      if (maxslotindex > wo->maxslotindex) { 
		DECPTR(1, p);
		orc = CONFNAK;
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: naking SLOTINDEX"))
		PUTCHAR(wo->maxslotindex, p);
		break;
	      }
	      ho->maxslotindex = maxslotindex;

	      GETCHAR(cflag, p);
	      IPCPDEBUG((LOG_INFO, "(cflag his=%d, ours=%d)", cflag,
			wo->cflag))
	      if (cflag != wo->cflag) {
		DECPTR(1, p);
		orc = CONFNAK;
	        IPCPDEBUG((LOG_INFO, "ipcp_reqci: naking CFLAG"))
		PUTCHAR(wo->cflag, p);
		break;
	      }
	      ho->cflag = wo->cflag;
	    }
	    break;

	  default:
	    INCPTR(cilen, p);
	    orc = CONFREJ;
	    IPCPDEBUG((LOG_INFO, "rejecting (%d)", citype));
	    break;
	}
	cilen += 2;			/* Adjust cilen whole CI */

endswitch:
	IPCPDEBUG((LOG_INFO, " (%s)",
		   orc == CONFACK ? "ACK" : (orc == CONFNAK ? "NAK" : "REJ")))
	if (orc == CONFACK &&		/* Good CI */
	    rc != CONFACK)		/*  but prior CI wasnt? */
	    continue;			/* Don't send this one */

	if (orc == CONFNAK) {		/* Nak this CI? */
	    if (rc == CONFREJ)		/* Rejecting prior CI? */
		continue;		/* Don't send this one */
	    if (rc == CONFACK) {	/* Ack'd all prior CIs? */
		rc = CONFNAK;		/* Not anymore... */
		ucp = inp;		/* Backup */
	    }
	}
	if (orc == CONFREJ &&		/* Reject this CI */
	    rc != CONFREJ) {		/*  but no prior ones? */
	    rc = CONFREJ;
	    ucp = inp;			/* Backup */
	}
	if (ucp != cip)			/* Need to move CI? */
	    BCOPY(cip, ucp, cilen);	/* Move it */
	INCPTR(cilen, ucp);		/* Update output pointer */
    }

    /*
     * XXX If we wanted to send additional NAKs (for unsent CIs), the
     * code would go here.  This must be done with care since it might
     * require a longer packet than we received.
     */

    *len = ucp - inp;			/* Compute output length */
    IPCPDEBUG((LOG_INFO, "ipcp_reqci: returning %s.",
	      rc == CONFACK ? "CONFACK" :
	      rc == CONFNAK ? "CONFNAK" : "CONFREJ"))
    return (rc);			/* Return final code */
}


/*
 * ipcp_up - IPCP has come UP.
 */
static void
  ipcp_up(f)
fsm *f;
{
  u_long mask;

  /* XXX gotoptions or hisoptions? */
  SIFADDR(f->unit, ipcp_gotoptions[f->unit].ouraddr,
	  ipcp_gotoptions[f->unit].hisaddr);
  /* set new netmask if specified */
  mask = GetMask(ipcp_gotoptions[f->unit].ouraddr);
  if (mask) 
    SIFMASK(f->unit, mask);
  SIFVJCOMP(f->unit, ipcp_hisoptions[f->unit].neg_vj);
  SIFSAPENABLE(PPP_IP);
}


/*
 * ipcp_down - IPCP has gone DOWN.
 *
 * Alert other protocols.
 */
static void
  ipcp_down(f)
fsm *f;
{
  IPCPDEBUG((LOG_DEBUG, "In ipcp_down()."));
  CIFADDR(f->unit, ipcp_gotoptions[f->unit].ouraddr,
	  ipcp_gotoptions[f->unit].hisaddr);
}
