Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * ppp - point-to-point protocol, rfc1331
 */
#include <u.h>
#include <libc.h>
#include <auth.h>
#include <bio.h>
#include <ip.h>
#include <libsec.h>
#include <ndb.h>
#include "ppp.h"

#define PATH 128

static  int     baud;
static  int     nocompress;
static  int     pppframing = 1;
static  int     noipcompress;
static  int     server;
static  int noauth;
static  int     nip;            /* number of ip interfaces */
static  int     dying;          /* flag to signal to all threads its time to go */
static  int     primary;        /* this is the primary IP interface */
static  char    *chatfile;

int     debug;
char*   LOG = "ppp";
char*   keyspec = "";

enum
{
        Rmagic= 0x12345
};

/*
 * Calculate FCS - rfc 1331
 */
ushort fcstab[256] =
{
      0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
      0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
      0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
      0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
      0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
      0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
      0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
      0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
      0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
      0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
      0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
      0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
      0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
      0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
      0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
      0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
      0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
      0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
      0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
      0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
      0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
      0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
      0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
      0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
      0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
      0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
      0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
      0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
      0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
      0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
      0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
      0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};

static char *snames[] =
{
        "Sclosed",
        "Sclosing",
        "Sreqsent",
        "Sackrcvd",
        "Sacksent",
        "Sopened",
};

static  void            authtimer(PPP*);
static  void            chapinit(PPP*);
static  void            config(PPP*, Pstate*, int);
static  uchar*          escapeuchar(PPP*, ulong, uchar*, ushort*);
static  void            getchap(PPP*, Block*);
static  Block*          getframe(PPP*, int*);
static  void            getlqm(PPP*, Block*);
static  int             getopts(PPP*, Pstate*, Block*);
static  void            getpap(PPP*, Block*);
static  void            init(PPP*);
static  void            invalidate(Ipaddr);
static  void            ipinproc(PPP*);
static  char*           ipopen(PPP*);
static  void            mediainproc(PPP*);
static  void            newstate(PPP*, Pstate*, int);
static  int             nipifcs(char*);
static  void            papinit(PPP*);
static  void            pinit(PPP*, Pstate*);
static  void            ppptimer(PPP*);
static  void            printopts(Pstate*, Block*, int);
static  void            ptimer(PPP*, Pstate*);
static  int             putframe(PPP*, int, Block*);
static  void            putlqm(PPP*);
static  void            putndb(PPP*, char*);
static  void            putpaprequest(PPP*);
static  void            rcv(PPP*, Pstate*, Block*);
static  void            rejopts(PPP*, Pstate*, Block*, int);
static  void            sendechoreq(PPP*, Pstate*);
static  void            sendtermreq(PPP*, Pstate*);
static  void            setphase(PPP*, int);
static  void            terminate(PPP*, int);
static  int             validv4(Ipaddr);
static  void            dmppkt(char *s, uchar *a, int na);
static  void            getauth(PPP*);

void
pppopen(PPP *ppp, int mediain, int mediaout, char *net,
        Ipaddr ipaddr, Ipaddr remip,
        int mtu, int framing)
{
        ppp->ipfd = -1;
        ppp->ipcfd = -1;
        invalidate(ppp->remote);
        invalidate(ppp->local);
        invalidate(ppp->curremote);
        invalidate(ppp->curlocal);
        invalidate(ppp->dns[0]);
        invalidate(ppp->dns[1]);
        invalidate(ppp->wins[0]);
        invalidate(ppp->wins[1]);

        ppp->mediain = mediain;
        ppp->mediaout = mediaout;
        if(validv4(remip)){
                ipmove(ppp->remote, remip);
                ppp->remotefrozen = 1;
        }
        if(validv4(ipaddr)){
                ipmove(ppp->local, ipaddr);
                ppp->localfrozen = 1;
        }
        ppp->mtu = Defmtu;
        ppp->mru = mtu;
        ppp->framing = framing;
        ppp->net = net;

        init(ppp);
        switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
        case -1:
                sysfatal("forking mediainproc");
        case 0:
                mediainproc(ppp);
                terminate(ppp, 1);
                _exits(0);
        }
}

static void
init(PPP* ppp)
{
        if(ppp->inbuf == nil){
                ppp->inbuf = allocb(4096);
                if(ppp->inbuf == nil)
                        abort();

                ppp->outbuf = allocb(4096);
                if(ppp->outbuf == nil)
                        abort();

                ppp->lcp = mallocz(sizeof(*ppp->lcp), 1);
                if(ppp->lcp == nil)
                        abort();
                ppp->lcp->proto = Plcp;
                ppp->lcp->state = Sclosed;

                ppp->ccp = mallocz(sizeof(*ppp->ccp), 1);
                if(ppp->ccp == nil)
                        abort();
                ppp->ccp->proto = Pccp;
                ppp->ccp->state = Sclosed;

                ppp->ipcp = mallocz(sizeof(*ppp->ipcp), 1);
                if(ppp->ipcp == nil)
                        abort();
                ppp->ipcp->proto = Pipcp;
                ppp->ipcp->state = Sclosed;

                ppp->chap = mallocz(sizeof(*ppp->chap), 1);
                if(ppp->chap == nil)
                        abort();
                ppp->chap->proto = APmschap;
                ppp->chap->state = Cunauth;
                auth_freechal(ppp->chap->cs);
                ppp->chap->cs = nil;

                switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
                case -1:
                        sysfatal("forking ppptimer");
                case 0:
                        ppptimer(ppp);
                        _exits(0);
                }
        }

        ppp->ctcp = compress_init(ppp->ctcp);
        pinit(ppp, ppp->lcp);
        setphase(ppp, Plink);
}

static void
setphase(PPP *ppp, int phase)
{
        int oldphase;

        oldphase = ppp->phase;

        ppp->phase = phase;
        switch(phase){
        default:
                sysfatal("ppp: unknown phase %d", phase);
        case Pdead:
                /* restart or exit? */
                pinit(ppp, ppp->lcp);
                setphase(ppp, Plink);
                break;
        case Plink:
                /* link down */
                switch(oldphase) {
                case Pauth:
                        auth_freechal(ppp->chap->cs);
                        ppp->chap->cs = nil;
                        ppp->chap->state = Cunauth;
                        break;
                case Pnet:
                        auth_freechal(ppp->chap->cs);
                        ppp->chap->cs = nil;
                        ppp->chap->state = Cunauth;
                        newstate(ppp, ppp->ccp, Sclosed);
                        newstate(ppp, ppp->ipcp, Sclosed);
                }
                break;
        case Pauth:
                if(server)
                        chapinit(ppp);
                else if(ppp->chap->proto == APpasswd)
                        papinit(ppp);
                else
                        setphase(ppp, Pnet);
                break;
        case Pnet:
                pinit(ppp, ppp->ccp);
                pinit(ppp, ppp->ipcp);
                break;
        case Pterm:
                /* what? */
                break;
        }
}

static void
pinit(PPP *ppp, Pstate *p)
{
        p->timeout = 0;

        switch(p->proto){
        case Plcp:
                ppp->magic = truerand();
                ppp->xctlmap = 0xffffffff;
                ppp->period = 0;
                p->optmask = 0xffffffff;
                if(!server)
                        p->optmask &=  ~(Fauth|Fmtu);
                ppp->rctlmap = 0;
                ppp->ipcp->state = Sclosed;
                ppp->ipcp->optmask = 0xffffffff;

                p->echotimeout = 0;

                /* quality goo */
                ppp->timeout = 0;
                memset(&ppp->in, 0, sizeof(ppp->in));
                memset(&ppp->out, 0, sizeof(ppp->out));
                memset(&ppp->pin, 0, sizeof(ppp->pin));
                memset(&ppp->pout, 0, sizeof(ppp->pout));
                memset(&ppp->sin, 0, sizeof(ppp->sin));
                break;
        case Pccp:
                if(nocompress)
                        p->optmask = 0;
                else
                        p->optmask = Fcmppc;

                if(ppp->ctype != nil)
                        (*ppp->ctype->fini)(ppp->cstate);
                ppp->ctype = nil;
                ppp->ctries = 0;
                ppp->cstate = nil;

                if(ppp->unctype)
                        (*ppp->unctype->fini)(ppp->uncstate);
                ppp->unctype = nil;
                ppp->uncstate = nil;
                break;
        case Pipcp:
                p->optmask = 0xffffffff;
                ppp->ctcp = compress_init(ppp->ctcp);
                break;
        }
        p->confid = p->rcvdconfid = -1;
        config(ppp, p, 1);
        newstate(ppp, p, Sreqsent);
}

/*
 *  change protocol to a new state.
 */
static void
newstate(PPP *ppp, Pstate *p, int state)
{
        char *err;

        netlog("ppp: %ux %s->%s ctlmap %lux/%lux flags %lux mtu %ld mru %ld\n",
                p->proto, snames[p->state], snames[state], ppp->rctlmap,
                ppp->xctlmap, p->flags,
                ppp->mtu, ppp->mru);
        syslog(0, "ppp", "%ux %s->%s ctlmap %lux/%lux flags %lux mtu %ld mru %ld",
                p->proto, snames[p->state], snames[state], ppp->rctlmap,
                ppp->xctlmap, p->flags,
                ppp->mtu, ppp->mru);

        if(p->proto == Plcp) {
                if(state == Sopened)
                        setphase(ppp, noauth? Pnet : Pauth);
                else if(state == Sclosed)
                        setphase(ppp, Pdead);
                else if(p->state == Sopened)
                        setphase(ppp, Plink);
        }

        if(p->proto == Pccp && state == Sopened) {
                if(ppp->unctype)
                        (*ppp->unctype->fini)(ppp->uncstate);
                ppp->unctype = nil;
                ppp->uncstate = nil;
                if(p->optmask & Fcmppc) {
                        ppp->unctype = &uncmppc;
                        ppp->uncstate = (*uncmppc.init)(ppp);
                }
                if(p->optmask & Fcthwack){
                        ppp->unctype = &uncthwack;
                        ppp->uncstate = (*uncthwack.init)(ppp);
                }
        }

        if(p->proto == Pipcp && state == Sopened) {
                if(server && !noauth && ppp->chap->state != Cauthok)
                        abort();

                err = ipopen(ppp);
                if(err != nil)
                        sysfatal("%s", err);
        }

        p->state = state;
}

