ref: 17b80cbcf1d5a7c6afd74c7432ab64c100dbdc2f
dir: /sys/src/cmd/ip/ppp/ppp.c/
/* * 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); 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 = APmschapv2; 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 { switch (ppp->chap->proto) { case APpasswd: papinit(ppp); break; case APmd5: case APmschap: case APmschapv2: break; default: setphase(ppp, Pnet); break; } } 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; if(noipcompress) { p->optmask &= ~Fac; ppp->ipcp->optmask &= ~Fipaddrs; } if(nocompress) { p->optmask &= ~Fpc; ppp->ipcp->optmask &= ~Fipcompress; } 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 %zd/%zd 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 %zd\n", proto, BLEN(b)); /* 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); } primary = 1; 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 && o->data[2] != APmschapv2) break; chapproto = o->data[2]; continue; } break; case Pccp: 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); /* 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 & 0x01000001) == 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", 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 defroute(char *net, char *verb, Ipaddr gate, Ipaddr local) { int fd; char path[128]; snprint(path, sizeof path, "%s/iproute", net); fd = open(path, ORDWR); if(fd < 0) return; fprint(fd, "tag ppp"); if(primary) fprint(fd, "%s 0.0.0.0 0.0.0.0 %I", verb, gate); fprint(fd, "%s 0.0.0.0 0.0.0.0 %I %I 255.255.255.255", verb, gate, local); close(fd); } 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(baud) fprint(cfd, "speed %d", baud); defroute(ppp->net, "add", ppp->remote, ppp->local); 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){ defroute(ppp->net, "remove", ppp->curremote, ppp->curlocal); 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); defroute(ppp->net, "add", ppp->remote, ppp->local); } 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++; } static char* getaproto(int proto) { switch(proto){ case APmd5: return "chap"; case APmschap: return "mschap"; case APmschapv2: return "mschapv2"; } return nil; } /* * init challenge response dialog */ static void chapinit(PPP *ppp) { Block *b; Lcpmsg *m; Chap *c; int len; c = ppp->chap; c->id++; if(c->ai != nil){ auth_freeAI(c->ai); c->ai = nil; } if((c->cs = auth_challenge("proto=%q role=server", getaproto(c->proto))) == 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; } /* * challenge response dialog */ static void setppekey(PPP *ppp, int isserver) { Chap *c = ppp->chap; switch(c->proto){ case APmschap: if(c->ai == nil || c->ai->nsecret != 16) sysfatal("could not get the encryption key"); memmove(ppp->sendkey, c->ai->secret, 16); memmove(ppp->recvkey, c->ai->secret, 16); break; case APmschapv2: if(c->ai == nil || c->ai->nsecret != 16+20) sysfatal("could not get the encryption key + authenticator"); getasymkey(ppp->sendkey, c->ai->secret, 1, isserver); getasymkey(ppp->recvkey, c->ai->secret, 0, isserver); break; } auth_freeAI(c->ai); c->ai = nil; } static void getchap(PPP *ppp, Block *b) { Lcpmsg *m; int len, vlen, i, id, n, nresp; char code; Chap *c; Chapreply cr; MSchapreply mscr; char uid[PATH]; uchar resp[256], *p; m = (Lcpmsg*)b->rptr; len = nhgets(m->len); if(BLEN(b) < len){ syslog(0, LOG, "short chap message"); freeb(b); return; } qlock(ppp); c = ppp->chap; vlen = m->data[0]; switch(m->code){ case Cchallenge: id = m->id; memset(ppp->chapname, 0, sizeof(ppp->chapname)); nresp = auth_respondAI(m->data+1, vlen, ppp->chapname, sizeof(ppp->chapname), resp, sizeof(resp), &c->ai, auth_getkey, "proto=%s role=client service=ppp %s", getaproto(c->proto), keyspec); if(nresp < 0) sysfatal("auth_respond: %r"); if(c->proto == APmschap || c->proto == APmschapv2) while(nresp < 49) resp[nresp++] = 0; freeb(b); len = 4 + 1 + nresp + strlen(ppp->chapname); 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: if(m->id != c->id || c->cs == nil) { 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: case APmschapv2: if(vlen > len - 5 || vlen < 48) { 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 = 𝓂 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((c->ai = auth_response(c->cs)) == nil || auth_chuid(c->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, c->ai->nsecret); } auth_freechal(c->cs); c->cs = nil; freeb(b); /* send reply */ if(code == Csuccess && c->proto == APmschapv2 && c->ai->nsecret == 16+20){ b = alloclcp(code, c->id, 4+2+2*20+1, &m); b->wptr += sprint((char*)m->data, "S=%.20H", c->ai->secret+16); } else { b = alloclcp(code, c->id, 4, &m); } hnputs(m->len, BLEN(b)); putframe(ppp, Pchap, b); if(c->state == Cauthok){ setppekey(ppp, 1); setphase(ppp, Pnet); } else { /* restart chapp negotiation */ chapinit(ppp); } break; case Csuccess: if(c->proto == APmschapv2 && c->ai != nil && c->ai->nsecret == 16+20){ n = snprint((char*)resp, sizeof(resp), "S=%.20H", c->ai->secret+16); if(len - 4 < n || tsmemcmp(m->data, resp, n) != 0){ netlog("ppp: chap: bad authenticator\n"); terminate(ppp, 0); break; } } netlog("ppp: chap succeeded\n"); setppekey(ppp, 0); setphase(ppp, Pnet); break; case Cfailure: netlog("ppp: chap failed\n"); terminate(ppp, 0); break; default: syslog(0, LOG, "chap code %d?", m->code); break; } qunlock(ppp); freeb(b); } static void putpaprequest(PPP *ppp) { Block *b; Lcpmsg *m; Chap *c; UserPasswd *up; int len, nlen, slen; up = auth_getuserpasswd(auth_getkey, "proto=pass service=ppp %s", keyspec); if(up == nil) sysfatal("auth_getuserpasswd: %r"); c = ppp->chap; c->id++; netlog("ppp: pap: send authreq %d %s %s\n", c->id, up->user, "****"); nlen = strlen(up->user); slen = strlen(up->passwd); len = 4 + 1 + nlen + 1 + slen; b = alloclcp(Pauthreq, c->id, len, &m); *b->wptr++ = nlen; memmove(b->wptr, up->user, nlen); b->wptr += nlen; *b->wptr++ = slen; memmove(b->wptr, up->passwd, slen); b->wptr += slen; hnputs(m->len, len); free(up); 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 (%zd < %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, 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); fmtinstall('H', encodefmt); dev = nil; invalidate(ipaddr); invalidate(remip); mtu = Defmtu; 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); /* create a /net/ndb entry */ if(primary) 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) { static char buf[16*1024]; char file[64], *p, *e; Ndbtuple *t, *nt; Ndb *db; 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]); /* append preexisting entries not matching our ip */ snprint(file, sizeof file, "%s/ndb", net); db = ndbopen(file); if(db != nil ){ while((t = ndbparse(db)) != nil){ uchar ip[IPaddrlen]; if((nt = ndbfindattr(t, t, "ip")) == nil || parseip(ip, nt->val) == -1 || ipcmp(ip, ppp->local) != 0){ p = seprint(p, e, "\n"); for(nt = t; nt != nil; nt = nt->entry) p = seprint(p, e, "%s=%s%s", nt->attr, nt->val, nt->entry==nil? "\n": nt->line!=nt->entry? "\n\t": " "); } ndbfree(t); } ndbclose(db); } if((fd = open(file, OWRITE|OTRUNC)) < 0) return; write(fd, buf, p-buf); close(fd); snprint(file, sizeof file, "%s/cs", net); if((fd = open(file, OWRITE)) >= 0){ write(fd, "refresh", 7); close(fd); } snprint(file, sizeof file, "%s/dns", net); if((fd = open(file, OWRITE)) >= 0){ write(fd, "refresh", 7); close(fd); } }