/*
 *  PPPIPCP.C   -- negotiate IP parameters
 *
 *  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 <ctype.h>
#include <time.h>
#include "global.h"
#ifdef PPP
#include "mbuf.h"
#include "iface.h"
#include "slhc.h"
#include "ppp.h"
#include "pppfsm.h"
#include "pppipcp.h"
#include "cmdparse.h"
#include "files.h"
#include "trace.h"
#include "pktdrvr.h"
  
  
/* These defaults are defined in the PPP RFCs, and must not be changed */
static struct ipcp_value_s ipcp_default = {
    FALSE,          /* no need to negotiate defaults */
  
    0L,         /* no source address */
    0L,         /* no destination address */
  
    0,          /* no compression protocol */
    0,          /* no slots */
    0           /* no slot compression */
};
  
/* for test purposes, accept anything we understand */
static int16 ipcp_negotiate = IPCP_N_ADDRESSES|IPCP_N_COMPRESS|IPCP_N_ADDRESS;
  
static byte_t option_length[] = {
    0,     /* unused */
    10,    /* addresses */
    6,     /* compression */
    6      /* address */
};
  
  
static int doipcp_local     __ARGS((int argc, char *argv[], void *p));
static int doipcp_open      __ARGS((int argc, char *argv[], void *p));
static int doipcp_pool      __ARGS((int argc, char *argv[], void *p));
static int doipcp_remote    __ARGS((int argc, char *argv[], void *p));
  
static int doipcp_address   __ARGS((int argc, char *argv[], void *p));
static int doipcp_compress  __ARGS((int argc, char *argv[], void *p));
static int doipcp_default   __ARGS((int argc, char *argv[], void *p));
  
static void ipcp_option __ARGS((struct mbuf **bpp, struct ipcp_value_s *value_p,
    byte_t o_type,byte_t o_length,struct mbuf **copy_bpp));
static void ipcp_makeoptions __ARGS((struct mbuf **bpp,struct ipcp_value_s *value_p,
    int16 negotiating));
static struct mbuf *ipcp_makereq __ARGS((struct fsm_s *fsm_p));
  
static int ipcp_check __ARGS((struct mbuf **bpp,struct ipcp_s *ipcp_p,
    struct ipcp_side_s *side_p,struct option_hdr *option_p,int request));
  
static int ipcp_request __ARGS((struct fsm_s *fsm_p,struct config_hdr *config,
    struct mbuf *data));
static int ipcp_ack __ARGS((struct fsm_s *fsm_p,struct config_hdr *config,
    struct mbuf *data));
static int ipcp_nak __ARGS((struct fsm_s *fsm_p,struct config_hdr *config,
    struct mbuf *data));
static int ipcp_reject  __ARGS((struct fsm_s *fsm_p,struct config_hdr *config,
    struct mbuf *data));
  
static void ipcp_reset  __ARGS((struct fsm_s *fsm_p));
  
static int32 ipcp_addr_idle __ARGS((int32 addr));
static int32 ipcp_lookuppeer __ARGS((char *peerid));
static int32 ipcp_poolnext __ARGS((struct ipcp_s *ipcp_p));
  
static void ipcp_starting __ARGS((struct fsm_s *fsm_p));
static void ipcp_stopping __ARGS((struct fsm_s *fsm_p));
  
static void ipcp_closing __ARGS((struct fsm_s *fsm_p));
static void ipcp_opening __ARGS((struct fsm_s *fsm_p));
  
static void ipcp_free   __ARGS((struct fsm_s *fsm_p));
  
  
static struct fsm_constant_s ipcp_constants = {
    "IPcp",
    PPP_IPCP_PROTOCOL,
    0x00FE,             /* codes 1-7 recognized */
  
    IPcp,
    IPCP_REQ_TRY,
    IPCP_NAK_TRY,
    IPCP_TERM_TRY,
    IPCP_TIMEOUT * 1000L,
  
    ipcp_free,
  
    ipcp_reset,
    ipcp_starting,
    ipcp_opening,
    ipcp_closing,
    ipcp_stopping,
  