/* returns (protocol, information) */
static Block*
getframe(PPP *ppp, int *protop)
{
        uchar *p, *from, *to;
        int n, len, proto;
        ulong c;
        ushort fcs;
        Block *buf, *b;

        *protop = 0;
        if(ppp->framing == 0) {
                /* assume data is already framed */
                b = allocb(2000);
                len = b->lim - b->wptr;
                n = read(ppp->mediain, b->wptr, len);
                dmppkt("RX", b->wptr, n);
                if(n <= 0 || n == len){
                        freeb(b);

                        return nil;
                }
                b->wptr += n;

                /* should probably copy to another block if small */

                if(pppframing && b->rptr[0] == PPP_addr && b->rptr[1] == PPP_ctl)
                        b->rptr += 2;
                proto = *b->rptr++;
                if((proto & 0x1) == 0)
                        proto = (proto<<8) | *b->rptr++;

                if(b->rptr >= b->wptr){
                        freeb(b);
                        return nil;
                }

                ppp->in.uchars += n;
                ppp->in.packets++;
                *protop = proto;
                netlog("getframe 0x%x\n", proto);
                return b;
        }

        buf = ppp->inbuf;
        for(;;){
                /* read till we hit a frame uchar or run out of room */
                for(p = buf->rptr; buf->wptr < buf->lim;){
                        for(; p < buf->wptr; p++)
                                if(*p == HDLC_frame)
                                        break;
                        if(p != buf->wptr)
                                break;

                        len = buf->lim - buf->wptr;
                        n = read(ppp->mediain, buf->wptr, len);
                        if(n <= 0){
                                syslog(0, LOG, "medium read returns %d: %r", n);
                                buf->wptr = buf->rptr;
                                return nil;
                        }
                        dmppkt("RX", buf->wptr, n);
                        buf->wptr += n;
                }

                /* copy into block, undoing escapes, and caculating fcs */
                fcs = PPP_initfcs;
                b = allocb(p - buf->rptr);
                to = b->wptr;
                for(from = buf->rptr; from != p;){
                        c = *from++;
                        if(c == HDLC_esc){
                                if(from == p)
                                        break;
                                c = *from++ ^ 0x20;
                        } else if((c < 0x20) && (ppp->rctlmap & (1 << c)))
                                continue;
                        *to++ = c;
                        fcs = (fcs >> 8) ^ fcstab[(fcs ^ c) & 0xff];
                }

                /* copy down what's left in buffer */
                p++;
                memmove(buf->rptr, p, buf->wptr - p);
                n = p - buf->rptr;
                buf->wptr -= n;
                b->wptr = to - 2;

                /* return to caller if checksum matches */
                if(fcs == PPP_goodfcs){
                        if(b->rptr[0] == PPP_addr && b->rptr[1] == PPP_ctl)
                                b->rptr += 2;
                        proto = *b->rptr++;
                        if((proto & 0x1) == 0)
                                proto = (proto<<8) | *b->rptr++;
                        if(b->rptr < b->wptr){
                                ppp->in.uchars += n;
                                ppp->in.packets++;
                                *protop = proto;
                                netlog("getframe 0x%x\n", proto);
                                return b;
                        }
                } else if(BLEN(b) > 0){
                        if(ppp->ctcp)
                                compress_error(ppp->ctcp);
                        ppp->in.discards++;
                        netlog("ppp: discard len %ld/%ld cksum %ux (%ux %ux %ux %ux)\n",
                                BLEN(b), BLEN(buf), fcs, b->rptr[0],
                                b->rptr[1], b->rptr[2], b->rptr[3]);
                }

                freeb(b);
        }
}

/* send a PPP frame */
static int
putframe(PPP *ppp, int proto, Block *b)
{
        Block *buf;
        uchar *to, *from;
        ushort fcs;
        ulong ctlmap;
        uchar c;
        Block *bp;

        ppp->out.packets++;

        if(proto == Plcp)
                ctlmap = 0xffffffff;
        else
                ctlmap = ppp->xctlmap;

        /* make sure we have head room */
        if(b->rptr - b->base < 4){
                b = padb(b, 4);
                b->rptr += 4;
        }

        netlog("ppp: putframe 0x%ux %ld\n", proto, b->wptr-b->rptr);

        /* add in the protocol and address, we'd better have left room */
        from = b->rptr;
        *--from = proto;
        if(!(ppp->lcp->flags&Fpc) || proto > 0x100 || proto == Plcp)
                *--from = proto>>8;
        if(pppframing && (!(ppp->lcp->flags&Fac) || proto == Plcp)){
                *--from = PPP_ctl;
                *--from = PPP_addr;
        }

        qlock(&ppp->outlock);
        buf = ppp->outbuf;

        if(ppp->framing == 0) {
                to = buf->rptr;
                for(bp = b; bp; bp = bp->next){
                        if(bp != b)
                                from = bp->rptr;
                        memmove(to, from, bp->wptr-from);
                        to += bp->wptr-from;
                }
        } else {
                /* escape and checksum the body */
                fcs = PPP_initfcs;
                to = buf->rptr;
        
                /* add frame marker */
                *to++ = HDLC_frame;

                for(bp = b; bp; bp = bp->next){
                        if(bp != b)
                                from = bp->rptr;
                        for(; from < bp->wptr; from++){
                                c = *from;
                                if(c == HDLC_frame || c == HDLC_esc
                                   || (c < 0x20 && ((1<<c) & ctlmap))){
                                        *to++ = HDLC_esc;
                                        *to++ = c ^ 0x20;
                                } else 
                                        *to++ = c;
                                fcs = (fcs >> 8) ^ fcstab[(fcs ^ c) & 0xff];
                        }
                }

                /* add on and escape the checksum */
                fcs = ~fcs;
                c = fcs;
                if(c == HDLC_frame || c == HDLC_esc
                   || (c < 0x20 && ((1<<c) & ctlmap))){
                        *to++ = HDLC_esc;
                        *to++ = c ^ 0x20;
                } else 
                        *to++ = c;
                c = fcs>>8;
                if(c == HDLC_frame || c == HDLC_esc
                   || (c < 0x20 && ((1<<c) & ctlmap))){
                        *to++ = HDLC_esc;
                        *to++ = c ^ 0x20;
                } else 
                        *to++ = c;
        
                /* add frame marker */
                *to++ = HDLC_frame;
        }

        /* send */
        buf->wptr = to;
        dmppkt("TX", buf->rptr, BLEN(buf));
        if(write(ppp->mediaout, buf->rptr, BLEN(buf)) < 0){
                qunlock(&ppp->outlock);
                return -1;
        }
        ppp->out.uchars += BLEN(buf);

        qunlock(&ppp->outlock);
        return 0;
}

Block*
alloclcp(int code, int id, int len, Lcpmsg **mp)
{
        Block *b;
        Lcpmsg *m;

        /*
         *  leave room for header
         */
        b = allocb(len);

        m = (Lcpmsg*)b->wptr;
        m->code = code;
        m->id = id;
        b->wptr += 4;

        *mp = m;
        return b;
}


static void
putlo(Block *b, int type, ulong val)
{
        *b->wptr++ = type;
        *b->wptr++ = 6;
        hnputl(b->wptr, val);
        b->wptr += 4;
}

static void
putv4o(Block *b, int type, Ipaddr val)
{
        *b->wptr++ = type;
        *b->wptr++ = 6;
        v6tov4(b->wptr, val);
        b->wptr += 4;
}

static void
putso(Block *b, int type, ulong val)
{
        *b->wptr++ = type;
        *b->wptr++ = 4;
        hnputs(b->wptr, val);
        b->wptr += 2;
}

static void
puto(Block *b, int type)
{
        *b->wptr++ = type;
        *b->wptr++ = 2;
}

/*
 *  send configuration request
 */
static void
config(PPP *ppp, Pstate *p, int newid)
{
        Block *b;
        Lcpmsg *m;
        int id;

        if(newid){
                id = p->id++;
                p->confid = id;
                p->timeout = Timeout;
        } else
                id = p->confid;
        b = alloclcp(Lconfreq, id, 256, &m);
        USED(m);

        switch(p->proto){
        case Plcp:
                if(p->optmask & Fctlmap)
                        putlo(b, Octlmap, 0);   /* we don't want anything escaped */
                if(p->optmask & Fmagic)
                        putlo(b, Omagic, ppp->magic);
                if(p->optmask & Fmtu)
                        putso(b, Omtu, ppp->mru);
                if(p->optmask & Fauth) {
                        *b->wptr++ = Oauth;
                        *b->wptr++ = 5;
                        hnputs(b->wptr, Pchap);
                        b->wptr += 2;
                        *b->wptr++ = ppp->chap->proto;
                }
                if(p->optmask & Fpc)
                        puto(b, Opc);
                if(p->optmask & Fac)
                        puto(b, Oac);
                break;
        case Pccp:
                if(p->optmask & Fcthwack)
                        puto(b, Octhwack);
                else if(p->optmask & Fcmppc) {
                        *b->wptr++ = Ocmppc;
                        *b->wptr++ = 6;
                        *b->wptr++ = 0;
                        *b->wptr++ = 0;
                        *b->wptr++ = 0;
                        *b->wptr++ = 0x41;
                }
                break;
        case Pipcp:
                if(p->optmask & Fipaddr)
{syslog(0, "ppp", "requesting %I", ppp->local);
                        putv4o(b, Oipaddr, ppp->local);
}
                if(primary && (p->optmask & Fipdns))
                        putv4o(b, Oipdns, ppp->dns[0]);
                if(primary && (p->optmask & Fipdns2))
                        putv4o(b, Oipdns2, ppp->dns[1]);
                if(primary && (p->optmask & Fipwins))
                        putv4o(b, Oipwins, ppp->wins[0]);
                if(primary && (p->optmask & Fipwins2))
                        putv4o(b, Oipwins2, ppp->wins[1]);
                /*
                 * don't ask for header compression while data compression is still pending.
                 * perhaps we should restart ipcp negotiation if compression negotiation fails.
                 */
                if(!noipcompress && !ppp->ccp->optmask && (p->optmask & Fipcompress)) {
                        *b->wptr++ = Oipcompress;
                        *b->wptr++ = 6;
                        hnputs(b->wptr, Pvjctcp);
                        b->wptr += 2;
                        *b->wptr++ = MAX_STATES-1;
                        *b->wptr++ = 1;
                }
                break;
        }
        hnputs(m->len, BLEN(b));
        printopts(p, b, 1);
        putframe(ppp, p->proto, b);
        freeb(b);
}

