ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/ip/nppp/ppplink.b/
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;
}