    ipcp_makereq,
    ipcp_request,
    ipcp_ack,
    ipcp_nak,
    ipcp_reject,
};
  
  
/************************************************************************/
  
/* "ppp <iface> ipcp" subcommands */
static struct cmds DFAR IPcpcmds[] = {
    "close",    doppp_close,    0,  0,  NULLCHAR,
    "listen",   doppp_passive,  0,  0,  NULLCHAR,
    "local",    doipcp_local,   0,  0,  NULLCHAR,
    "open",     doipcp_open,    0,  0,  NULLCHAR,
    "pool",     doipcp_pool,    0,  0,  NULLCHAR,
    "remote",   doipcp_remote,  0,  0,  NULLCHAR,
    "timeout",  doppp_timeout,  0,  0,  NULLCHAR,
    "try",      doppp_try,  0,  0,  NULLCHAR,
    NULLCHAR,
};
  
/* "ppp <iface> ipcp {local | remote}" subcommands */
static struct cmds DFAR IPcpside_cmds[] = {
    "address",  doipcp_address, 0,  0,  NULLCHAR,
    "compress", doipcp_compress,0,  0,  NULLCHAR,
    "default",  doipcp_default, 0,  0,  NULLCHAR,
    NULLCHAR,
};
  
  
int
doppp_ipcp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct iface *ifp = p;
    register struct ppp_s *ppp_p = ifp->edv;
  
    return subcmd(IPcpcmds, argc, argv, &(ppp_p->fsm[IPcp]));
}
  
  
static int
doipcp_local(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct fsm_s *fsm_p = p;
    struct ipcp_s *ipcp_p = fsm_p->pdv;
    return subcmd(IPcpside_cmds, argc, argv, &(ipcp_p->local));
}
  
  
static int
doipcp_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 == pppREADY ) {
        fsm_start( fsm_p );
    }
    return 0;
}
  
  
/* Set a pool of peer addresses for PPP interface */
static int
doipcp_pool(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct fsm_s *fsm_p = p;
    struct ipcp_s *ipcp_p = fsm_p->pdv;
    int32 pool_addr;
    int pool_cnt;
  
    if (argc < 2) {
        if ( ipcp_p->peer_min == 0L ) {
            tputs("None");
        } else {
            tprintf("%s thru ", inet_ntoa(ipcp_p->peer_min));
            tprintf("%s\n", inet_ntoa(ipcp_p->peer_max));
        }
        return 0;
    }
  
    if ((pool_addr = resolve(argv[1])) == 0L) {
        tprintf(Badhost,argv[1]);
        return -1;
    }
  
    /* May specify a consecutive range of addresses; otherwise assume 1 */
    if (argc < 3)
        pool_cnt = 1;
    else
        pool_cnt = (int)strtol( argv[2], NULLCHARP, 0 );
  
    if (pool_cnt <= 0) {
        tprintf("Pool count %s (%d) must be > 0\n",argv[1],pool_cnt);
        return -1;
    }
  
    ipcp_p->peer_min = pool_addr;
    ipcp_p->peer_max = pool_addr + pool_cnt - 1;
    return 0;
}
  
  
static int
doipcp_remote(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct fsm_s *fsm_p = p;
    struct ipcp_s *ipcp_p = fsm_p->pdv;
    return subcmd(IPcpside_cmds, argc, argv, &(ipcp_p->remote));
}
  
  
/************************************************************************/
/* Set addresses for PPP interface */
static int
doipcp_address(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct ipcp_side_s *side_p = p;
    int32 x32;
  
    if (argc < 2) {
        tprintf("%s\n", inet_ntoa(side_p->want.address));
        return 0;
    } else if ( stricmp(argv[1],"allow") == 0 ) {
        return bit16cmd( &(side_p->will_negotiate), IPCP_N_ADDRESSES|IPCP_N_ADDRESS,
        "Allow Address", --argc, &argv[1] );
    }
    if ((x32 = (int32)stricmp(argv[1],"none")) != 0L && (x32 = resolve(argv[1])) == 0L) {
        tprintf(Badhost,argv[1]);
    }
    side_p->want.address = x32;
    side_p->want.negotiate |= (IPCP_N_ADDRESSES|IPCP_N_ADDRESS);
    return 0;
}
  
  
/* Set IP compression type for PPP interface */
static int
doipcp_compress(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct ipcp_side_s *side_p = p;
  
    if (argc < 2) {
        if ( side_p->want.negotiate & IPCP_N_COMPRESS ) {
            switch ( side_p->want.compression ) {
                case PPP_COMPR_PROTOCOL:
                    tprintf("TCP header compression enabled; "
                    "Slots = %d, slot compress = %x\n",
                    side_p->want.slots,
                    side_p->want.slot_compress);
                    break;
                default:
                    tprintf("0x%04x\n", side_p->want.compression);
                    break;
            };
        } else {
            tputs("None\n");
        }
    } else if ( stricmp(argv[1],"allow") == 0 ) {
        return bit16cmd( &(side_p->will_negotiate), IPCP_N_COMPRESS,
        "Allow Compression", --argc, &argv[1] );
    } else if ( stricmp(argv[1],"tcp") == 0
    || stricmp(argv[1],"vj") == 0 ) {
        side_p->want.compression = PPP_COMPR_PROTOCOL;
        if ( argc >= 3 ) {
            side_p->want.slots = strtol(argv[2],NULLCHARP,0);
            if ( side_p->want.slots < 1 || side_p->want.slots > 255 ) {
                tputs( "slots must be in range 1 to 255" );
                return 1;
            }
        } else {
            side_p->want.slots = IPCP_SLOT_DEFAULT;
        }
        if ( argc >= 4 ) {
            side_p->want.slot_compress = strtol(argv[3],NULLCHARP,0);
        } else {
            side_p->want.slot_compress = IPCP_SLOT_COMPRESS;
        }
        side_p->want.negotiate |= IPCP_N_COMPRESS;
    } else if (stricmp(argv[1],"none") == 0) {
        side_p->want.negotiate &= ~IPCP_N_COMPRESS;
    } else {
        tputs("allow tcp none\n");
        return 1;
    }
    return 0;
}
  
  
static int
doipcp_default(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct ipcp_side_s *side_p = p;
  
    ASSIGN( side_p->want, ipcp_default );
    return 0;
}
  
  
/************************************************************************/
/*          E V E N T   P R O C E S S I N G         */
/************************************************************************/
  