static void
getipinfo(PPP *ppp)
{
        char *av[3];
        int ndns, nwins;
        char ip[64];
        Ndbtuple *t, *nt;

        if(!validv4(ppp->local))
                return;

        av[0] = "dns";
        av[1] = "wins";
        sprint(ip, "%I", ppp->local);
        t = csipinfo(ppp->net, "ip", ip, av, 2);
        ndns = nwins = 0;
        for(nt = t; nt != nil; nt = nt->entry){
                if(strcmp(nt->attr, "dns") == 0){
                        if(ndns < 2)
                                parseip(ppp->dns[ndns++], nt->val);
                } else if(strcmp(nt->attr, "wins") == 0){
                        if(nwins < 2)
                                parseip(ppp->wins[nwins++], nt->val);
                }
        }
        if(t != nil)
                ndbfree(t);
}

/*
 *  parse configuration request, sends an ack or reject packet
 *
 *      returns:        -1 if request was syntacticly incorrect
 *                       0 if packet was accepted
 *                       1 if packet was rejected
 */
static int
getopts(PPP *ppp, Pstate *p, Block *b)
{
        Lcpmsg *m, *repm;       
        Lcpopt *o;
        uchar *cp, *ap;
        ulong rejecting, nacking, flags, proto, chapproto;
        ulong mtu, ctlmap, period;
        ulong x;
        Block *repb;
        Comptype *ctype;
        Ipaddr ipaddr;

        rejecting = 0;
        nacking = 0;
        flags = 0;

        /* defaults */
        invalidate(ipaddr);
        mtu = ppp->mtu;
        ctlmap = 0xffffffff;
        period = 0;
        ctype = nil;
        chapproto = 0;

        m = (Lcpmsg*)b->rptr;
        repb = alloclcp(Lconfack, m->id, BLEN(b), &repm);

        /* copy options into ack packet */
        memmove(repm->data, m->data, b->wptr - m->data);
        repb->wptr += b->wptr - m->data;

        /* look for options we don't recognize or like */
        for(cp = m->data; cp < b->wptr; cp += o->len){
                o = (Lcpopt*)cp;
                if(cp + o->len > b->wptr || o->len==0){
                        freeb(repb);
                        netlog("ppp: bad option length %ux\n", o->type);
                        return -1;
                }

                switch(p->proto){
                case Plcp:
                        switch(o->type){
                        case Oac:
                                flags |= Fac;
                                continue;
                        case Opc:
                                flags |= Fpc;
                                continue;
                        case Omtu:
                                mtu = nhgets(o->data);
                                continue;
                        case Omagic:
                                if(ppp->magic == nhgetl(o->data))
                                        netlog("ppp: possible loop\n");
                                continue;
                        case Octlmap:
                                ctlmap = nhgetl(o->data);
                                continue;
                        case Oquality:
                                proto = nhgets(o->data);
                                if(proto != Plqm)
                                        break;
                                x = nhgetl(o->data+2)*10;
                                period = (x+Period-1)/Period;
                                continue;
                        case Oauth:
                                proto = nhgets(o->data);
                                if(proto == Ppasswd && !server){
                                        chapproto = APpasswd;
                                        continue;
                                }
                                if(proto != Pchap)
                                        break;
                                if(o->data[2] != APmd5 && o->data[2] != APmschap)
                                        break;
                                chapproto = o->data[2];
                                continue;
                        }
                        break;
                case Pccp:
                        if(nocompress)
                                break;
                        switch(o->type){
                        case Octhwack:
                                break;
                        /*
                                if(o->len == 2){
                                        ctype = &cthwack;
                                        continue;
                                }
                                if(!nacking){
                                        nacking = 1;
                                        repb->wptr = repm->data;
                                        repm->code = Lconfnak;
                                }
                                puto(repb, Octhwack);
                                continue;
                        */
                        case Ocmppc:
                                x = nhgetl(o->data);

                                // hack for Mac
                                // if(x == 0)
                                //      continue;

                                /* stop ppp loops */
                                if((x&0x41) == 0 || ppp->ctries++ > 5) {
                                        /*
                                         * turn off requests as well - I don't think this
                                         * is needed in the standard
                                         */
                                        p->optmask &= ~Fcmppc;
                                        break;
                                }
                                if(rejecting)
                                        continue;
                                if(x & 1) {
                                        ctype = &cmppc;
                                        ppp->sendencrypted = (o->data[3]&0x40) == 0x40;
                                        continue;
                                }
                                if(!nacking){
                                        nacking = 1;
                                        repb->wptr = repm->data;
                                        repm->code = Lconfnak;
                                }
                                *repb->wptr++ = Ocmppc;
                                *repb->wptr++ = 6;
                                *repb->wptr++ = 0;
                                *repb->wptr++ = 0;
                                *repb->wptr++ = 0;
                                *repb->wptr++ = 0x41;
                                continue;
                        }
                        break;
                case Pipcp:
                        switch(o->type){
                        case Oipaddr:   
                                v4tov6(ipaddr, o->data);
                                if(!validv4(ppp->remote))
                                        continue;
                                if(!validv4(ipaddr) && !rejecting){
                                        /* other side requesting an address */
                                        if(!nacking){
                                                nacking = 1;
                                                repb->wptr = repm->data;
                                                repm->code = Lconfnak;
                                        }
                                        putv4o(repb, Oipaddr, ppp->remote);
                                }
                                continue;
                        case Oipdns:
                                ap = ppp->dns[0];
                                goto ipinfo;
                        case Oipdns2:   
                                ap = ppp->dns[1];
                                goto ipinfo;
                        case Oipwins:   
                                ap = ppp->wins[0];
                                goto ipinfo;
                        case Oipwins2:
                                ap = ppp->wins[1];
                                goto ipinfo;
                        ipinfo:
                                if(!validv4(ap))
                                        getipinfo(ppp);
                                if(!validv4(ap))
                                        break;
                                v4tov6(ipaddr, o->data);
                                if(!validv4(ipaddr) && !rejecting){
                                        /* other side requesting an address */
                                        if(!nacking){
                                                nacking = 1;
                                                repb->wptr = repm->data;
                                                repm->code = Lconfnak;
                                        }
                                        putv4o(repb, o->type, ap);
                                }
                                continue;
                        case Oipcompress:
                                /*
                                 * don't compress tcp header if we've negotiated data compression.
                                 * tcp header compression has very poor performance if there is an error.
                                 */
                                proto = nhgets(o->data);
                                if(noipcompress || proto != Pvjctcp || ppp->ctype != nil)
                                        break;
                                if(compress_negotiate(ppp->ctcp, o->data+2) < 0)
                                        break;
                                flags |= Fipcompress;
                                continue;
                        }
                        break;
                }

                /* come here if option is not recognized */
                if(!rejecting){
                        rejecting = 1;
                        repb->wptr = repm->data;
                        repm->code = Lconfrej;
                }
                netlog("ppp: bad %ux option %d\n", p->proto, o->type);
                memmove(repb->wptr, o, o->len);
                repb->wptr += o->len;
        }

        /* permanent changes only after we know that we liked the packet */
        if(!rejecting && !nacking){
                switch(p->proto){
                case Plcp:
                        ppp->period = period;
                        ppp->xctlmap = ctlmap;
                        if(mtu > Maxmtu)
                                mtu = Maxmtu;
                        if(mtu < Minmtu)
                                mtu = Minmtu;
                        ppp->mtu = mtu;
                        if(chapproto)
                                ppp->chap->proto = chapproto;
                        
                        break;
                case Pccp:
                        if(ppp->ctype != nil){
                                (*ppp->ctype->fini)(ppp->cstate);
                                ppp->cstate = nil;
                        }
                        ppp->ctype = ctype;
                        if(ctype)
                                ppp->cstate = (*ctype->init)(ppp);
                        break;
                case Pipcp:
                        if(validv4(ipaddr) && ppp->remotefrozen == 0)
                                ipmove(ppp->remote, ipaddr);
                        break;
                }
                p->flags = flags;
        }

        hnputs(repm->len, BLEN(repb));
        printopts(p, repb, 1);
        putframe(ppp, p->proto, repb);
        freeb(repb);

        return rejecting || nacking;
}
static void
dmppkt(char *s, uchar *a, int na)
{
        int i;

        if (debug < 3)
                return;

        fprint(2, "%s", s);
        for(i = 0; i < na; i++)
                fprint(2, " %.2ux", a[i]);
        fprint(2, "\n");
}

static void
dropoption(Pstate *p, Lcpopt *o)
{
        unsigned n = o->type;

        switch(n){
        case Oipaddr:
                break;
        case Oipdns:
                p->optmask &= ~Fipdns;
                break;
        case Oipwins:
                p->optmask &= ~Fipwins;
                break;
        case Oipdns2:
                p->optmask &= ~Fipdns2;
                break;
        case Oipwins2:
                p->optmask &= ~Fipwins2;
                break;
        default:
                if(o->type < 8*sizeof(p->optmask))
                        p->optmask &= ~(1<<o->type);
                break;
        }
}

/*
 *  parse configuration rejection, just stop sending anything that they
 *  don't like (except for ipcp address nak).
 */
