diff options
Diffstat (limited to 'appl/cmd/ip/nppp/ppplink.b')
| -rw-r--r-- | appl/cmd/ip/nppp/ppplink.b | 782 |
1 files changed, 782 insertions, 0 deletions
diff --git a/appl/cmd/ip/nppp/ppplink.b b/appl/cmd/ip/nppp/ppplink.b new file mode 100644 index 00000000..5f0e9686 --- /dev/null +++ b/appl/cmd/ip/nppp/ppplink.b @@ -0,0 +1,782 @@ +implement PPPlink; + +# +# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +include "cfgfile.m"; + cfg: CfgFile; + ConfigFile: import cfg; + +include "lock.m"; +include "modem.m"; +include "script.m"; + +include "sh.m"; + +include "translate.m"; + translate: Translate; + Dict: import translate; + dict: ref Dict; + +PPPlink: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +PPPInfo: adt { + ipaddr: string; + ipmask: string; + peeraddr: string; + maxmtu: string; + username: string; + password: string; +}; + +modeminfo: ref Modem->ModemInfo; +context: ref Draw->Context; +pppinfo: ref PPPInfo; +scriptinfo: ref Script->ScriptInfo; +isp_number: string; +lastCdir: ref Sys->Dir; # state of file when last read +netdir := "/net"; + +Packet: adt { + src: array of byte; + dst: array of byte; + data: array of byte; +}; + +DEFAULT_ISP_DB_PATH: con "/services/ppp/isp.cfg"; # contains pppinfo & scriptinfo +DEFAULT_MODEM_DB_PATH: con "/services/ppp/modem.cfg"; # contains modeminfo +MODEM_DB_PATH: con "modem.cfg"; # contains modeminfo +ISP_DB_PATH: con "isp.cfg"; # contains pppinfo & scriptinfo + +primary := 0; +framing := 1; + +Disconnected, Modeminit, Dialling, Modemup, Scriptstart, Scriptdone, Startingppp, Startedppp, Login, Linkup: con iota; +Error: con -1; + +Ignorems: con 10*1000; # time to ignore outgoing packets between dial attempts + +statustext := array[] of { +Disconnected => "Disconnected", +Modeminit => "Initializing Modem", +Dialling => "Dialling Service Provider", +Modemup => "Logging Into Network", +Scriptstart => "Executing Login Script", +Scriptdone => "Script Execution Complete", +Startingppp => "Logging Into Network", +Startedppp => "Logging Into Network", +Login => "Verifying Password", +Linkup => "Connected", +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: ppplink [-P] [-f] [-m mtu] [local [remote]]\n"); + raise "fail:usage"; +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + translate = load Translate Translate->PATH; + if(translate != nil) { + translate->init(); + dictname := translate->mkdictname("", "pppclient"); + (dict, nil) = translate->opendict(dictname); + } + mtu := 1450; + + arg := load Arg Arg->PATH; + if(arg == nil) + error(0, sys->sprint("can't load %s: %r", Arg->PATH)); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'm' => + if((s := arg->arg()) == nil || !(s[0]>='0' && s[0]<='9')) + usage(); + mtu = int s; + 'P' => + primary = 1; + 'f' => + framing = 0; + * => + usage(); + } + args = arg->argv(); + arg = nil; + localip := "10.9.8.7"; # should be something locally unique + fake := 1; + if(args != nil){ + fake = 0; + localip = hd args; + args = tl args; + } + + cerr := configinit(); + if(cerr != nil) + error(0, sys->sprint("can't configure: %s", cerr)); + context = ctxt; + + # make default (for now) + # if packet appears, start ppp and reset routing until it stops + + (cfd, dir, err) := getifc(); + if(err != nil) + error(0, err); + + if(sys->fprint(cfd, "bind pkt") < 0) + error(0, sys->sprint("can't bind pkt: %r")); + if(sys->fprint(cfd, "add %s 255.255.255.0 10.9.8.0 %d", localip, mtu) < 0) + error(0, sys->sprint("can't add ppp addresses: %r")); + if(primary && addroute("0", "0", localip) < 0) + error(0, sys->sprint("can't add default route: %r")); + dfd := sys->open(dir+"/data", Sys->ORDWR); + if(dfd == nil) + error(0, sys->sprint("can't open %s: %r", dir)); + + sys->pctl(Sys->NEWPGRP, nil); + + packets := chan of ref Packet; + spawn netreader(dfd, dir, localip, fake, packets); + + logger := chan of (int, string); + iocmd := sys->file2chan("/chan", "pppctl"); + if(iocmd == nil) + error(0, sys->sprint("can't create /chan/pppctl: %r")); + spawn servestatus(iocmd.read, logger); + + starteduser := 0; + lasttime := 0; + + for(;;) alt{ + (nil, data, nil, wc) := <-iocmd.write => # remote io control + if(wc == nil) + break; + (nil, flds) := sys->tokenize(string data, " \t"); + if(len flds > 1){ + case hd flds { + "cancel" or "disconnect" or "hangup" => + ; # ignore it + "connect" => + # start connection ... + ; + * => + wreply(wc, (0, "illegal request")); + continue; + } + } + wreply(wc, (len data, nil)); + + pkt := <-packets => + sys->print("ppplink: received packet %s->%s: %d bytes\n", ipa(pkt.src), ipa(pkt.dst), len pkt.data); + if(abs(sys->millisec()-lasttime) < Ignorems){ + sys->print("ppplink: ignored, not enough time elapsed yet between dial attempts\n"); + break; + } + (ok, stat) := sys->stat(ISP_DB_PATH); + if(ok < 0 || lastCdir == nil || !samefile(*lastCdir, stat)){ + cerr = configinit(); + if(cerr != nil){ + sys->print("ppplink: can't reconfigure: %s\n", cerr); + # use existing configuration + } + } + if(!starteduser){ + sync := chan of int; + spawn userinterface(sync); + starteduser = <-sync; + } + (ppperr, pppdir) := makeconnection(packets, logger, iocmd.write); + lasttime = sys->millisec(); + if(ppperr == nil){ + sys->print("ppplink: connected on %s\n", pppdir); + # converse ... +sys->sleep(120*1000); + }else{ + sys->print("ppplink: ppp connect error: %s\n", ppperr); + hangup(pppdir); + } + } +} + +servestatus(reader: chan of (int, int, int, Sys->Rread), updates: chan of (int, string)) +{ + statuspending := 0; + statusreq: (int, int, Sys->Rread); + step := Disconnected; + statuslist := statusline(step, step, nil) :: nil; + + for(;;) alt{ + (off, nbytes, fid, rc) := <-reader=> + if(rc == nil){ + statuspending = 0; + if(step == Disconnected) + statuslist = nil; + break; + } + if(statuslist == nil){ + if(statuspending){ + alt{ + rc <-= (nil, "pppctl file already in use") => ; + * => ; + } + break; + } + statusreq = (nbytes, fid, rc); + statuspending = 1; + break; + } + alt{ + rc <-= reads(hd statuslist, 0, nbytes) => + statuslist = tl statuslist; + * => ; + } + + (code, arg) := <-updates => + # convert to string + if(code != Error) + step = code; + status := statusline(step, code, arg); + if(code == Error) + step = Disconnected; + statuslist = appends(statuslist, status); + sys->print("status: %d %d %s\n", step, code, status); + if(statuspending){ + (nbytes, nil, rc) := statusreq; + statuspending = 0; + alt{ + rc <-= reads(hd statuslist, 0, nbytes) => + statuslist = tl statuslist; + * => + ; + } + } + } +} + +makeconnection(packets: chan of ref Packet, logger: chan of (int, string), writer: chan of (int, array of byte, int, Sys->Rwrite)): (string, string) +{ + result := chan of (string, string); + sync := chan of int; + spawn pppconnect(result, sync, logger); + pid := <-sync; + for(;;) alt{ + (err, pppdir) := <-result => + # pppconnect finished + return (err, pppdir); + + pkt := <-packets => + # ignore packets whilst connecting + sys->print("ppplink: ignored packet %s->%s: %d byten", ipa(pkt.src), ipa(pkt.dst), len pkt.data); + + (nil, data, nil, wc) := <-writer => # user control + if(wc == nil) + break; + (nil, flds) := sys->tokenize(string data, " \t"); + if(len flds > 1){ + case hd flds { + "connect" => + ; # ignore it + "cancel" or "disconnect" or "hangup"=> + kill(pid, "killgrp"); + wreply(wc, (len data, nil)); + return ("cancelled", nil); + * => + wreply(wc, (0, "illegal request")); + continue; + } + } + wreply(wc, (len data, nil)); + } +} + +wreply(wc: chan of (int, string), v: (int, string)) +{ + alt{ + wc <-= v => ; + * => ; + } +} + +appends(l: list of string, s: string): list of string +{ + if(l == nil) + return s :: nil; + return hd l :: appends(tl l, s); +} + +statusline(step: int, code: int, arg: string): string +{ + s: string; + if(code >= 0 && code < len statustext){ + n := "step"; + if(code == Linkup) + n = "connect"; + s = sys->sprint("%d %d %s %s", step, len statustext, n, X(statustext[code])); + }else + s = sys->sprint("%d %d error", step, len statustext); + if(arg != nil) + s += sys->sprint(": %s", arg); + return s; +} + +getifc(): (ref Sys->FD, string, string) +{ + clonefile := netdir+"/ipifc/clone"; + cfd := sys->open(clonefile, Sys->ORDWR); + if(cfd == nil) + return (nil, nil, sys->sprint("can't open %s: %r", clonefile)); + buf := array[32] of byte; + n := sys->read(cfd, buf, len buf); + if(n <= 0) + return (nil, nil, sys->sprint("can't read %s: %r", clonefile)); + return (cfd, netdir+"/ipifc/" + string buf[0:n], nil); +} + +addroute(addr, mask, gate: string): int +{ + fd := sys->open(netdir+"/iproute", Sys->OWRITE); + if(fd == nil) + return -1; + return sys->fprint(fd, "add %s %s %s", addr, mask, gate); +} + +# uchar vihl; /* Version and header length */ +# uchar tos; /* Type of service */ +# uchar length[2]; /* packet length */ +# uchar id[2]; /* ip->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 dst[4]; /* IP destination */ +IPhdrlen: con 20; + +netreader(dfd: ref Sys->FD, dir: string, localip: string, fake: int, outc: chan of ref Packet) +{ + buf := array [32*1024] of byte; + while((n := sys->read(dfd, buf, len buf)) > 0){ + if(n < IPhdrlen){ + sys->print("ppplink: received short packet: %d bytes\n", n); + continue; + } + pkt := ref Packet; + if(n < 9*1024){ + pkt.data = array[n] of byte; + pkt.data[0:] = buf[0:n]; + }else{ + pkt.data = buf[0:n]; + buf = array[32*1024] of byte; + } + pkt.src = pkt.data[12:]; + pkt.dst = pkt.data[16:]; + outc <-= pkt; + } + if(n < 0) + error(1, sys->sprint("packet interface read error: %r")); + else if(n == 0) + error(1, "packet interface: end of file"); +} + +ipa(a: array of byte): string +{ + if(len a < 4) + return "???"; + return sys->sprint("%d.%d.%d.%d", int a[0], int a[1], int a[2], int a[3]); +} + +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +readppplog(log: chan of (int, string), errfile: string, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + src := sys->open(errfile, Sys->OREAD); + if(src == nil) + log <-= (Error, sys->sprint("can't open %s: %r", errfile)); + + buf := array[1024] of byte; + connected := 0; + lasterror := ""; + + while((count := sys->read(src, buf, len buf)) > 0) { + (nil, tokens) := sys->tokenize(string buf[:count],"\n"); + for(; tokens != nil; tokens = tl tokens) { + case hd tokens { + "no error" => + log <-= (Linkup, nil); + lasterror = nil; + connected = 1; + "permission denied" => + lasterror = X("Username or Password Incorrect"); + log <-= (Error, lasterror); + "write to hungup channel" => + lasterror = X("Remote Host Hung Up"); + log <-= (Error, lasterror); + * => + lasterror = X(hd tokens); + log <-= (Error, lasterror); + } + } + } + if(count == 0 && connected && lasterror == nil){ # should change ip/pppmedium.c instead? + #hangup(nil); + log <-= (Error, X("Lost Connection")); + } +} + +dialup(mi: ref Modem->ModemInfo, number: string, scriptinfo: ref Script->ScriptInfo, logchan: chan of (int, string)): (string, ref Sys->Connection) +{ + logchan <-= (Modeminit, nil); + + # open & init the modem + + modeminfo = mi; + modem := load Modem Modem->PATH; + if(modem == nil) + return (sys->sprint("can't load %s: %r", Modem->PATH), nil); + err := modem->init(); + if(err != nil) + return (sys->sprint("couldn't init modem: %s", err), nil); + Device: import modem; + d := Device.new(modeminfo, 1); + logchan <-= (Dialling, number); + err = d.dial(number); + if(err != nil){ + d.close(); + return (err, nil); + } + logchan <-= (Modemup, nil); + + # login script + + if(scriptinfo != nil) { + logchan <-= (Scriptstart, nil); + err = runscript(modem, d, scriptinfo); + if(err != nil){ + d.close(); + return (err, nil); + } + logchan <-= (Scriptdone, nil); + } + + mc := d.close(); + return (nil, mc); + +} + +startppp(logchan: chan of (int, string), pppinfo: ref PPPInfo): (string, string) +{ + (ifd, dir, err) := getifc(); + if(ifd == nil) + return (err, nil); + + sync := chan of int; + spawn readppplog(logchan, dir + "/err", sync); # unbind gives eof on err + <-sync; + + if(pppinfo.ipaddr == nil) + pppinfo.ipaddr = "-"; +# if(pppinfo.ipmask == nil) +# pppinfo.ipmask = "255.255.255.255"; + if(pppinfo.peeraddr == nil) + pppinfo.peeraddr = "-"; + if(pppinfo.maxmtu == nil) + pppinfo.maxmtu = "-"; +# if(pppinfo.maxmtu <= 0) +# pppinfo.maxmtu = mtu; +# if(pppinfo.maxmtu < 576) +# pppinfo.maxmtu = 576; + if(pppinfo.username == nil) + pppinfo.username = "-"; + if(pppinfo.password == nil) + pppinfo.password = "-"; + + ifc := "bind ppp "+modeminfo.path+" "+ pppinfo.ipaddr+" "+pppinfo.peeraddr+" "+pppinfo.maxmtu + +" "+string framing+" "+pppinfo.username+" "+pppinfo.password; + + if(sys->fprint(ifd, "%s", ifc) < 0) + return (sys->sprint("can't bind ppp to %s: %r", dir), nil); + + sys->print("ppplink: %s\n", ifc); + + return (nil, dir); +} + +runscript(modem: Modem, dev: ref Modem->Device, scriptinfo: ref Script->ScriptInfo): string +{ + script := load Script Script->PATH; + if(script == nil) + return sys->sprint("can't load %s: %r", Script->PATH); + err := script->init(modem); + if(err != nil) + return err; + return script->execute(dev, scriptinfo); +} + +hangup(pppdir: string) +{ + sys->print("ppplink: hangup...\n"); + if(pppdir != nil){ # shut down the PPP link + fd := sys->open(pppdir + "/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "unbind") < 0) + sys->print("ppplink: hangup: can't unbind ppp on %s: %r\n", pppdir); + fd = nil; + } + modem := load Modem Modem->PATH; + if(modem == nil) { + sys->print("ppplink: hangup: can't load %s: %r", Modem->PATH); + return; + } + err := modem->init(); + if(err != nil){ + sys->print("ppplink: hangup: couldn't init modem: %s", err); + return; + } + Device: import modem; + d := Device.new(modeminfo, 1); + if(d != nil){ + d.onhook(); + d.close(); + } +} + +kill(pid: int, msg: string) +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "%s", msg) < 0) + sys->print("pppclient: can't %s %d: %r\n", msg, pid); +} + +error(dokill: int, s: string) +{ + sys->fprint(sys->fildes(2), "ppplink: %s\n", s); + if(dokill) + kill(sys->pctl(0, nil), "killgrp"); + raise "fail:error"; +} + +X(s : string) : string +{ + if(dict != nil) + return dict.xlate(s); + return s; +} + +cfile(file: string): string +{ + if(len file > 0 && file[0] == '/') + return file; + return "/usr/"+user()+"/config/"+file; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + buf := array[64] of byte; + if(fd != nil && (n := sys->read(fd, buf, len buf)) > 0) + return string buf[0:n]; + return "inferno"; # hmmm. +} + +cfvalue(c: ref ConfigFile, key: string) :string +{ + s := ""; + for(values := c.getcfg(key); values != nil; values = tl values){ + if(s != "") + s[len s] = ' '; + s += hd values; + } + return s; +} + +configinit(): string +{ + cfg = load CfgFile CfgFile->PATH; + if(cfg == nil) + return sys->sprint("can't load %s: %r", CfgFile->PATH); + + # Modem Configuration + + modemdb := cfile(MODEM_DB_PATH); + cfg->verify(DEFAULT_MODEM_DB_PATH, modemdb); + modemcfg := cfg->init(modemdb); + if(modemcfg == nil) + return sys->sprint("can't open %s: %r", modemdb); + modeminfo = ref Modem->ModemInfo; + modeminfo.path = cfvalue(modemcfg, "PATH"); + modeminfo.init = cfvalue(modemcfg, "INIT"); + modeminfo.country = cfvalue(modemcfg, "COUNTRY"); + modeminfo.other = cfvalue(modemcfg, "OTHER"); + modeminfo.errorcorrection = cfvalue(modemcfg,"CORRECT"); + modeminfo.compression = cfvalue(modemcfg,"COMPRESS"); + modeminfo.flowctl = cfvalue(modemcfg,"FLOWCTL"); + modeminfo.rateadjust = cfvalue(modemcfg,"RATEADJ"); + modeminfo.mnponly = cfvalue(modemcfg,"MNPONLY"); + modeminfo.dialtype = cfvalue(modemcfg,"DIALING"); + if(modeminfo.dialtype!="ATDP") + modeminfo.dialtype="ATDT"; + + ispdb := cfile(ISP_DB_PATH); + cfg->verify(DEFAULT_ISP_DB_PATH, ispdb); + sys->print("cfg->init(%s)\n", ispdb); + + # ISP Configuration + pppcfg := cfg->init(ispdb); + if(pppcfg == nil) + return sys->sprint("can't read or create ISP configuration file %s: %r", ispdb); + (ok, stat) := sys->stat(ispdb); + if(ok >= 0) + lastCdir = ref stat; + + pppinfo = ref PPPInfo; + isp_number = cfvalue(pppcfg, "NUMBER"); + pppinfo.ipaddr = cfvalue(pppcfg,"IPADDR"); + pppinfo.ipmask = cfvalue(pppcfg,"IPMASK"); + pppinfo.peeraddr = cfvalue(pppcfg,"PEERADDR"); + pppinfo.maxmtu = cfvalue(pppcfg,"MAXMTU"); + pppinfo.username = cfvalue(pppcfg,"USERNAME"); + pppinfo.password = cfvalue(pppcfg,"PASSWORD"); + + info := pppcfg.getcfg("SCRIPT"); + if(info != nil) { + scriptinfo = ref Script->ScriptInfo; + scriptinfo.path = hd info; + scriptinfo.username = pppinfo.username; + scriptinfo.password = pppinfo.password; + } else + scriptinfo = nil; + + info = pppcfg.getcfg("TIMEOUT"); + if(info != nil) + scriptinfo.timeout = int (hd info); + cfg = nil; # unload it + + if(modeminfo.path == nil) + return "no modem device configured"; + if(isp_number == nil) + return "no telephone number configured for ISP"; + + return nil; +} + +isipaddr(a: string): int +{ + i, c, ac, np : int = 0; + + for(i = 0; i < len a; i++) { + c = a[i]; + if(c >= '0' && c <= '9') { + np = 10*np + c - '0'; + continue; + } + if(c == '.' && np) { + ac++; + if(np > 255) + return 0; + np = 0; + continue; + } + return 0; + } + return np && np < 256 && ac == 3; +} + +userinterface(sync: chan of int) +{ + pppgui := load Command "pppchat.dis"; + if(pppgui == nil){ + sys->fprint(sys->fildes(2), "ppplink: can't load %s: %r\n", "/dis/svc/nppp/pppchat.dis"); + # TO DO: should be optional + sync <-= 0; + } + + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); + sync <-= sys->pctl(0, nil); + pppgui->init(context, "pppchat" :: nil); +} + +pppconnect(result: chan of (string, string), sync: chan of int, status: chan of (int, string)) +{ + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); + sync <-= sys->pctl(0, nil); + pppdir: string; + (err, mc) := dialup(modeminfo, isp_number, scriptinfo, status); # mc keeps connection open until startppp binds it to ppp + if(err == nil){ + if(0 && (cfd := mc.cfd) != nil){ + sys->fprint(cfd, "m1"); # cts/rts flow control/fifo's on + sys->fprint(cfd, "q64000"); # increase queue size to 64k + sys->fprint(cfd, "n1"); # nonblocking writes on + sys->fprint(cfd, "r1"); # rts on + sys->fprint(cfd, "d1"); # dtr on + } + status <-= (Startingppp, nil); + (err, pppdir) = startppp(status, pppinfo); + if(err == nil){ + status <-= (Startedppp, nil); + result <-= (nil, pppdir); + return; + } + } + status <-= (Error, err); + result <-= (err, nil); +} + +getspeed(file: string): string +{ + return findrate("/dev/modemstat", "rcvrate" :: "baud" :: nil); +} + +findrate(file: string, opt: list of string): string +{ + fd := sys->open(file, sys->OREAD); + if(fd == nil) + return nil; + buf := array [1024] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 1) + return nil; + (nil, flds) := sys->tokenize(string buf[0:n], " \t\r\n"); + for(; flds != nil; flds = tl flds) + for(l := opt; l != nil; l = tl l) + if(hd flds == hd l) + return hd tl flds; + return nil; +} + +samefile(d1, d2: Sys->Dir): int +{ + return d1.dev==d2.dev && d1.dtype==d2.dtype && + d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers && + d1.mtime==d2.mtime; +} + +abs(n: int): int +{ + if(n < 0) + return -n; + return n; +} |