static void
ipcp_option( bpp, value_p, o_type, o_length, copy_bpp )
struct mbuf **bpp;
struct ipcp_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;
    register int both_addrs = 1;
  
    if ((bp = alloc_mbuf(o_length)) == NULLBUF) {
        return;
    }
    cp = bp->data;
    *cp++ = o_type;
    *cp++ = o_length;
  
    switch ( o_type ) {
        case IPCP_ADDRESS:      /* LAK for rfc 1332 */
            both_addrs = 0;
            /* Fall into IPCP_ADDRESSES case */
  
        case IPCP_ADDRESSES:
            cp = put32(cp, value_p->address);
            toss -= 4;
            if (both_addrs) {
                cp = put32(cp, value_p->other);
                toss -= 4;
            }
#ifdef PPP_DEBUG_OPTIONS
            if (PPPtrace & PPP_DEBUG_OPTIONS) {
                trace_log(PPPiface, "    making IP source address: %s",
                inet_ntoa(value_p->address));
                if (both_addrs)
                    trace_log(PPPiface, "    making IP destination address %s",
                    inet_ntoa(value_p->other));
            }
#endif
            break;
  
        case IPCP_COMPRESS:
            cp = put16(cp, value_p->compression);
            toss -= 2;
#ifdef PPP_DEBUG_OPTIONS
            if (PPPtrace & PPP_DEBUG_OPTIONS)
                trace_log(PPPiface, "    making IP compression 0x%04x",
                value_p->compression);
#endif
            if ( value_p->compression == PPP_COMPR_PROTOCOL ) {
                *cp++ = value_p->slots - 1;
                *cp++ = value_p->slot_compress;
                toss -= 2;
#ifdef PPP_DEBUG_OPTIONS
                if (PPPtrace & PPP_DEBUG_OPTIONS)
                    trace_log(PPPiface, "    with IP compression slots %d, flag %x",
                    value_p->slots,
                    value_p->slot_compress);
#endif
            }
            break;
  
        default:
#ifdef PPP_DEBUG_OPTIONS
            if (PPPtrace & PPP_DEBUG_OPTIONS)
                trace_log(PPPiface, "    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
ipcp_makeoptions(bpp, value_p, negotiating)
struct mbuf **bpp;
struct ipcp_value_s *value_p;
int16 negotiating;
{
    register int o_type;
  
    PPP_DEBUG_ROUTINES("ipcp_makeoptions()");
  
    for ( o_type = 1; o_type <= IPCP_OPTION_LIMIT; o_type++ ) {
        if (negotiating & (1 << o_type)) {
            ipcp_option( bpp, value_p,
            o_type, option_length[ o_type ], NULLBUFP);
        }
    }
}
  
  
/************************************************************************/
/* Build a request to send to remote host */
static struct mbuf *
ipcp_makereq(fsm_p)
struct fsm_s *fsm_p;
{
    struct ipcp_s *ipcp_p = fsm_p->pdv;
    struct mbuf *req_bp = NULLBUF;
  
    PPP_DEBUG_ROUTINES("ipcp_makereq()");
  
    ipcp_makeoptions( &req_bp, &(ipcp_p->local.work),
    ipcp_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
ipcp_check( bpp, ipcp_p, side_p, option_p, request )
struct mbuf **bpp;
struct ipcp_s *ipcp_p;
struct ipcp_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 */
    int both_addrs = 1;
    int test;
  
    switch(option_p->type) {
        case IPCP_ADDRESS:    /* LAK for rfc1332 */
            both_addrs = 0;
            /* fall into IPCP_ADDRESSES case */

        case IPCP_ADDRESSES:
            side_p->work.address = pull32(bpp);
            toss -= 4;
            if (both_addrs) {
                side_p->work.other = pull32(bpp);
                toss -= 4;
            }
#ifdef PPP_DEBUG_OPTIONS
            if (PPPtrace & PPP_DEBUG_OPTIONS) {
                trace_log(PPPiface, "    checking IP source address: %s",
                inet_ntoa(side_p->work.address));
                if (both_addrs)
                    trace_log(PPPiface, "    checking IP destination address %s",
                    inet_ntoa(side_p->work.other));
            }
#endif
            if ( !request ) {
            /* override any undesirable changes */
                if (both_addrs && ipcp_p->remote.want.address != 0L) {
                    ipcp_p->local.work.other
                    = ipcp_p->remote.want.address;
                }
#ifdef PPP_NO_LOCALADDR_CHANGE
/* Refuse new local IP address suggestion when we already have one defined.
 * This breaks dynamic IP address assignment when we don't reattach before
 * redialing. -- n5knx
 */
                if (ipcp_p->local.want.address != 0L) {
                    ipcp_p->local.work.address
                    = ipcp_p->local.want.address;
                }
#endif
                break;
            }
  
        /* Ensure that addresses match */
            if (both_addrs) {
                if (ipcp_p->remote.work.address == ipcp_p->remote.want.address) {
                    if (ipcp_p->remote.want.address == 0L) {
                    /* don't know address either */
                        option_result = CONFIG_REJ;
                    }
                    else goto we_agree2;
                } else if (ipcp_p->remote.want.address == 0L) {
we_agree2:          ipcp_p->local.work.other = ipcp_p->remote.work.address; /* n5knx */
                } else {
                    ipcp_p->remote.work.address = ipcp_p->remote.want.address;
                    option_result = CONFIG_NAK;
                }
            }
  
            if (ipcp_p->remote.work.other == ipcp_p->local.want.address) {
                if (ipcp_p->local.want.address == 0L) {
                /* don't know address either */
                    option_result = CONFIG_REJ;
                }
            	else goto we_agree;  /* n5knx */
            } else if (ipcp_p->local.want.address == 0L) {
we_agree:       ipcp_p->local.work.address = ipcp_p->remote.work.other;
            } else {
                option_result = CONFIG_NAK;
                ipcp_p->remote.work.other = ipcp_p->local.want.address;
            }
            break;
  
        case IPCP_COMPRESS:
            side_p->work.compression = pull16(bpp);
            toss -= 2;
#ifdef PPP_DEBUG_OPTIONS
            if (PPPtrace & PPP_DEBUG_OPTIONS)
                trace_log(PPPiface, "    checking IP compression 0x%04x",
                side_p->work.compression);
#endif
        /* Check if requested type is acceptable */
            switch ( side_p->work.compression ) {
                case PPP_COMPR_PROTOCOL:
                    if ( (test = pullchar(bpp)) == -1 ) {
                        return -1;
                    }
                    if ( (side_p->work.slots = test + 1) < IPCP_SLOT_LO) {
                        side_p->work.slots = IPCP_SLOT_LO;
                        option_result = CONFIG_NAK;
                    } else if (side_p->work.slots > IPCP_SLOT_HI) {
                        side_p->work.slots = IPCP_SLOT_HI;
                        option_result = CONFIG_NAK;
                    }
  
                    if ( (test = pullchar(bpp)) == -1 ) {
                        return -1;
                    }
                    if ( (side_p->work.slot_compress = test) > 1 ) {
                        side_p->work.slot_compress = 1;
                        option_result = CONFIG_NAK;
                    }
                    toss -= 2;
#ifdef PPP_DEBUG_OPTIONS
                    if (PPPtrace & PPP_DEBUG_OPTIONS)
                        trace_log(PPPiface, "    with IP compression slots %d, flag %x",
                        side_p->work.slots,
                        side_p->work.slot_compress);
#endif
                    break;
  
                default:
                    if ( side_p->want.negotiate & IPCP_N_COMPRESS ) {
                        side_p->work.compression = side_p->want.compression;
                        side_p->work.slots = side_p->want.slots;
                        side_p->work.slot_compress = side_p->want.slot_compress;
                    } else {
                        side_p->work.compression = PPP_COMPR_PROTOCOL;
                        side_p->work.slots = IPCP_SLOT_DEFAULT;
                        side_p->work.slot_compress = IPCP_SLOT_COMPRESS;
                    }
                    option_result = CONFIG_NAK;
                    break;
            };
            break;
  
        default:
            option_result = CONFIG_REJ;
            break;
    };
  
    if (option_p->type > IPCP_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 options requested by the remote host */
static int
ipcp_request(fsm_p, config, data)
struct fsm_s *fsm_p;
struct config_hdr *config;
struct mbuf *data;
{
    struct ipcp_s *ipcp_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("ipcp_request()");
    ipcp_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("IPCP REQ: bad header length");
            free_p(data);
            free_p(reply_bp);
            return -1;
        }
  
        if ( ( option_result = ipcp_check( &data, ipcp_p,
        &(ipcp_p->remote), &option, TRUE ) ) == -1 ) {
            PPP_DEBUG_CHECKS("IPCP REQ: ran out of data");
            free_p(data);
            free_p(reply_bp);
            return -1;
        }
  
#ifdef PPP_DEBUG_OPTIONS
        if (PPPtrace & PPP_DEBUG_OPTIONS) {
            trace_log(PPPiface, "IPCP 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 <= IPCP_OPTION_LIMIT ) {
            ipcp_p->remote.work.negotiate |= (1 << option.type);
        }
  
        /* Add option response to the return list */
        ipcp_option( &reply_bp, &(ipcp_p->remote.work),
        option.type, option.len, &data );
    }
  
    /* Now check for any missing options which are desired */
    if ( fsm_p->retry_nak > 0
        &&  (desired = ipcp_p->remote.want.negotiate
    & ~ipcp_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:
                ipcp_makeoptions( &reply_bp, &(ipcp_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
ipcp_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("ipcp_ack()");
  
    /* ID field must match last request we sent */
    if (config->id != fsm_p->lastid) {
        PPP_DEBUG_CHECKS("IPCP ACK: wrong ID");
        free_p(data);
        return -1;
    }
  
    /* Get a copy of last request we sent */
    req_bp = ipcp_makereq(fsm_p);
  
    /* Overall buffer length should match */
    if (config->len != len_p(req_bp)) {
        PPP_DEBUG_CHECKS("IPCP 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("IPCP ACK: data mismatch");
                error = TRUE;
                break;
            }
        }
    }
    free_p(req_bp);
    free_p(data);
  
    if (error) {
        return -1;
    }
  
    PPP_DEBUG_CHECKS("IPCP ACK: valid");
    return 0;
}
  
  
/************************************************************************/
/* Process configuration NAK sent by remote host */
static int
ipcp_nak(fsm_p, config, data)
struct fsm_s *fsm_p;
struct config_hdr *config;
struct mbuf *data;
{
    struct ipcp_s *ipcp_p = fsm_p->pdv;
    struct ipcp_side_s *local_p = &(ipcp_p->local);
    int32 signed_length = config->len;
    struct option_hdr option;
    int last_option = 0;
    int result;
  
    PPP_DEBUG_ROUTINES("ipcp_nak()");
  
    /* ID field must match last request we sent */
    if (config->id != fsm_p->lastid) {
        PPP_DEBUG_CHECKS("IPCP 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("IPCP NAK: bad header length");
            free_p(data);
            return -1;
        }
        if ( option.type > IPCP_OPTION_LIMIT ) {
            PPP_DEBUG_CHECKS("IPCP 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("IPCP NAK: option out of order");
                free_p(data);
                return -1;      /* was requested */
            }
            local_p->work.negotiate |= (1 << option.type);
            last_option = IPCP_OPTION_LIMIT + 1;
        } else {
            last_option = option.type;
        }
        if ( ( result = ipcp_check( &data, ipcp_p,
        local_p, &option, FALSE ) ) == -1 ) {
            PPP_DEBUG_CHECKS("IPCP NAK: ran out of data");
            free_p(data);
            return -1;
        }
        /* update the negotiation status */
        if ( result == CONFIG_REJ
        && option.type <= IPCP_OPTION_LIMIT ) {
            local_p->work.negotiate &= ~(1 << option.type);
        }
    }
    PPP_DEBUG_CHECKS("IPCP NAK: valid");
    free_p(data);
    return 0;
}
  
  
/************************************************************************/
/* Process configuration reject sent by remote host */
static int
ipcp_reject(fsm_p, config, data)
struct fsm_s *fsm_p;
struct config_hdr *config;
struct mbuf *data;
{
    struct ipcp_s *ipcp_p = fsm_p->pdv;
    struct ipcp_side_s *local_p = &(ipcp_p->local);
    int32 signed_length = config->len;
    struct option_hdr option;
    int last_option = 0;
  
    PPP_DEBUG_ROUTINES("ipcp_reject()");
  
    /* ID field must match last request we sent */
    if (config->id != fsm_p->lastid) {
        PPP_DEBUG_CHECKS("IPCP 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("IPCP REJ: bad header length");
            free_p(data);
            return -1;
        }
        if ( option.type > IPCP_OPTION_LIMIT ) {
            PPP_DEBUG_CHECKS("IPCP REJ: option out of range");
        } else if (option.type < last_option
        || !(local_p->work.negotiate & (1 << option.type))) {
            PPP_DEBUG_CHECKS("IPCP 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("IPCP REJ: ran out of data");
                free_p(data);
                return -1;
            }
        }
        last_option = option.type;
  
        if ( option.type <= IPCP_OPTION_LIMIT ) {
            local_p->work.negotiate &= ~(1 << option.type);
        }
    }
    PPP_DEBUG_CHECKS("IPCP 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
ipcp_reset(fsm_p)
struct fsm_s *fsm_p;
{
    struct ipcp_s *ipcp_p = fsm_p->pdv;
  
    PPP_DEBUG_ROUTINES("ipcp_reset()");
  
    ASSIGN( ipcp_p->local.work, ipcp_p->local.want );
    ipcp_p->local.work.other = ipcp_p->remote.want.address;
    ipcp_p->local.will_negotiate |= ipcp_p->local.want.negotiate;
  
    ipcp_p->remote.work.negotiate = FALSE;
    ipcp_p->remote.will_negotiate |= ipcp_p->remote.want.negotiate;
}
  
  
/************************************************************************/
/* After termination */
static void
ipcp_stopping(fsm_p)
struct fsm_s *fsm_p;
{
    PPP_DEBUG_ROUTINES("ipcp_stopping()");
}
  
  
/************************************************************************/
/* Close IPCP */
static void
ipcp_closing(fsm_p)
struct fsm_s *fsm_p;
{
    struct ipcp_s *ipcp_p =     fsm_p->pdv;
  
    /* free old slhc configuration, if any */
    slhc_free( ipcp_p->slhcp );
    ipcp_p->slhcp = NULL;
  
#ifdef notdef
    if (PPPtrace > 1)
        trace_log(PPPiface,"%s PPP/IPCP Drop route to peer (%s)",
        ifp->name,
        inet_ntoa(ipcp_p->local.work.other));
  
    rt_drop(ipcp_p->local.work.other, (unsigned int)32);
#endif
}
  
  
/************************************************************************/
/* configuration negotiation complete */
static void
ipcp_opening(fsm_p)
struct fsm_s *fsm_p;
{
    struct ipcp_s *ipcp_p =     fsm_p->pdv;
    struct iface *ifp =         fsm_p->ppp_p->iface;
    int32 address = ipcp_p->local.work.address;
    int rslots = 0;
    int tslots = 0;
  
    /* Set our IP address to reflect negotiated option */
    if (address != ifp->addr) {
        /* address not the same as last time */
        if (Ip_addr == 0L) {
            /* no global address */
            Ip_addr = address;
        } else if ( Ip_addr == ifp->addr ) {
            /* global was same as local; must be replaced */
            /* !!! TO DO: reset tcp connections */
            Ip_addr = address;
        }
        ifp->addr = address;
  
        if (PPPtrace > 1)
            trace_log(PPPiface,"%s PPP/IPCP Setting new IP address: %s",
            ifp->name,
            inet_ntoa(address));
    }
  
#ifdef notdef
    rt_add(ipcp_p->local.work.other, (unsigned int)32, (int32)0,
    ifp, (int32)1, (int32)0, (char)1);
  
    if (PPPtrace > 1)
        trace_log(PPPiface,"%s PPP/IPCP Add route to peer (%s)",
        ifp->name,
        inet_ntoa(ipcp_p->local.work.other));
#endif
  
    /* free old slhc configuration, if any */
    slhc_free( ipcp_p->slhcp );
    ipcp_p->slhcp = NULL;
  
    if (ipcp_p->local.work.negotiate & IPCP_N_COMPRESS) {
        rslots = ipcp_p->local.work.slots;
    }
    if (ipcp_p->remote.work.negotiate & IPCP_N_COMPRESS) {
        tslots = ipcp_p->remote.work.slots;
    }
  
    if ( rslots != 0 || tslots != 0 ) {
        ipcp_p->slhcp = slhc_init( rslots, tslots );
  
        if (PPPtrace > 1)
            trace_log(PPPiface,"%s PPP/IPCP Compression enabled;"
            " Recv slots = %d, flag = %x;"
            " Xmit slots = %d, flag = %x",
            ifp->name,
            rslots,
            ipcp_p->local.work.slot_compress,
            tslots,
            ipcp_p->remote.work.slot_compress);
    }
}
  
  
/************************************************************************/
/* Check the address against all other assigned addresses */
static int32
ipcp_addr_idle(addr)
int32 addr;
{
    struct iface *ifp;
    struct fsm_s *fsm_p;
  
    /* Check if peer IP address is already in use on another interface */
    /* n5knx: now also looks at *remote* addresses */
    for (ifp=Ifaces; ifp != NULLIF; ifp = ifp->next) {
        if (ifp->addr == addr)
            return 0L;
        if (ifp->type == CL_PPP && ifp->edv && (fsm_p = &(((struct ppp_s *)ifp->edv)->fsm[IPcp])) != NULL
            && ((struct ipcp_s *)fsm_p->pdv)->local.work.other == addr)
            return 0L;
        /* we could also check out remote slip addrs?? */
    }
    return addr;
}
  
  
/************************************************************************/
/* Assign the next unused address from a pool */
static int32
ipcp_poolnext(ipcp_p)
struct ipcp_s *ipcp_p;
{
    int32 i = 1L + ipcp_p->peer_max - ipcp_p->peer_min;
    int32 nextaddr = 0L;
  
    while ( i-- > 0  &&  nextaddr == 0L ) {
        if (++ipcp_p->local.want.other < ipcp_p->peer_min
            || ipcp_p->local.want.other > ipcp_p->peer_max)
            ipcp_p->local.want.other = ipcp_p->peer_min;
  
        nextaddr = ipcp_addr_idle(ipcp_p->local.want.other);
    }
    return(nextaddr);
}
  
  
/************************************************************************/
/* Check if we have a specific IP address to assign to remote peer host */
/* !!! TO DO: subnet mask, and routing */
static int32
ipcp_lookuppeer(peerid)
char *peerid;
{
    char *buf;
    int32 peer_addr = 0L;
  
    if (peerid == NULLCHAR)
        return 0L;
  
    if ( (buf = userlookup( peerid, NULLCHARP, NULLCHARP,
    NULL, &peer_addr )) != NULLCHAR ) {
        free(buf);
    }
    return(peer_addr);
}
  
  
/************************************************************************/
/* Prepare to begin configuration exchange */
static void
ipcp_starting(fsm_p)
struct fsm_s *fsm_p;
{
    struct ipcp_s *ipcp_p =     fsm_p->pdv;
  
    PPP_DEBUG_ROUTINES("ipcp_starting()");
  
    /* If not already set, and we know the name of the peer,
     * look in login file for an address
     */
    if ( ipcp_p->remote.want.address == 0L ){
        ipcp_p->remote.want.address
        = ipcp_lookuppeer(fsm_p->ppp_p->peername);
    }
  
    /* If available, get next address from PPP pool */
    if ((ipcp_p->remote.want.address == 0L)
    && (ipcp_p->peer_min != 0L)) {
        ipcp_p->remote.want.address = ipcp_poolnext(ipcp_p);
    }
  
    ipcp_p->local.want.address = fsm_p->ppp_p->iface->addr;
}
  
  
/************************************************************************/
static void
ipcp_free(fsm_p)
struct fsm_s *fsm_p;
{
    struct ipcp_s *ipcp_p = fsm_p->pdv;
  
    slhc_free( ipcp_p->slhcp );
}
  
  
/* Initialize configuration structure */
void
ipcp_init(ppp_p)
struct ppp_s *ppp_p;
{
    struct fsm_s *fsm_p = &(ppp_p->fsm[IPcp]);
    struct ipcp_s *ipcp_p;
  
    PPPtrace = ppp_p->trace;
    PPPiface = ppp_p->iface;
  
    PPP_DEBUG_ROUTINES("ipcp_init()");
  
    fsm_p->ppp_p = ppp_p;
    fsm_p->pdc = &ipcp_constants;
    fsm_p->pdv =
    ipcp_p = callocw(1,sizeof(struct ipcp_s));
  
    /* Set option parameters to first request defaults */
    ASSIGN( ipcp_p->local.want, ipcp_default );
    ipcp_p->local.will_negotiate = ipcp_negotiate;
  
    ASSIGN( ipcp_p->remote.want, ipcp_default );
    ASSIGN( ipcp_p->remote.work, ipcp_default);
    ipcp_p->remote.will_negotiate = ipcp_negotiate;
  
    fsm_init(fsm_p);
}
#endif /* PPP */
  