static void
rejopts(PPP *ppp, Pstate *p, Block *b, int code)
{
        Lcpmsg *m;
        Lcpopt *o;
        uchar newip[IPaddrlen];

        /* just give up trying what the other side doesn't like */
        m = (Lcpmsg*)b->rptr;
        for(b->rptr = m->data; b->rptr < b->wptr; b->rptr += o->len){
                o = (Lcpopt*)b->rptr;
                if(b->rptr + o->len > b->wptr){
                        netlog("ppp: bad roption length %ux\n", o->type);
                        return;
                }

                if(code == Lconfrej){
                        dropoption(p, o);
                        netlog("ppp: %ux rejecting %d\n",
                                        p->proto, o->type);
                        continue;
                }

                switch(p->proto){
                case Plcp:
                        switch(o->type){
                        case Octlmap:
                                ppp->rctlmap = nhgetl(o->data);
                                break;
                        case Oauth:
                                /* don't allow client to request no auth */
                                /* could try different auth protocol here */
                                fprint(2, "ppp: can not reject CHAP\n");
                                exits("ppp: CHAP");
                                break;
                        default:
                                if(o->type < 8*sizeof(p->optmask))
                                        p->optmask &= ~(1<<o->type);
                                break;
                        };
                        break;
                case Pccp:
                        switch(o->type){
                        default:
                                dropoption(p, o);
                                break;
                        }
                        break;
                case Pipcp:
                        switch(o->type){
                        case Oipaddr:
syslog(0, "ppp", "rejected addr %I with %V", ppp->local, o->data);
                                /* if we're a server, don't let other end change our addr */
                                if(ppp->localfrozen){
                                        dropoption(p, o);
                                        break;
                                }

                                /* accept whatever server tells us */
                                if(!validv4(ppp->local)){
                                        v4tov6(ppp->local, o->data);
                                        dropoption(p, o);
                                        break;
                                }

                                /* if he didn't like our addr, ask for a generic one */
                                v4tov6(newip, o->data);
                                if(!validv4(newip)){
                                        invalidate(ppp->local);
                                        break;
                                }

                                /* if he gives us something different, use it anyways */
                                v4tov6(ppp->local, o->data);
                                dropoption(p, o);
                                break;
                        case Oipdns:
                                if (!validv4(ppp->dns[0])){
                                        v4tov6(ppp->dns[0], o->data);
                                        dropoption(p, o);
                                        break;
                                }
                                v4tov6(newip, o->data);
                                if(!validv4(newip)){
                                        invalidate(ppp->dns[0]);
                                        break;
                                }
                                v4tov6(ppp->dns[0], o->data);
                                dropoption(p, o);
                                break;
                        case Oipwins:
                                if (!validv4(ppp->wins[0])){
                                        v4tov6(ppp->wins[0], o->data);
                                        dropoption(p, o);
                                        break;
                                }
                                v4tov6(newip, o->data);
                                if(!validv4(newip)){
                                        invalidate(ppp->wins[0]);
                                        break;
                                }
                                v4tov6(ppp->wins[0], o->data);
                                dropoption(p, o);
                                break;
                        case Oipdns2:
                                if (!validv4(ppp->dns[1])){
                                        v4tov6(ppp->dns[1], o->data);
                                        dropoption(p, o);
                                        break;
                                }
                                v4tov6(newip, o->data);
                                if(!validv4(newip)){
                                        invalidate(ppp->dns[1]);
                                        break;
                                }
                                v4tov6(ppp->dns[1], o->data);
                                dropoption(p, o);
                                break;
                        case Oipwins2:
                                if (!validv4(ppp->wins[1])){
                                        v4tov6(ppp->wins[1], o->data);
                                        dropoption(p, o);
                                        break;
                                }
                                v4tov6(newip, o->data);
                                if(!validv4(newip)){
                                        invalidate(ppp->wins[1]);
                                        break;
                                }
                                v4tov6(ppp->wins[1], o->data);
                                dropoption(p, o);
                                break;
                        default:
                                dropoption(p, o);
                                break;
                        }
                        break;
                }
        }
}


/*
 *  put a messages through the lcp or ipcp state machine.  They are
 *  very similar.
 */
static void
rcv(PPP *ppp, Pstate *p, Block *b)
{
        ulong len;
        int err;
        Lcpmsg *m;
        int proto;

        if(BLEN(b) < 4){
                netlog("ppp: short lcp message\n");
                freeb(b);
                return;
        }
        m = (Lcpmsg*)b->rptr;
        len = nhgets(m->len);
        if(BLEN(b) < len){
                netlog("ppp: short lcp message\n");
                freeb(b);
                return;
        }

        netlog("ppp: %ux rcv %d len %ld id %d/%d/%d\n",
                p->proto, m->code, len, m->id, p->confid, p->id);

        if(p->proto != Plcp && ppp->lcp->state != Sopened){
                netlog("ppp: non-lcp with lcp not open\n");
                freeb(b);
                return;
        }

        qlock(ppp);
        switch(m->code){
        case Lconfreq:
                printopts(p, b, 0);
                err = getopts(ppp, p, b);
                if(err < 0)
                        break;

                if(m->id == p->rcvdconfid)
                        break;                  /* don't change state for duplicates */

                switch(p->state){
                case Sackrcvd:
                        if(err)
                                break;
                        newstate(ppp, p, Sopened);
                        break;
                case Sclosed:
                case Sopened:
                        config(ppp, p, 1);
                        if(err == 0)
                                newstate(ppp, p, Sacksent);
                        else
                                newstate(ppp, p, Sreqsent);
                        break;
                case Sreqsent:
                case Sacksent:
                        if(err == 0)
                                newstate(ppp, p, Sacksent);
                        else
                                newstate(ppp, p, Sreqsent);
                        break;
                }
                break;
        case Lconfack:
                if(p->confid != m->id){
                        /* ignore if it isn't the message we're sending */
                        netlog("ppp: dropping confack\n");
                        break;
                }
                p->confid = -1;         /* ignore duplicates */
                p->id++;                /* avoid sending duplicates */

                netlog("ppp: recv confack\n");
                switch(p->state){
                case Sopened:
                case Sackrcvd:
                        config(ppp, p, 1);
                        newstate(ppp, p, Sreqsent);
                        break;
                case Sreqsent:
                        newstate(ppp, p, Sackrcvd);
                        break;
                case Sacksent:
                        newstate(ppp, p, Sopened);
                        break;
                }
                break;
        case Lconfrej:
        case Lconfnak:
                if(p->confid != m->id) {
                        /* ignore if it isn't the message we're sending */
                        netlog("ppp: dropping confrej or confnak\n");
                        break;
                }
                p->confid = -1;         /* ignore duplicates */
                p->id++;                /* avoid sending duplicates */

                switch(p->state){
                case Sopened:
                case Sackrcvd:
                        config(ppp, p, 1);
                        newstate(ppp, p, Sreqsent);
                        break;
                case Sreqsent:
                case Sacksent:
                        printopts(p, b, 0);
                        rejopts(ppp, p, b, m->code);
                        config(ppp, p, 1);
                        break;
                }
                break;
        case Ltermreq:
                m->code = Ltermack;
                putframe(ppp, p->proto, b);

                switch(p->state){
                case Sackrcvd:
                case Sacksent:
                        newstate(ppp, p, Sreqsent);
                        break;
                case Sopened:
                        newstate(ppp, p, Sclosing);
                        break;
                }
                break;
        case Ltermack:
                if(p->termid != m->id)  /* ignore if it isn't the message we're sending */
                        break;

                if(p->proto == Plcp)
                        ppp->ipcp->state = Sclosed;
                switch(p->state){
                case Sclosing:
                        newstate(ppp, p, Sclosed);
                        break;
                case Sackrcvd:
                        newstate(ppp, p, Sreqsent);
                        break;
                case Sopened:
                        config(ppp, p, 0);
                        newstate(ppp, p, Sreqsent);
                        break;
                }
                break;
        case Lcoderej:
                //newstate(ppp, p, Sclosed);
                syslog(0, LOG, "code reject %d\n", m->data[0]);
                break;
        case Lprotorej:
                proto = nhgets(m->data);
                netlog("ppp: proto reject %ux\n", proto);
                if(proto == Pccp)
                        newstate(ppp, ppp->ccp, Sclosed);
                break;
        case Lechoreq:
                if(BLEN(b) < 8){
                        netlog("ppp: short lcp echo request\n");
                        freeb(b);
                        return;
                }
                m->code = Lechoack;
                hnputl(m->data, ppp->magic);
                putframe(ppp, p->proto, b);
                break;
        case Lechoack:
                p->echoack = 1;
                break;
        case Ldiscard:
                /* nothing to do */
                break;
        case Lresetreq:
                if(p->proto != Pccp)
                        break;
                ppp->stat.compreset++;
                if(ppp->ctype != nil)
                        b = (*ppp->ctype->resetreq)(ppp->cstate, b);
                if(b != nil) {
                        m = (Lcpmsg*)b->rptr;
                        m->code = Lresetack;
                        putframe(ppp, p->proto, b);
                }
                break;
        case Lresetack:
                if(p->proto != Pccp)
                        break;
                if(ppp->unctype != nil)
                        (*ppp->unctype->resetack)(ppp->uncstate, b);
                break;
        }

        qunlock(ppp);
        freeb(b);
}

/*
 *  timer for protocol state machine
 */
static void
ptimer(PPP *ppp, Pstate *p)
{
        if(p->state == Sopened || p->state == Sclosed)
                return;

        p->timeout--;
        switch(p->state){
        case Sclosing:
                sendtermreq(ppp, p);
                break;
        case Sreqsent:
        case Sacksent:
                if(p->timeout <= 0)
                        newstate(ppp, p, Sclosed);
                else {
                        config(ppp, p, 0);
                }
                break;
        case Sackrcvd:
                if(p->timeout <= 0)
                        newstate(ppp, p, Sclosed);
                else {
                        config(ppp, p, 0);
                        newstate(ppp, p, Sreqsent);
                }
                break;
        }
}

/* paptimer -- pap timer event handler
 *
 * If PAP authorization hasn't come through, resend an authreqst.  If
 * the maximum number of requests have been sent (~ 30 seconds), give
 * up.
 *
 */
static void
authtimer(PPP* ppp)
{
        if(ppp->chap->proto != APpasswd)
                return;

        if(ppp->chap->id < 21)
                putpaprequest(ppp);
        else {
                terminate(ppp, 0);
                netlog("ppp: pap timed out--not authorized\n");
        }
}


/*
 *  timer for ppp
 */
static void
ppptimer(PPP *ppp)
{
        while(!dying){
                sleep(Period);
                qlock(ppp);

                netlog("ppp: ppptimer\n");
                ptimer(ppp, ppp->lcp);
                if(ppp->lcp->state == Sopened) {
                        switch(ppp->phase){
                        case Pnet:
                                ptimer(ppp, ppp->ccp);
                                ptimer(ppp, ppp->ipcp);
                                break;
                        case Pauth:
                                authtimer(ppp);
                                break;
                        }
                }

                /* link quality measurement */
                if(ppp->period && --(ppp->timeout) <= 0){
                        ppp->timeout = ppp->period;
                        putlqm(ppp);
                }

                qunlock(ppp);
        }
}

