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; }