static void
setdefroute(char *net, Ipaddr gate)
{
        int fd;
        char path[128];

        snprint(path, sizeof path, "%s/iproute", net);
        fd = open(path, ORDWR);
        if(fd < 0)
                return;
        fprint(fd, "add 0 0 %I", gate);
        close(fd);
}

enum
{
        Mofd=   32,
};
struct
{
        Lock;

        int     fd[Mofd];
        int     cfd[Mofd];
        int     n;
} old;

static char*
ipopen(PPP *ppp)
{
        static int ipinprocpid;
        int n, cfd, fd;
        char path[128];
        char buf[128];

        if(ipinprocpid <= 0){
                snprint(path, sizeof path, "%s/ipifc/clone", ppp->net);
                cfd = open(path, ORDWR);
                if(cfd < 0)
                        return "can't open ip interface";

                n = read(cfd, buf, sizeof(buf) - 1);
                if(n <= 0){
                        close(cfd);
                        return "can't open ip interface";
                }
                buf[n] = 0;

                netlog("ppp: setting up IP interface local %I remote %I (valid %d)\n",
                        ppp->local, ppp->remote, validv4(ppp->remote));
                if(!validv4(ppp->remote))
                        ipmove(ppp->remote, ppp->local);

                snprint(path, sizeof path, "%s/ipifc/%s/data", ppp->net, buf);
                fd = open(path, ORDWR);
                if(fd < 0){
                        close(cfd);
                        return "can't open ip interface";
                }

                if(fprint(cfd, "bind pkt") < 0)
                        return "binding pkt to ip interface";
                if(fprint(cfd, "add %I 255.255.255.255 %I %lud proxy", ppp->local,
                        ppp->remote, ppp->mtu-10) < 0){
                        close(cfd);
                        return "can't set addresses";
                }
                if(primary)
                        setdefroute(ppp->net, ppp->remote);
                ppp->ipfd = fd;
                ppp->ipcfd = cfd;

                /* signal main() that ip is configured */
                rendezvous((void*)Rmagic, 0);

                switch(ipinprocpid = rfork(RFPROC|RFMEM|RFNOWAIT)){
                case -1:
                        sysfatal("forking ipinproc");
                case 0:
                        ipinproc(ppp);
                        terminate(ppp, 1);
                        _exits(0);
                }
        } else {
                /* we may have changed addresses */
                if(ipcmp(ppp->local, ppp->curlocal) != 0 ||
                   ipcmp(ppp->remote, ppp->curremote) != 0){
                        snprint(buf, sizeof buf, "remove %I 255.255.255.255 %I",
                            ppp->curlocal, ppp->curremote);
                        if(fprint(ppp->ipcfd, "%s", buf) < 0)
                                syslog(0, "ppp", "can't %s: %r", buf);
                        snprint(buf, sizeof buf, "add %I 255.255.255.255 %I %lud proxy",
                            ppp->local, ppp->remote, ppp->mtu-10);
                        if(fprint(ppp->ipcfd, "%s", buf) < 0)
                                syslog(0, "ppp", "can't %s: %r", buf);
                }
                syslog(0, "ppp", "%I/%I -> %I/%I", ppp->curlocal, ppp->curremote,
                   ppp->local, ppp->remote);
        }
        ipmove(ppp->curlocal, ppp->local);
        ipmove(ppp->curremote, ppp->remote);

        return nil;
}

/* return next input IP packet */
Block*
pppread(PPP *ppp)
{
        Block *b, *reply;
        int proto, len;
        Lcpmsg *m;

        while(!dying){
                b = getframe(ppp, &proto);
                if(b == nil)
                        return nil;

Again:
                switch(proto){
                case Plcp:
                        rcv(ppp, ppp->lcp, b);
                        break;
                case Pccp:
                        rcv(ppp, ppp->ccp, b);
                        break;
                case Pipcp:
                        rcv(ppp, ppp->ipcp, b);
                        break;
                case Pip:
                        if(ppp->ipcp->state == Sopened)
                                return b;
                        netlog("ppp: IP recved: link not up\n");
                        freeb(b);
                        break;
                case Plqm:
                        getlqm(ppp, b);
                        break;
                case Pchap:
                        getchap(ppp, b);
                        break;
                case Ppasswd:
                        getpap(ppp, b);
                        break;
                case Pvjctcp:
                case Pvjutcp:
                        if(ppp->ipcp->state != Sopened){
                                netlog("ppp: VJ tcp recved: link not up\n");
                                freeb(b);
                                break;
                        }
                        ppp->stat.vjin++;
                        b = tcpuncompress(ppp->ctcp, b, proto);
                        if(b != nil)
                                return b;
                        ppp->stat.vjfail++;
                        break;
                case Pcdata:
                        ppp->stat.uncomp++;
                        if(ppp->ccp->state != Sopened){
                                netlog("ppp: compressed data recved: link not up\n");
                                freeb(b);
                                break;
                        }
                        if(ppp->unctype == nil) {
                                netlog("ppp: compressed data recved: no compression\n");
                                freeb(b);
                                break;
                        }
                        len = BLEN(b);
                        b = (*ppp->unctype->uncompress)(ppp, b, &proto, &reply);
                        if(reply != nil){
                                /* send resetreq */
                                ppp->stat.uncompreset++;
                                putframe(ppp, Pccp, reply);
                                freeb(reply);
                        }
                        if(b == nil)
                                break;
                        ppp->stat.uncompin += len;
                        ppp->stat.uncompout += BLEN(b);
/* netlog("ppp: uncompressed frame %ux %d %d (%d uchars)\n", proto, b->rptr[0], b->rptr[1], BLEN(b)); /* */
                        goto Again;     
                default:
                        syslog(0, LOG, "unknown proto %ux", proto);
                        if(ppp->lcp->state == Sopened){
                                /* reject the protocol */
                                b->rptr -= 6;
                                m = (Lcpmsg*)b->rptr;
                                m->code = Lprotorej;
                                m->id = ++ppp->lcp->id;
                                hnputs(m->data, proto);
                                hnputs(m->len, BLEN(b));
                                putframe(ppp, Plcp, b);
                        }
                        freeb(b);
                        break;
                }
        }
        return nil;
}

/* transmit an IP packet */
int
pppwrite(PPP *ppp, Block *b)
{
        int proto;
        int len;

        qlock(ppp);
        /* can't send ip packets till we're established */
        if(ppp->ipcp->state != Sopened) {
                qunlock(ppp);
                syslog(0, LOG, "IP write: link not up");
                len = blen(b);
                freeb(b);
                return len;
        }

        proto = Pip;
        ppp->stat.ipsend++;

        if(ppp->ipcp->flags & Fipcompress){
                b = compress(ppp->ctcp, b, &proto);
                if(b == nil){
                        qunlock(ppp);
                        return 0;
                }
                if(proto != Pip)
                        ppp->stat.vjout++;
        }

        if(ppp->ctype != nil) {
                len = blen(b);
                b = (*ppp->ctype->compress)(ppp, proto, b, &proto);
                if(proto == Pcdata) {
                        ppp->stat.comp++;
                        ppp->stat.compin += len;
                        ppp->stat.compout += blen(b);
                }
        } 

        if(putframe(ppp, proto, b) < 0) {
                qunlock(ppp);
                freeb(b);
                return -1;
        }
        qunlock(ppp);

        len = blen(b);
        freeb(b);
        return len;
}

static void
terminate(PPP *ppp, int kill)
{
        close(ppp->ipfd);
        ppp->ipfd = -1;
        close(ppp->ipcfd);
        ppp->ipcfd = -1;
        close(ppp->mediain);
        close(ppp->mediaout);
        ppp->mediain = -1;
        ppp->mediaout = -1;
        dying = 1;

        if(kill)
                postnote(PNGROUP, getpid(), "die");
}

typedef struct Iphdr Iphdr;
struct Iphdr
{
        uchar   vihl;           /* Version and header length */
        uchar   tos;            /* Type of service */
        uchar   length[2];      /* packet length */
        uchar   id[2];          /* Identification */
        uchar   frag[2];        /* Fragment information */
        uchar   ttl;            /* Time to live */
        uchar   proto;          /* Protocol */
        uchar   cksum[2];       /* Header checksum */
        uchar   src[4];         /* Ip source (uchar ordering unimportant) */
        uchar   dst[4];         /* Ip destination (uchar ordering unimportant) */
};

static void
ipinproc(PPP *ppp)
{
        Block *b;
        int m, n;
        Iphdr *ip;

        while(!dying){

                b = allocb(Buflen);
                n = read(ppp->ipfd, b->wptr, b->lim-b->wptr);
                if(n < 0)
                        break;

                /* trim packet if there's padding (e.g. from ether) */
                ip = (Iphdr*)b->rptr;
                m = nhgets(ip->length);
                if(m < n && m > 0)
                        n = m;
                b->wptr += n;

                if(pppwrite(ppp, b) < 0)
                        break;
        }
}

static void
catchdie(void*, char *msg)
{
        if(strstr(msg, "die") != nil)
                noted(NCONT);
        else
                noted(NDFLT);
}

static void
hexdump(uchar *a, int na)
{
        int i;
        char buf[80];

        fprint(2, "dump %p %d\n", a, na);
        buf[0] = '\0';
        for(i=0; i<na; i++){
                sprint(buf+strlen(buf), " %.2ux", a[i]);
                if(i%16 == 7)
                        sprint(buf+strlen(buf), " --");
                if(i%16==15){
                        sprint(buf+strlen(buf), "\n");
                        write(2, buf, strlen(buf));
                        buf[0] = '\0';
                }
        }
        if(i%16){
                sprint(buf+strlen(buf), "\n");
                write(2, buf, strlen(buf));
        }
}

static void
mediainproc(PPP *ppp)
{
        Block *b;
        Ipaddr remote;

        notify(catchdie);
        while(!dying){
                b = pppread(ppp);
                if(b == nil){
                        syslog(0, LOG, "pppread return nil");
                        break;
                }
                ppp->stat.iprecv++;
                if(ppp->ipcp->state != Sopened) {
                        ppp->stat.iprecvnotup++;
                        freeb(b);
                        continue;
                }

                if(server) {
                        v4tov6(remote, b->rptr+12);
                        if(ipcmp(remote, ppp->remote) != 0) {
                                ppp->stat.iprecvbadsrc++;
                                freeb(b);
                                continue;
                        }
                }
                if(debug > 1){
                        netlog("ip write pkt %p %d\n", b->rptr, blen(b));
                        hexdump(b->rptr, blen(b));
                }
                if(write(ppp->ipfd, b->rptr, blen(b)) < 0) {
                        syslog(0, LOG, "error writing to pktifc");
                        freeb(b);
                        break;
                }

                freeb(b);
        }

        netlog(": remote=%I: ppp shutting down\n", ppp->remote);
        syslog(0, LOG, ": remote=%I: ppp shutting down", ppp->remote);
        syslog(0, LOG, "\t\tppp send = %lud/%lud recv= %lud/%lud",
                ppp->out.packets, ppp->out.uchars,
                ppp->in.packets, ppp->in.uchars);
        syslog(0, LOG, "\t\tip send=%lud", ppp->stat.ipsend);
        syslog(0, LOG, "\t\tip recv=%lud notup=%lud badsrc=%lud",
                ppp->stat.iprecv, ppp->stat.iprecvnotup, ppp->stat.iprecvbadsrc);
        syslog(0, LOG, "\t\tcompress=%lud in=%lud out=%lud reset=%lud",
                ppp->stat.comp, ppp->stat.compin, ppp->stat.compout, ppp->stat.compreset);
        syslog(0, LOG, "\t\tuncompress=%lud in=%lud out=%lud reset=%lud",
                ppp->stat.uncomp, ppp->stat.uncompin, ppp->stat.uncompout,
                ppp->stat.uncompreset);
        syslog(0, LOG, "\t\tvjin=%lud vjout=%lud vjfail=%lud", 
                ppp->stat.vjin, ppp->stat.vjout, ppp->stat.vjfail);
}

/*
 *  link quality management
 */
static void
getlqm(PPP *ppp, Block *b)
{
        Qualpkt *p;

        p = (Qualpkt*)b->rptr;
        if(BLEN(b) == sizeof(Qualpkt)){
                ppp->in.reports++;
                ppp->pout.reports = nhgetl(p->peeroutreports);
                ppp->pout.packets = nhgetl(p->peeroutpackets);
                ppp->pout.uchars = nhgetl(p->peeroutuchars);
                ppp->pin.reports = nhgetl(p->peerinreports);
                ppp->pin.packets = nhgetl(p->peerinpackets);
                ppp->pin.discards = nhgetl(p->peerindiscards);
                ppp->pin.errors = nhgetl(p->peerinerrors);
                ppp->pin.uchars = nhgetl(p->peerinuchars);

                /* save our numbers at time of reception */
                memmove(&ppp->sin, &ppp->in, sizeof(Qualstats));

        }
        freeb(b);
        if(ppp->period == 0)
                putlqm(ppp);

}

static void
putlqm(PPP *ppp)
{
        Qualpkt *p;
        Block *b;

        b = allocb(sizeof(Qualpkt));
        b->wptr += sizeof(Qualpkt);
        p = (Qualpkt*)b->rptr;
        hnputl(p->magic, 0);

        /* heresay (what he last told us) */
        hnputl(p->lastoutreports, ppp->pout.reports);
        hnputl(p->lastoutpackets, ppp->pout.packets);
        hnputl(p->lastoutuchars, ppp->pout.uchars);

        /* our numbers at time of last reception */
        hnputl(p->peerinreports, ppp->sin.reports);
        hnputl(p->peerinpackets, ppp->sin.packets);
        hnputl(p->peerindiscards, ppp->sin.discards);
        hnputl(p->peerinerrors, ppp->sin.errors);
        hnputl(p->peerinuchars, ppp->sin.uchars);

        /* our numbers now */
        hnputl(p->peeroutreports, ppp->out.reports+1);
        hnputl(p->peeroutpackets, ppp->out.packets+1);
        hnputl(p->peeroutuchars, ppp->out.uchars+53/*hack*/);

        putframe(ppp, Plqm, b);
        freeb(b);
        ppp->out.reports++;
}

/*
 * init challenge response dialog
 */
static void
chapinit(PPP *ppp)
{
        Block *b;
        Lcpmsg *m;
        Chap *c;
        int len;
        char *aproto;

        getauth(ppp);

        c = ppp->chap;
        c->id++;

        switch(c->proto){
        default:
                abort();
        case APmd5:
                aproto = "chap";
                break;
        case APmschap:
                aproto = "mschap";
                break;
        }
        if((c->cs = auth_challenge("proto=%q role=server", aproto)) == nil)
                sysfatal("auth_challenge: %r");
        syslog(0, LOG, ": remote=%I: sending %d byte challenge", ppp->remote, c->cs->nchal);
        len = 4 + 1 + c->cs->nchal + strlen(ppp->chapname);
        b = alloclcp(Cchallenge, c->id, len, &m);

        *b->wptr++ = c->cs->nchal;
        memmove(b->wptr, c->cs->chal, c->cs->nchal);
        b->wptr += c->cs->nchal;
        memmove(b->wptr, ppp->chapname, strlen(ppp->chapname));
        b->wptr += strlen(ppp->chapname);
        hnputs(m->len, len);
        putframe(ppp, Pchap, b);
        freeb(b);

        c->state = Cchalsent;
}

/*
 * BUG factotum should do this
 */
enum {
        MShashlen = 16,
        MSresplen = 24,
        MSchallen = 8,
};

void
desencrypt(uchar data[8], uchar key[7])
{
        ulong ekey[32];

        key_setup(key, ekey);
        block_cipher(ekey, data, 0);
}

void
nthash(uchar hash[MShashlen], char *passwd)
{
        uchar buf[512];
        int i;
        
        for(i=0; *passwd && i<sizeof(buf); passwd++) {
                buf[i++] = *passwd;
                buf[i++] = 0;
        }
        memset(hash, 0, 16);
        md4(buf, i, hash, 0);
}

void
mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen])
{
        int i;
        uchar buf[21];
        
        memset(buf, 0, sizeof(buf));
        memcpy(buf, hash, MShashlen);

        for(i=0; i<3; i++) {
                memmove(resp+i*MSchallen, chal, MSchallen);
                desencrypt(resp+i*MSchallen, buf+i*7);
        }
}

/*
 *  challenge response dialog
 */
extern  int     _asrdresp(int, uchar*, int);

static void
getchap(PPP *ppp, Block *b)
{
        AuthInfo *ai;
        Lcpmsg *m;
        int len, vlen, i, id, n, nresp;
        char md5buf[512], code;
        Chap *c;
        Chapreply cr;
        MSchapreply mscr;
        char uid[PATH];
        uchar digest[16], *p, *resp, sdigest[SHA1dlen];
        uchar mshash[MShashlen], mshash2[MShashlen];
        DigestState *s;
        uchar msresp[2*MSresplen+1];

        m = (Lcpmsg*)b->rptr;
        len = nhgets(m->len);
        if(BLEN(b) < len){
                syslog(0, LOG, "short chap message");
                freeb(b);
                return;
        }

        qlock(ppp);

        switch(m->code){
        case Cchallenge:
                getauth(ppp);

                vlen = m->data[0];
                if(vlen > len - 5) {
                        netlog("PPP: chap: bad challenge len\n");
                        break;
                }

                id = m->id;
                switch(ppp->chap->proto){
                default:
                        abort();
                case APmd5:
                        md5buf[0] = m->id;
                        strcpy(md5buf+1, ppp->secret);
                        n = strlen(ppp->secret) + 1;
                        memmove(md5buf+n, m->data+1, vlen);
                        n += vlen;
                        md5((uchar*)md5buf, n, digest, nil);
                        resp = digest;
                        nresp = 16;
                        break;
                case APmschap:
                        nthash(mshash, ppp->secret);
                        memset(msresp, 0, sizeof msresp);
                        mschalresp(msresp+MSresplen, mshash, m->data+1);
                        resp = msresp;
                        nresp = sizeof msresp;
                        nthash(mshash, ppp->secret);
                        md4(mshash, 16, mshash2, 0);
                        s = sha1(mshash2, 16, 0, 0);
                        sha1(mshash2, 16, 0, s);
                        sha1(m->data+1, 8, sdigest, s);
                        memmove(ppp->key, sdigest, 16);
                        break;
                }
                len = 4 + 1 + nresp + strlen(ppp->chapname);
                freeb(b);
                b = alloclcp(Cresponse, id, len, &m);
                *b->wptr++ = nresp;
                memmove(b->wptr, resp, nresp);
                b->wptr += nresp;
                memmove(b->wptr, ppp->chapname, strlen(ppp->chapname));
                b->wptr += strlen(ppp->chapname);
                hnputs(m->len, len);
                netlog("PPP: sending response len %d\n", len);
                putframe(ppp, Pchap, b);
                break;
        case Cresponse:
                c = ppp->chap;
                vlen = m->data[0];
                if(m->id != c->id) {
                        netlog("PPP: chap: bad response id\n");
                        break;
                }
                switch(c->proto) {
                default:
                        sysfatal("unknown chap protocol: %d", c->proto);
                case APmd5:
                        if(vlen > len - 5 || vlen != 16) {
                                netlog("PPP: chap: bad response len\n");
                                break;
                        }

                        cr.id = m->id;
                        memmove(cr.resp, m->data+1, 16);
                        memset(uid, 0, sizeof(uid));
                        n = len-5-vlen;
                        if(n >= PATH)
                                n = PATH-1;
                        memmove(uid, m->data+1+vlen, n);
                        c->cs->user = uid;
                        c->cs->resp = &cr;
                        c->cs->nresp = sizeof cr;
                        break;
                case APmschap:
                        if(vlen > len - 5 || vlen != 49) {
                                netlog("PPP: chap: bad response len\n");
                                break;
                        }
                        memset(&mscr, 0, sizeof(mscr));
                        memmove(mscr.LMresp, m->data+1, 24);
                        memmove(mscr.NTresp, m->data+24+1, 24);
                        n = len-5-vlen;
                        p = m->data+1+vlen;
                        /* remove domain name */
                        for(i=0; i<n; i++) {
                                if(p[i] == '\\') {
                                        p += i+1;
                                        n -= i+1;
                                        break;
                                }
                        }
                        if(n >= PATH)
                                n = PATH-1;
                        memset(uid, 0, sizeof(uid));
                        memmove(uid, p, n);
                        c->cs->user = uid;
                        c->cs->resp = &mscr;
                        c->cs->nresp = sizeof mscr;
                        break;
                } 

                syslog(0, LOG, ": remote=%I vlen %d proto %d response user %s nresp %d", ppp->remote, vlen, c->proto, c->cs->user, c->cs->nresp);
                if((ai = auth_response(c->cs)) == nil || auth_chuid(ai, nil) < 0){
                        c->state = Cunauth;
                        code = Cfailure;
                        syslog(0, LOG, ": remote=%I: auth failed: %r, uid=%s", ppp->remote, uid);
                }else{
                        c->state = Cauthok;
                        code = Csuccess;
                        syslog(0, LOG, ": remote=%I: auth ok: uid=%s nsecret=%d", ppp->remote, uid, ai->nsecret);
                        if(c->proto == APmschap){
                                if(ai->nsecret != sizeof(ppp->key))
                                        sysfatal("could not get the encryption key");
                                memmove(ppp->key, ai->secret, sizeof(ppp->key));
                        }
                }
                auth_freeAI(ai);
                auth_freechal(c->cs);
                c->cs = nil;
                freeb(b);

                /* send reply */
                len = 4;
                b = alloclcp(code, c->id, len, &m);
                hnputs(m->len, len);
                putframe(ppp, Pchap, b);

                if(c->state == Cauthok) {
                        setphase(ppp, Pnet);
                } else {
                        /* restart chapp negotiation */
                        chapinit(ppp);
                }
                
                break;
        case Csuccess:
                netlog("ppp: chap succeeded\n");
                break;
        case Cfailure:
                netlog("ppp: chap failed\n");
                break;
        default:
                syslog(0, LOG, "chap code %d?\n", m->code);
                break;
        }
        qunlock(ppp);
        freeb(b);
}

static void
putpaprequest(PPP *ppp)
{
        Block *b;
        Lcpmsg *m;
        Chap *c;
        int len, nlen, slen;

        getauth(ppp);

        c = ppp->chap;
        c->id++;
        netlog("PPP: pap: send authreq %d %s %s\n", c->id, ppp->chapname, "****");

        nlen = strlen(ppp->chapname);
        slen = strlen(ppp->secret);
        len = 4 + 1 + nlen + 1 + slen;
        b = alloclcp(Pauthreq, c->id, len, &m);

        *b->wptr++ = nlen;
        memmove(b->wptr, ppp->chapname, nlen);
        b->wptr += nlen;
        *b->wptr++ = slen;
        memmove(b->wptr, ppp->secret, slen);
        b->wptr += slen;
        hnputs(m->len, len);

        putframe(ppp, Ppasswd, b);
        freeb(b);
}

static void
papinit(PPP *ppp)
{
        ppp->chap->id = 0;
        putpaprequest(ppp);
}

static void
getpap(PPP *ppp, Block *b)
{
        Lcpmsg *m;
        int len;

        m = (Lcpmsg*)b->rptr;
        len = 4;
        if(BLEN(b) < 4 || BLEN(b) < (len = nhgets(m->len))){
                syslog(0, LOG, "short pap message (%ld < %d)", BLEN(b), len);
                freeb(b);
                return;
        }
        if(len < sizeof(Lcpmsg))
                m->data[0] = 0;

        qlock(ppp);
        switch(m->code){
        case Pauthreq:
                netlog("PPP: pap auth request, not supported\n");
                break;
        case Pauthack:
                if(ppp->phase == Pauth
                && ppp->chap->proto == APpasswd
                && m->id <= ppp-> chap->id){
                        netlog("PPP: pap succeeded\n");
                        setphase(ppp, Pnet);
                }
                break;
        case Pauthnak:
                if(ppp->phase == Pauth
                && ppp->chap->proto == APpasswd
                && m->id <= ppp-> chap->id){
                        netlog("PPP: pap failed (%d:%.*s)\n",
                                m->data[0], m->data[0], (char*)m->data+1);
                        terminate(ppp, 0);
                }
                break;
        default:
                netlog("PPP: unknown pap messsage %d\n", m->code);
        }
        qunlock(ppp);
        freeb(b);
}

static void
printopts(Pstate *p, Block *b, int send)
{
        Lcpmsg *m;      
        Lcpopt *o;
        int proto, x, period;
        uchar *cp;
        char *code, *dir;

        m = (Lcpmsg*)b->rptr;
        switch(m->code) {
        default: code = "<unknown>"; break;
        case Lconfreq: code = "confrequest"; break;
        case Lconfack: code = "confack"; break;
        case Lconfnak: code = "confnak"; break;
        case Lconfrej: code = "confreject"; break;
        }

        if(send)
                dir = "send";
        else
                dir = "recv";

        netlog("ppp: %s %s: id=%d\n", dir, code, m->id);

        for(cp = m->data; cp < b->wptr; cp += o->len){
                o = (Lcpopt*)cp;
                if(cp + o->len > b->wptr){
                        netlog("\tbad option length %ux\n", o->type);
                        return;
                }

                switch(p->proto){
                case Plcp:
                        switch(o->type){
                        default:
                                netlog("\tunknown %d len=%d\n", o->type, o->len);
                                break;
                        case Omtu:
                                netlog("\tmtu = %d\n", nhgets(o->data));
                                break;
                        case Octlmap:
                                netlog("\tctlmap = %ux\n", nhgetl(o->data));
                                break;
                        case Oauth:
                                netlog("\tauth = %ux", nhgetl(o->data));
                                proto = nhgets(o->data);
                                switch(proto) {
                                default:
                                        netlog("unknown auth proto %d\n", proto);
                                        break;
                                case Ppasswd:
                                        netlog("password\n");
                                        break;
                                case Pchap:
                                        netlog("chap %ux\n", o->data[2]);
                                        break;
                                }
                                break;
                        case Oquality:
                                proto = nhgets(o->data);
                                switch(proto) {
                                default:
                                        netlog("\tunknown quality proto %d\n", proto);
                                        break;
                                case Plqm:
                                        x = nhgetl(o->data+2)*10;
                                        period = (x+Period-1)/Period;
                                        netlog("\tlqm period = %d\n", period);
                                        break;
                                }
                        case Omagic:
                                netlog("\tmagic = %ux\n", nhgetl(o->data));
                                break;
                        case Opc:
                                netlog("\tprotocol compress\n");
                                break;
                        case Oac:
                                netlog("\taddr compress\n");
                                break;
                        }
                        break;
                case Pccp:
                        switch(o->type){
                        default:
                                netlog("\tunknown %d len=%d\n", o->type, o->len);
                                break;
                        case Ocoui:     
                                netlog("\tOUI\n");
                                break;
                        case Ocstac:
                                netlog("\tstac LZS\n");
                                break;
                        case Ocmppc:    
                                netlog("\tMicrosoft PPC len=%d %ux\n", o->len, nhgetl(o->data));
                                break;
                        case Octhwack:  
                                netlog("\tThwack\n");
                                break;
                        }
                        break;
                case Pecp:
                        switch(o->type){
                        default:
                                netlog("\tunknown %d len=%d\n", o->type, o->len);
                                break;
                        case Oeoui:     
                                netlog("\tOUI\n");
                                break;
                        case Oedese:
                                netlog("\tDES\n");
                                break;
                        }
                        break;
                case Pipcp:
                        switch(o->type){
                        default:
                                netlog("\tunknown %d len=%d\n", o->type, o->len);
                                break;
                        case Oipaddrs:  
                                netlog("\tip addrs - deprecated\n");
                                break;
                        case Oipcompress:
                                netlog("\tip compress\n");
                                break;
                        case Oipaddr:   
                                netlog("\tip addr %V\n", o->data);
                                break;
                        case Oipdns:    
                                netlog("\tdns addr %V\n", o->data);
                                break;
                        case Oipwins:   
                                netlog("\twins addr %V\n", o->data);
                                break;
                        case Oipdns2:   
                                netlog("\tdns2 addr %V\n", o->data);
                                break;
                        case Oipwins2:  
                                netlog("\twins2 addr %V\n", o->data);
                                break;
                        }
                        break;
                }
        }
}

static void
sendtermreq(PPP *ppp, Pstate *p)
{
        Block *b;
        Lcpmsg *m;

        p->termid = ++(p->id);
        b = alloclcp(Ltermreq, p->termid, 4, &m);
        hnputs(m->len, 4);
        putframe(ppp, p->proto, b);
        freeb(b);
        newstate(ppp, p, Sclosing);
}

static void
sendechoreq(PPP *ppp, Pstate *p)
{
        Block *b;
        Lcpmsg *m;

        p->termid = ++(p->id);
        b = alloclcp(Lechoreq, p->id, 4, &m);
        hnputs(m->len, 4);
        putframe(ppp, p->proto, b);
        freeb(b);
}

enum
{
        CtrlD   = 0x4,
        CtrlE   = 0x5,
        CtrlO   = 0xf,
        Cr      = 13,
        View    = 0x80,
};

int conndone;

static void
xfer(int fd)
{
        int i, n;
        uchar xbuf[128];

        for(;;) {
                n = read(fd, xbuf, sizeof(xbuf));
                if(n < 0)
                        break;
                if(conndone)
                        break;
                for(i = 0; i < n; i++)
                        if(xbuf[i] == Cr)
                                xbuf[i] = ' ';
                write(1, xbuf, n);
        }
        close(fd);
}

static int
readcr(int fd, char *buf, int nbuf)
{
        char c;
        int n, tot;

        tot = 0;
        while((n=read(fd, &c, 1)) == 1){
                if(c == '\n'){
                        buf[tot] = 0;
                        return tot;
                }
                buf[tot++] = c;
                if(tot == nbuf)
                        sysfatal("line too long in readcr");
        }
        return n;
}

static void
connect(int fd, int cfd)
{
        int n, ctl;
        char xbuf[128];

        if (chatfile) {
                int chatfd, lineno, nb;
                char *buf, *p, *s, response[128];
                Dir *dir;

                if ((chatfd = open(chatfile, OREAD)) < 0)
                        sysfatal("cannot open %s: %r", chatfile);

                if ((dir = dirfstat(chatfd)) == nil)
                        sysfatal("cannot fstat %s: %r",chatfile);

                buf = (char *)malloc(dir->length + 1);
                assert(buf);

                if ((nb = read(chatfd, buf, dir->length)) < 0)
                        sysfatal("cannot read chatfile %s: %r", chatfile);
                assert(nb == dir->length);
                buf[dir->length] = '\0';
                free(dir);
                close(chatfd);

                p = buf;
                lineno = 0;
                for(;;) {
                        char *_args[3];

                        if ((s = strchr(p, '\n')) == nil)
                                break;
                        *s++ = '\0';
                
                        lineno++;

                        if (*p == '#') {
                                p = s; 
                                continue;
                        }

                        if (tokenize(p, _args, 3) != 2)
                                sysfatal("invalid line %d (line expected: 'send' 'expect')", 
                                                lineno);

                        if (debug)
                                print("sending %s, expecting %s\n", _args[0], _args[1]);

                        if(strlen(_args[0])){
                                nb = fprint(fd, "%s\r", _args[0]);
                                assert(nb > 0);
                        }

                        if (strlen(_args[1]) > 0) {
                                if ((nb = readcr(fd, response, sizeof response-1)) < 0)
                                        sysfatal("cannot read response from: %r");

                                if (debug)
                                        print("response %s\n", response);

                                if (nb == 0)
                                        sysfatal("eof on input?");

                                if (cistrstr(response, _args[1]) == nil)
                                        sysfatal("expected %s, got %s", _args[1], response);
                        }
                        p = s;
                }
                free(buf);
                return;
        }

        print("Connect to file system now, type ctrl-d when done.\n");
        print("...(Use the view or down arrow key to send a break)\n");
        print("...(Use ctrl-e to set even parity or ctrl-o for odd)\n");

        ctl = open("/dev/consctl", OWRITE);
        if(ctl < 0)
                sysfatal("opening consctl");
        fprint(ctl, "rawon");

        fd = dup(fd, -1);
        conndone = 0;
        switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
        case -1:
                sysfatal("forking xfer");
        case 0:
                xfer(fd);
                _exits(nil);
        }

        for(;;){
                read(0, xbuf, 1);
                switch(xbuf[0]&0xff) {
                case CtrlD:     /* done */
                        conndone = 1;
                        close(ctl);
                        print("\n");
                        return;
                case CtrlE:     /* set even parity */
                        fprint(cfd, "pe");
                        break;
                case CtrlO:     /* set odd parity */
                        fprint(cfd, "po");
                        break;
                case View:      /* send a break */
                        fprint(cfd, "k500");
                        break;
                default:
                        n = write(fd, xbuf, 1);
                        if(n < 0) {
                                errstr(xbuf, sizeof(xbuf));
                                conndone = 1;
                                close(ctl);
                                print("[remote write error (%s)]\n", xbuf);
                                return;
                        }
                }
        }
}

int interactive;

void
usage(void)
{
        fprint(2, "usage: ppp [-CPSacdfu] [-b baud] [-k keyspec] [-m mtu] "
                "[-M chatfile] [-p dev] [-x netmntpt] [-t modemcmd] "
                "[local-addr [remote-addr]]\n");
        exits("usage");
}

void
main(int argc, char **argv)
{
        int mtu, baud, framing, user, mediain, mediaout, cfd;
        Ipaddr ipaddr, remip;
        char *dev, *modemcmd;
        char net[128];
        PPP *ppp;
        char buf[128];

        rfork(RFREND|RFNOTEG|RFNAMEG);

        fmtinstall('I', eipfmt);
        fmtinstall('V', eipfmt);
        fmtinstall('E', eipfmt);

        dev = nil;

        invalidate(ipaddr);
        invalidate(remip);

        mtu = Defmtu;
        baud = 0;
        framing = 0;
        setnetmtpt(net, sizeof(net), nil);
        user = 0;
        modemcmd = nil;

        ARGBEGIN{
        case 'a':
                noauth = 1;
                break;
        case 'b':
                baud = atoi(EARGF(usage()));
                if(baud < 0)
                        baud = 0;
                break;
        case 'c':
                nocompress = 1;
                break;
        case 'C':
                noipcompress = 1;
                break;
        case 'd':
                debug++;
                break;
        case 'f':
                framing = 1;
                break;
        case 'F':
                pppframing = 0;
                break;
        case 'k':
                keyspec = EARGF(usage());
                break;
        case 'm':
                mtu = atoi(EARGF(usage()));
                if(mtu < Minmtu)
                        mtu = Minmtu;
                if(mtu > Maxmtu)
                        mtu = Maxmtu;
                break;
        case 'M':
                chatfile = EARGF(usage());
                break;
        case 'p':
                dev = EARGF(usage());
                break;
        case 'P':
                primary = 1;
                break;
        case 'S':
                server = 1;
                break;
        case 't':
                modemcmd = EARGF(usage());
                break;
        case 'u':
                user = 1;
                break;
        case 'x':
                setnetmtpt(net, sizeof net, EARGF(usage()));
                break;
        default:
                fprint(2, "unknown option %c\n", ARGC());
                usage();
        }ARGEND;

        switch(argc){
        case 2:
                if (parseip(remip, argv[1]) == -1)
                        sysfatal("bad remote ip %s", argv[1]);
        case 1:
                if (parseip(ipaddr, argv[0]) == -1)
                        sysfatal("bad ip %s", argv[0]);
        case 0:
                break;
        default:
                usage();
        }

        nip = nipifcs(net);
        if(nip == 0 && !server)
                primary = 1;

        if(dev != nil){
                mediain = open(dev, ORDWR);
                if(mediain < 0){
                        if(strchr(dev, '!')){
                                if((mediain = dial(dev, 0, 0, &cfd)) == -1){
                                        fprint(2, "ppp: couldn't dial %s: %r\n", dev);
                                        exits(dev);
                                }
                        } else {
                                fprint(2, "ppp: couldn't open %s\n", dev);
                                exits(dev);
                        }
                } else {
                        snprint(buf, sizeof buf, "%sctl", dev);
                        cfd = open(buf, ORDWR);
                }
                if(cfd > 0){
                        if(baud)
                                fprint(cfd, "b%d", baud);
                        fprint(cfd, "m1");      /* cts/rts flow control (and fifo's) on */
                        fprint(cfd, "q64000");  /* increase q size to 64k */
                        fprint(cfd, "n1");      /* nonblocking writes on */
                        fprint(cfd, "r1");      /* rts on */
                        fprint(cfd, "d1");      /* dtr on */
                        fprint(cfd, "c1");      /* dcdhup on */
                        if(user || chatfile)
                                connect(mediain, cfd);
                        close(cfd);
                } else {
                        if(user || chatfile)
                                connect(mediain, -1);
                }
                mediaout = mediain;
        } else {
                mediain = open("/fd/0", OREAD);
                if(mediain < 0){
                        fprint(2, "ppp: couldn't open /fd/0\n");
                        exits("/fd/0");
                }
                mediaout = open("/fd/1", OWRITE);
                if(mediaout < 0){
                        fprint(2, "ppp: couldn't open /fd/0\n");
                        exits("/fd/1");
                }
        }

        if(modemcmd != nil && mediaout >= 0)
                fprint(mediaout, "%s\r", modemcmd);

        ppp = mallocz(sizeof(*ppp), 1);
        pppopen(ppp, mediain, mediaout, net, ipaddr, remip, mtu, framing);

        /* wait until ip is configured */
        rendezvous((void*)Rmagic, 0);

        if(primary){
                /* create a /net/ndb entry */
                putndb(ppp, net);
        }

        exits(0);
}

void
netlog(char *fmt, ...)
{
        va_list arg;
        char *m;
        static long start;
        long now;

        if(debug == 0)
                return;

        now = time(0);
        if(start == 0)
                start = now;

        va_start(arg, fmt);
        m = vsmprint(fmt, arg);
        fprint(2, "%ld %s", now-start, m);
        free(m);
        va_end(arg);
}

/*
 *  return non-zero if this is a valid v4 address
 */
static int
validv4(Ipaddr addr)
{
        return memcmp(addr, v4prefix, IPv4off) == 0 && memcmp(addr, v4prefix, IPaddrlen) != 0;
}

static void
invalidate(Ipaddr addr)
{
        ipmove(addr, IPnoaddr);
}

/*
 *  return number of networks
 */
static int
nipifcs(char *net)
{
        static Ipifc *ifc;
        Ipifc *nifc;
        Iplifc *lifc;
        int n;

        n = 0;
        ifc = readipifc(net, ifc, -1);
        for(nifc = ifc; nifc != nil; nifc = nifc->next)
                for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next)
                        n++;
        return n;
}

/*
 *   make an ndb entry and put it into /net/ndb for the servers to see
 */
static void
putndb(PPP *ppp, char *net)
{
        char buf[1024];
        char file[64];
        char *p, *e;
        int fd;

        e = buf + sizeof(buf);
        p = buf;
        p = seprint(p, e, "ip=%I ipmask=255.255.255.255 ipgw=%I\n", ppp->local,
                        ppp->remote);
        if(validv4(ppp->dns[0]))
                p = seprint(p, e, "\tdns=%I\n", ppp->dns[0]);
        if(validv4(ppp->dns[1]))
                p = seprint(p, e, "\tdns=%I\n", ppp->dns[1]);
        if(validv4(ppp->wins[0]))
                p = seprint(p, e, "\twins=%I\n", ppp->wins[0]);
        if(validv4(ppp->wins[1]))
                p = seprint(p, e, "\twins=%I\n", ppp->wins[1]);
        seprint(file, file+sizeof file, "%s/ndb", net);
        fd = open(file, OWRITE);
        if(fd < 0)
                return;
        write(fd, buf, p-buf);
        close(fd);
        seprint(file, file+sizeof file, "%s/cs", net);
        fd = open(file, OWRITE);
        write(fd, "refresh", 7);
        close(fd);
        seprint(file, file+sizeof file, "%s/dns", net);
        fd = open(file, OWRITE);
        write(fd, "refresh", 7);
        close(fd);
}

static void
getauth(PPP *ppp)
{
        UserPasswd *up;

        if(*ppp->chapname)
                return;

        up = auth_getuserpasswd(auth_getkey,"proto=pass service=ppp %s", keyspec);
        if(up != nil){
                strcpy(ppp->chapname, up->user);
                strcpy(ppp->secret, up->passwd);
        }               
}