summaryrefslogtreecommitdiff
path: root/appl/cmd/ip/nppp
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ip/nppp')
-rw-r--r--appl/cmd/ip/nppp/mkfile24
-rw-r--r--appl/cmd/ip/nppp/modem.b469
-rw-r--r--appl/cmd/ip/nppp/modem.m47
-rw-r--r--appl/cmd/ip/nppp/pppchat.b322
-rw-r--r--appl/cmd/ip/nppp/ppplink.b782
-rw-r--r--appl/cmd/ip/nppp/ppptest.b90
-rw-r--r--appl/cmd/ip/nppp/script.b171
-rw-r--r--appl/cmd/ip/nppp/script.m15
8 files changed, 1920 insertions, 0 deletions
diff --git a/appl/cmd/ip/nppp/mkfile b/appl/cmd/ip/nppp/mkfile
new file mode 100644
index 00000000..0f803acd
--- /dev/null
+++ b/appl/cmd/ip/nppp/mkfile
@@ -0,0 +1,24 @@
+<../../../../mkconfig
+
+TARG=\
+ ppplink.dis\
+ pppchat.dis\
+ modem.dis\
+ script.dis\
+# ppptest.dis\
+
+MODULES=\
+ modem.m\
+ script.m\
+
+SYSMODULES=\
+ sys.m\
+ draw.m\
+ tk.m\
+ dict.m\
+ string.m\
+ lock.m\
+
+DISBIN=$ROOT/dis/ip/nppp
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/ip/nppp/modem.b b/appl/cmd/ip/nppp/modem.b
new file mode 100644
index 00000000..f8c81396
--- /dev/null
+++ b/appl/cmd/ip/nppp/modem.b
@@ -0,0 +1,469 @@
+implement Modem;
+
+#
+# Copyright © 1998-2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "lock.m";
+ lock: Lock;
+ Semaphore: import lock;
+
+include "draw.m";
+
+include "modem.m";
+
+hangupcmd := "ATH0"; # was ATZH0 but some modem versions on Umec hung on ATZ
+
+# modem return codes
+Ok, Success, Failure, Abort, Noise, Found: con iota;
+
+maxspeed: con 115200;
+
+#
+# modem return messages
+#
+Msg: adt {
+ text: string;
+ code: int;
+};
+
+msgs: array of Msg = array [] of {
+ ("OK", Ok),
+ ("NO CARRIER", Failure),
+ ("ERROR", Failure),
+ ("NO DIALTONE", Failure),
+ ("BUSY", Failure),
+ ("NO ANSWER", Failure),
+ ("CONNECT", Success),
+};
+
+kill(pid: int)
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "kill") < 0)
+ sys->print("modem: can't kill %d: %r\n", pid);
+}
+
+#
+# prepare a modem port
+#
+openserial(d: ref Device): string
+{
+ d.data = nil;
+ d.ctl = nil;
+
+ d.data = sys->open(d.local, Sys->ORDWR);
+ if(d.data == nil)
+ return sys->sprint("can't open %s: %r", d.local);
+
+ d.ctl = sys->open(d.local+"ctl", Sys->ORDWR);
+ if(d.ctl == nil)
+ return sys->sprint("can't open %s: %r", d.local+"ctl");
+
+ d.speed = maxspeed;
+ d.avail = nil;
+ return nil;
+}
+
+#
+# shut down the monitor (if any) and return the connection
+#
+
+Device.close(m: self ref Device): ref Sys->Connection
+{
+ if(m.pid != 0){
+ kill(m.pid);
+ m.pid = 0;
+ }
+ if(m.data == nil)
+ return nil;
+ mc := ref sys->Connection(m.data, m.ctl, nil);
+ m.ctl = nil;
+ m.data = nil;
+ return mc;
+}
+
+#
+# Send a string to the modem
+#
+
+Device.send(d: self ref Device, x: string): string
+{
+ a := array of byte x;
+ f := sys->write(d.data, a, len a);
+ if(f != len a) {
+ # let's attempt to close & reopen the modem
+ d.close();
+ err := openserial(d);
+ if(err != nil)
+ return err;
+ f = sys->write(d.data,a, len a);
+ if(f < 0)
+ return sys->sprint("%r");
+ if(f != len a)
+ return "short write";
+ }
+ if(d.trace)
+ sys->print("->%s\n",x);
+ return nil;
+}
+
+#
+# apply a string of commands to modem & look for a response
+#
+
+apply(d: ref Device, s: string, substr: string, secs: int): int
+{
+ m := Ok;
+ buf := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ buf[len buf] = c; # assume no Unicode
+ if(c == '\r' || i == (len s -1)){
+ if(c != '\r')
+ buf[len buf] = '\r';
+ if(d.send(buf) != nil)
+ return Abort;
+ (m, nil) = readmsg(d, secs, substr);
+ buf = "";
+ }
+ }
+ return m;
+}
+
+#
+# get modem into command mode if it isn't already
+#
+GUARDTIME: con 1100; # usual default for S12=50 in units of 1/50 sec; allow 100ms fuzz
+
+attention(d: ref Device): int
+{
+ for(i := 0; i < 3; i++){
+ if(apply(d, hangupcmd, nil, 2) == Ok)
+ return Ok;
+ sys->sleep(GUARDTIME);
+ if(d.send("+++") != nil)
+ return Abort;
+ sys->sleep(GUARDTIME);
+ (nil, msg) := readmsg(d, 0, nil);
+ if(msg != nil && d.trace)
+ sys->print("status: %s\n", msg);
+ }
+ return Failure;
+}
+
+#
+# apply a command type
+#
+
+applyspecial(d: ref Device, cmd: string): int
+{
+ if(cmd == nil)
+ return Failure;
+ return apply(d, cmd, nil, 2);
+}
+
+#
+# hang up any connections in progress and close the device
+#
+Device.onhook(d: self ref Device)
+{
+ # hang up the modem
+ monitoring(d);
+ if(attention(d) != Ok)
+ sys->print("modem: no attention\n");
+
+ # hangup the stream (eg, for ppp) and toggle the lines to the modem
+ if(d.ctl != nil) {
+ sys->fprint(d.ctl,"d0\n");
+ sys->fprint(d.ctl,"r0\n");
+ sys->fprint(d.ctl, "h\n"); # hangup on native serial
+ sys->sleep(250);
+ sys->fprint(d.ctl,"r1\n");
+ sys->fprint(d.ctl,"d1\n");
+ }
+
+ d.close();
+}
+
+#
+# does string s contain t anywhere?
+#
+
+contains(s, t: string): int
+{
+ if(t == nil)
+ return 1;
+ if(s == nil)
+ return 0;
+ n := len t;
+ for(i := 0; i+n <= len s; i++)
+ if(s[i:i+n] == t)
+ return 1;
+ return 0;
+}
+
+#
+# read till we see a message or we time out
+#
+readmsg(d: ref Device, secs: int, substr: string): (int, string)
+{
+ found := 0;
+ msecs := secs*1000;
+ limit := 1000; # pretty arbitrary
+ s := "";
+
+ for(start := sys->millisec(); sys->millisec() <= start+msecs;){
+ a := d.getinput(1);
+ if(len a == 0){
+ if(limit){
+ sys->sleep(1);
+ continue;
+ }
+ break;
+ }
+ if(a[0] == byte '\n' || a[0] == byte '\r' || limit == 0){
+ if (len s) {
+ if (s[(len s)-1] == '\r')
+ s[(len s)-1] = '\n';
+ sys->print("<-%s\n",s);
+ }
+ if(substr != nil && contains(s, substr))
+ found = 1;
+ for(k := 0; k < len msgs; k++)
+ if(len s >= len msgs[k].text &&
+ s[0:len msgs[k].text] == msgs[k].text){
+ if(found)
+ return (Found, s);
+ return (msgs[k].code, s);
+ }
+ start = sys->millisec();
+ s = "";
+ continue;
+ }
+ s[len s] = int a[0];
+ limit--;
+ }
+ s = "no response from modem";
+ if(found)
+ return (Found, s);
+
+ return (Noise, s);
+}
+
+#
+# get baud rate from a connect message
+#
+
+getspeed(msg: string, speed: int): int
+{
+ p := msg[7:]; # skip "CONNECT"
+ while(p[0] == ' ' || p[0] == '\t')
+ p = p[1:];
+ s := int p;
+ if(s <= 0)
+ return speed;
+ else
+ return s;
+}
+
+#
+# set speed and RTS/CTS modem flow control
+#
+
+setspeed(d: ref Device, baud: int)
+{
+ if(d != nil && d.ctl != nil){
+ sys->fprint(d.ctl, "b%d", baud);
+ sys->fprint(d.ctl, "m1");
+ }
+}
+
+monitoring(d: ref Device)
+{
+ # if no monitor then spawn one
+ if(d.pid == 0) {
+ pidc := chan of int;
+ spawn monitor(d, pidc, nil);
+ d.pid = <-pidc;
+ }
+}
+
+#
+# a process to read input from a modem.
+#
+monitor(d: ref Device, pidc: chan of int, errc: chan of string)
+{
+ err := openserial(d);
+ pidc <-= sys->pctl(0, nil);
+ if(err != nil && errc != nil)
+ errc <-= err;
+ a := array[Sys->ATOMICIO] of byte;
+ for(;;) {
+ d.lock.obtain();
+ d.status = "Idle";
+ d.remote = "";
+ setspeed(d, d.speed);
+ d.lock.release();
+ # shuttle bytes
+ while((n := sys->read(d.data, a, len a)) > 0){
+ d.lock.obtain();
+ if (len d.avail < Sys->ATOMICIO) {
+ na := array[len d.avail + n] of byte;
+ na[0:] = d.avail[0:];
+ na[len d.avail:] = a[0:n];
+ d.avail = na;
+ }
+ d.lock.release();
+ }
+ # on an error, try reopening the device
+ d.data = nil;
+ d.ctl = nil;
+ err = openserial(d);
+ if(err != nil && errc != nil)
+ errc <-= err;
+ }
+}
+
+#
+# return up to n bytes read from the modem by monitor()
+#
+Device.getinput(d: self ref Device, n: int): array of byte
+{
+ if(d==nil || n <= 0)
+ return nil;
+ a: array of byte;
+ d.lock.obtain();
+ if(len d.avail != 0){
+ if(n > len d.avail)
+ n = len d.avail;
+ a = d.avail[0:n];
+ d.avail = d.avail[n:];
+ }
+ d.lock.release();
+ return a;
+}
+
+Device.getc(d: self ref Device, msec: int): int
+{
+ start := sys->millisec();
+ while((b := d.getinput(1)) == nil) {
+ if (msec && sys->millisec() > start+msec)
+ return 0;
+ sys->sleep(1);
+ }
+ return int b[0];
+}
+
+init(): string
+{
+ sys = load Sys Sys->PATH;
+ lock = load Lock Lock->PATH;
+ if(lock == nil)
+ return sys->sprint("can't load %s: %r", Lock->PATH);
+ lock->init();
+ return nil;
+}
+
+Device.new(modeminfo: ref ModemInfo, trace: int): ref Device
+{
+ d := ref Device;
+ d.lock = Semaphore.new();
+ d.local = modeminfo.path;
+ d.pid = 0;
+ d.speed = 0;
+ d.t = *modeminfo;
+ if(d.t.hangup == nil)
+ d.t.hangup = hangupcmd;
+ d.trace = trace | 1; # always trace for now
+ return d;
+}
+
+#
+# dial a number
+#
+Device.dial(d: self ref Device, number: string): string
+{
+ monitoring(d);
+
+ # modem type should already be established, but just in case
+ if(d.trace)
+ sys->print("modem: attention\n");
+ x := attention(d);
+ if (x != Ok && d.trace)
+ return "bad response from modem";
+ #
+ # extended Hayes commands, meaning depends on modem
+ #
+ sys->print("modem: init\n");
+ if(d.t.country != nil)
+ applyspecial(d, d.t.country);
+
+ if(d.t.init != nil)
+ applyspecial(d, d.t.init);
+
+ if(d.t.other != nil)
+ applyspecial(d, d.t.other);
+
+ applyspecial(d, d.t.errorcorrection);
+
+ compress := Abort;
+ if(d.t.mnponly != nil)
+ compress = applyspecial(d, d.t.mnponly);
+ if(d.t.compression != nil)
+ compress = applyspecial(d, d.t.compression);
+
+ rateadjust := Abort;
+ if(compress != Ok)
+ rateadjust = applyspecial(d, d.t.rateadjust);
+ applyspecial(d, d.t.flowctl);
+
+ # finally, dialout
+ if(d.trace)
+ sys->print("modem: dial\n");
+ if((dt := d.t.dialtype) == nil)
+ dt = "ATDT";
+ err := d.send(sys->sprint("%s%s\r", dt, number));
+ if(err != nil){
+ if(d.trace)
+ sys->print("modem: can't dial %s: %s\n", number, err);
+ return err;
+ }
+
+ (i, msg) := readmsg(d, 120, nil);
+ if(i != Success){
+ if(d.trace)
+ sys->print("modem: modem error reply: %s\n", msg);
+ return msg;
+ }
+
+ connectspeed := getspeed(msg, d.speed);
+
+ # change line rate if not compressing
+ if(rateadjust == Ok)
+ setspeed(d, connectspeed);
+
+ if(d.ctl != nil){
+ if(d != nil)
+ sys->fprint(d.ctl, "s%d", connectspeed); # set DCE speed (if device implements it)
+ sys->fprint(d.ctl, "c1"); # enable CD monitoring
+ }
+
+ return nil;
+}
+
+dumpa(a: array of byte): string
+{
+ s := "";
+ for(i:=0; i<len a; i++){
+ b := int a[i];
+ if(b >= ' ' && b < 16r7f)
+ s[len s] = b;
+ else
+ s += sys->sprint("\\%.2x", b);
+ }
+ return s;
+}
diff --git a/appl/cmd/ip/nppp/modem.m b/appl/cmd/ip/nppp/modem.m
new file mode 100644
index 00000000..6e84b0e3
--- /dev/null
+++ b/appl/cmd/ip/nppp/modem.m
@@ -0,0 +1,47 @@
+Modem: module
+{
+ PATH: con "/dis/ip/nppp/modem.dis";
+
+ ModemInfo: adt {
+ path: string;
+ init: string;
+ country: string;
+ other: string;
+ errorcorrection:string;
+ compression: string;
+ flowctl: string;
+ rateadjust: string;
+ mnponly: string;
+ dialtype: string;
+ hangup: string;
+ };
+
+ Device: adt {
+ lock: ref Lock->Semaphore;
+ # modem stuff
+ ctl: ref Sys->FD;
+ data: ref Sys->FD;
+
+ local: string;
+ remote: string;
+ status: string;
+ speed: int;
+ t: ModemInfo;
+ trace: int;
+
+ # input reader
+ avail: array of byte;
+ pid: int;
+
+ new: fn(i: ref ModemInfo, trace: int): ref Device;
+ dial: fn(m: self ref Device, number: string): string;
+ getc: fn(m: self ref Device, msec: int): int;
+ getinput: fn(m: self ref Device, n: int): array of byte;
+ send: fn(m: self ref Device, x: string): string;
+ close: fn(m: self ref Device): ref Sys->Connection;
+ onhook: fn(m: self ref Device);
+ };
+
+ init: fn(): string;
+
+};
diff --git a/appl/cmd/ip/nppp/pppchat.b b/appl/cmd/ip/nppp/pppchat.b
new file mode 100644
index 00000000..77202b18
--- /dev/null
+++ b/appl/cmd/ip/nppp/pppchat.b
@@ -0,0 +1,322 @@
+implement Dialupchat;
+
+#
+# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "wmlib.m";
+ wmlib: Wmlib;
+
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+ dict: ref Dict;
+
+Dialupchat: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# Dimension constant for ISP Connect window
+WIDTH: con 300;
+HEIGHT: con 58;
+
+LightGreen: con "#00FF80"; # colour for successful blob
+Blobx: con 8;
+Gapx: con 4;
+BARW: con (Blobx+Gapx)*10; # Progress bar width
+BARH: con 18; # Progress bar height
+DIALQUANTA : con 1000;
+ICONQUANTA : con 5000;
+
+pppquanta := DIALQUANTA;
+
+Maxstep: con 9;
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ wmlib = load Wmlib Wmlib->PATH;
+ wmlib->init();
+
+ translate = load Translate Translate->PATH;
+ if(translate != nil) {
+ translate->init();
+ dictname := translate->mkdictname("", "pppchat");
+ dicterr: string;
+ (dict, dicterr) = translate->opendict(dictname);
+ if(dicterr != nil)
+ sys->fprint(sys->fildes(2), "pppchat: can't open %s: %s\n", dictname, dicterr);
+ }else
+ sys->fprint(sys->fildes(2), "pppchat: can't load %s: %r\n", Translate->PATH);
+
+ tkargs: string;
+ if(args != nil) {
+ tkargs = hd args;
+ args = tl args;
+ }
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ pppfd := sys->open("/chan/pppctl", Sys->ORDWR);
+ if(pppfd == nil)
+ error(sys->sprint("can't open /chan/pppctl: %r"));
+
+ (t, wmctl) := wmlib->titlebar(ctxt.screen, tkargs, X("Dialup Connection"), Wmlib->Hide);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ pb := Progressbar.mk(t, ".f.prog.c", (BARW, BARH));
+
+ config_win := array[] of {
+ "frame .f",
+ "frame .f.prog",
+ "frame .f.b",
+
+ pb.tkcreate(),
+ "pack .f.prog.c -pady 6 -side top",
+
+ "label .f.stat -fg blue -text {"+X("Initialising connection...")+"}",
+ "pack .f.stat -side top -fill x -expand 1 -anchor n",
+
+ "pack .f -side top -expand 1 -padx 5 -pady 3 -fill both -anchor w",
+ "pack .f.prog -side top -expand 1 -fill x",
+ "button .f.b.done -text {"+X("Cancel")+"} -command {send cmd cancel}",
+ "pack .f.b.done -side right -padx 1 -pady 1 -anchor s",
+ "button .f.b.retry -text {"+X("Retry")+"} -command {send cmd retry} -state disabled",
+ "pack .f.b.retry -side left -padx 1 -pady 1 -anchor s",
+ "pack .f.b -side top -expand 1 -fill x",
+
+ "pack propagate . 0",
+ "update",
+ };
+
+ for(i := 0; i < len config_win; i++)
+ tkcmd(t, config_win[i]);
+
+ connected := 0;
+ winmapped := 1;
+ timecount := 0;
+ xmin := 0;
+ x := 0;
+ turn := 0;
+
+ pppquanta = DIALQUANTA;
+ ticks := chan of int;
+ spawn ppptimer(ticks);
+
+ statuslines := chan of (string, string);
+ pids := chan of int;
+ spawn ctlreader(pppfd, pids, statuslines);
+ ctlpid := <-pids;
+
+Work:
+ for(;;) alt {
+
+ s := <-wmctl =>
+ if(s == "exit")
+ s = "task";
+ if(s == "task"){
+ spawn wmlib->titlectl(t, s);
+ continue;
+ }
+ wmlib->titlectl(t, s);
+
+ press := <-cmd =>
+ case press {
+ "cancel" or "disconnect" =>
+ tkcmd(t, sys->sprint(".f.stat configure -text '%s", X("Disconnecting")));
+ tkcmd(t, "update");
+ if(sys->fprint(pppfd, "hangup") < 0){
+ err := sys->sprint("%r");
+ tkcmd(t, sys->sprint(".f.stat configure -text '%s: %s", X("Error disconnecting"), X(err)));
+ sys->fprint(sys->fildes(2), "pppchat: can't disconnect: %s\n", err);
+ }
+ break Work;
+ "retry" =>
+ if(sys->fprint(pppfd, "connect") < 0){
+ err := sys->sprint("%r");
+ }
+ }
+
+ <-ticks =>
+ ticks <-= 1;
+ if(!connected){
+ if(pb != nil){
+ if((turn ^= 1) == 0)
+ pb.setcolour("white");
+ else
+ pb.setcolour(LightGreen);
+ }
+ tkcmd(t, "raise .; update");
+ }
+
+ (status, err) := <-statuslines =>
+ if(status == nil){
+ status = "0 1 empty status";
+ if(err != nil)
+ sys->print("pppchat: !%s\n", err);
+ } else
+ sys->print("pppchat: %s\n", status);
+ (nf, flds) := sys->tokenize(status, " \t\n");
+# for(i = 0; i < len status; i++)
+# if(status[i] == ' ' || status[i] == '\t') {
+# status = status[i+1:];
+# break;
+# }
+ if(nf < 3)
+ break;
+ step := int hd flds; flds = tl flds;
+ nstep := int hd flds; flds = tl flds;
+ if(step < 0)
+ raise "pppchat: bad step";
+ case hd flds {
+ "error:" =>
+ tkcmd(t, ".f.stat configure -fg red -text '"+X(status));
+ tkcmd(t, ".f.b.retry configure -state normal");
+ tkcmd(t, "update");
+ wmlib->unhide();
+ winmapped = 1;
+ pb.stepto(step, "red");
+ #break Work;
+ * =>
+ pb.setcolour(LightGreen);
+ pb.stepto(step, LightGreen);
+ }
+ turn = 0;
+ statusmsg := X(status);
+ tkcmd(t, ".f.stat configure -text '"+statusmsg);
+ tkcmd(t, "raise .; update");
+
+ case hd flds {
+ "up" or "done" =>
+ if(!connected){
+ connected = 1;
+ }
+ pppquanta = ICONQUANTA;
+
+ # display connection speed
+ if(tl flds != nil)
+ tkcmd(t, ".f.stat configure -text {"+statusmsg+" "+"SPEED"+" hd tl flds}");
+ else
+ tkcmd(t, ".f.stat configure -text {"+statusmsg+"}");
+ tkcmd(t, ".f.b.done configure -text Disconnect -command 'send cmd disconnect");
+ tkcmd(t, "update");
+ sys->sleep(2000);
+ tkcmd(t, "pack forget .f.prog; update");
+ spawn wmlib->titlectl(t, "task");
+ winmapped = 0;
+ }
+ tkcmd(t, "update");
+ }
+ <-ticks;
+ ticks <-= 0; # stop ppptimer
+ kill(ctlpid);
+}
+
+ppptimer(ticks: chan of int)
+{
+ do{
+ sys->sleep(pppquanta);
+ ticks <-= 1;
+ }while(<-ticks);
+}
+
+ctlreader(fd: ref Sys->FD, pidc: chan of int, lines: chan of (string, string))
+{
+ pidc <-= sys->pctl(0, nil);
+ buf := array[128] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0)
+ lines <-= (string buf[0:n], nil);
+ if(n < 0)
+ lines <-= (nil, sys->sprint("%r"));
+ else
+ lines <-= (nil, nil);
+}
+
+Progressbar: adt {
+ t: ref Tk->Toplevel;
+ canvas: string;
+ csize: Point;
+ blobs: list of string;
+
+ mk: fn(t: ref Tk->Toplevel, canvas: string, csize: Point): ref Progressbar;
+ tkcreate: fn(pb: self ref Progressbar): string;
+ setcolour: fn(pb: self ref Progressbar, c: string);
+ stepto: fn(pb: self ref Progressbar, step: int, col: string);
+ destroy: fn(pb: self ref Progressbar);
+};
+
+Progressbar.mk(t: ref Tk->Toplevel, canvas: string, csize: Point): ref Progressbar
+{
+ return ref Progressbar(t, canvas, csize, nil);
+}
+
+Progressbar.tkcreate(pb: self ref Progressbar): string
+{
+ return sys->sprint("canvas %s -width %d -height %d", pb.canvas, pb.csize.x, pb.csize.y);
+}
+
+Progressbar.setcolour(pb: self ref Progressbar, colour: string)
+{
+ if(pb.blobs != nil)
+ tkcmd(pb.t, sys->sprint("%s itemconfigure %s -fill %s; update", pb.canvas, hd pb.blobs, colour));
+}
+
+Progressbar.stepto(pb: self ref Progressbar, step: int, col: string)
+{
+ for(nblob := len pb.blobs; nblob > step+1; nblob--){
+ tkcmd(pb.t, sys->sprint("%s delete %s", pb.canvas, hd pb.blobs));
+ pb.blobs = tl pb.blobs;
+ }
+ if(nblob == step+1)
+ return;
+ p := Point(step*(Blobx+Gapx), 0);
+ r := Rect(p, p.add((Blobx, pb.csize.y-2)));
+ pb.blobs = tkcmd(pb.t, sys->sprint("%s create rectangle %d %d %d %d -fill %s", pb.canvas, r.min.x,r.min.y, r.max.x,r.max.y, col)) :: pb.blobs;
+}
+
+Progressbar.destroy(pb: self ref Progressbar)
+{
+ tk->cmd(pb.t, "destroy "+pb.canvas); # ignore errors
+}
+
+tkcmd(t: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(t, s);
+ if(e != nil && e[0] == '!')
+ sys->print("pppchat: tk error: %s [%s]\n", e, s);
+ return e;
+}
+
+kill(pid: int)
+{
+ if(pid > 0 && (fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE)) != nil)
+ sys->fprint(fd, "kill");
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "pppchat: %s\n", s);
+ raise "fail:error";
+}
+
+X(s: string): string
+{
+ if(dict != nil)
+ return dict.xlate(s);
+ return s;
+}
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;
+}
diff --git a/appl/cmd/ip/nppp/ppptest.b b/appl/cmd/ip/nppp/ppptest.b
new file mode 100644
index 00000000..af8e16e0
--- /dev/null
+++ b/appl/cmd/ip/nppp/ppptest.b
@@ -0,0 +1,90 @@
+# Last change: R 24 May 2001 11:05 am
+implement PPPTest;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+
+include "lock.m";
+include "modem.m";
+include "script.m";
+include "pppclient.m";
+include "pppgui.m";
+
+PPPTest: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+usage()
+{
+ sys->print("ppptest device modem_init tel user password \n");
+ sys->print("Example: ppptest /dev/modem atw2 4125678 rome xxxxxxxx\n");
+ exit;
+
+}
+init( ctxt: ref Draw->Context, argv: list of string )
+{
+ sys = load Sys Sys->PATH;
+
+ mi: Modem->ModemInfo;
+ pi: PPPClient->PPPInfo;
+ tel : string;
+# si: Script->ScriptInfo;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.path = hd argv;
+
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ mi.init = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ tel = hd argv;
+ argv = tl argv;
+ if(argv == nil)
+ usage();
+ else
+ pi.username = hd argv;
+ argv = tl argv;
+ if(argv==nil)
+ usage();
+ else
+ pi.password = hd argv;
+
+
+ #si.path = "rdid.script";
+ #si.username = "ericvh";
+ #si.password = "foobar";
+ #si.timeout = 60;
+
+
+ ppp := load PPPClient PPPClient->PATH;
+
+ logger := chan of int;
+
+ spawn ppp->connect( ref mi, tel, nil, ref pi, logger );
+
+ pppgui := load PPPGUI PPPGUI->PATH;
+ (respchan, err) := pppgui->init(ctxt, logger, ppp, nil);
+ if(err != nil){
+ sys->print("ppptest: can't %s: %s\n", PPPGUI->PATH, err);
+ exit;
+ }
+
+ event := 0;
+ while(1) {
+ event =<- respchan;
+ sys->print("GUI event received: %d\n",event);
+ if(event) {
+ sys->print("success");
+ exit;
+ } else {
+ raise "fail: Couldn't connect to ISP";
+ }
+ }
+}
diff --git a/appl/cmd/ip/nppp/script.b b/appl/cmd/ip/nppp/script.b
new file mode 100644
index 00000000..d929ff7a
--- /dev/null
+++ b/appl/cmd/ip/nppp/script.b
@@ -0,0 +1,171 @@
+implement Script;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "lock.m";
+include "modem.m";
+ modem: Modem;
+ Device: import modem;
+
+include "script.m";
+
+Scriptlim: con 32*1024; # should be enough for all
+
+init(mm: Modem): string
+{
+ sys = load Sys Sys->PATH;
+ modem = mm;
+ str = load String String->PATH;
+ if(str == nil)
+ return sys->sprint("can't load %s: %r", String->PATH);
+ return nil;
+}
+
+execute(m: ref Modem->Device, scriptinfo: ref ScriptInfo): string
+{
+ if(scriptinfo.path != nil) {
+ if(m.trace)
+ sys->print("script: using %s\n",scriptinfo.path);
+ # load the script
+ err: string;
+ (scriptinfo.content, err) = scriptload(scriptinfo.path);
+ if(err != nil)
+ return err;
+ }else{
+ if(m.trace)
+ sys->print("script: using inline script\n");
+ }
+
+ if(scriptinfo.timeout == 0)
+ scriptinfo.timeout = 20;
+
+ tend := sys->millisec() + 1000*scriptinfo.timeout;
+
+ for(conv := scriptinfo.content; conv != nil; conv = tl conv){
+ e, s: string = nil;
+ p := hd conv;
+ if(len p == 0)
+ continue;
+ if(m.trace)
+ sys->print("script: %s\n",p);
+ if(p[0] == '-') { # just send
+ if(len p == 1)
+ continue;
+ s = p[1:];
+ } else {
+ (n, esl) := sys->tokenize(p, "-");
+ if(n > 0) {
+ e = hd esl;
+ esl = tl esl;
+ if(n > 1)
+ s = hd esl;
+ }
+ }
+ if(e != nil) {
+ if(match(m, special(e,scriptinfo), tend-sys->millisec()) == 0) {
+ if(m.trace)
+ sys->print("script: match failed\n");
+ return "script failed";
+ }
+ }
+ if(s != nil)
+ m.send(special(s, scriptinfo));
+ }
+ if(m.trace)
+ sys->print("script: done\n");
+ return nil;
+}
+
+match(m: ref Modem->Device, s: string, msec: int): int
+{
+ for(;;) {
+ c := m.getc(msec);
+ if(c == '\r')
+ c = '\n';
+ if(m.trace)
+ sys->print("%c",c);
+ if(c == 0)
+ return 0;
+ head:
+ while(c == s[0]) {
+ i := 1;
+ while(i < len s) {
+ c = m.getc(msec);
+ if(c == '\r')
+ c = '\n';
+ if(m.trace)
+ sys->print("%c",c);
+ if(c == 0)
+ return 0;
+ if(c != s[i])
+ continue head;
+ i++;
+ }
+ return 1;
+ }
+ if(c == '~')
+ return 1; # assume PPP for now
+ }
+}
+
+#
+# Expand special script sequences
+#
+special(s: string, scriptinfo: ref ScriptInfo): string
+{
+ if(s == "$username") # special variable
+ s = scriptinfo.username;
+ else if(s == "$password")
+ s = scriptinfo.password;
+ return deparse(s);
+}
+
+deparse(s: string): string
+{
+ r: string = "";
+ for(i:=0; i < len s; i++) {
+ c := s[i];
+ if(c == '\\' && i+1 < len s) {
+ c = s[++i];
+ case c {
+ 't' => c = '\t';
+ 'n' => c = '\n';
+ 'r' => c = '\r';
+ 'b' => c = '\b';
+ 'a' => c = '\a';
+ 'v' => c = '\v';
+ '0' => c = '\0';
+ '$' => c = '$';
+ 'u' =>
+ if(i+4 < len s) {
+ i++;
+ (c, nil) = str->toint(s[i:i+4], 16);
+ i+=3;
+ }
+ }
+ }
+ r[len r] = c;
+ }
+ return r;
+}
+
+scriptload(path: string): (list of string, string)
+{
+ dfd := sys->open(path, Sys->OREAD);
+ if(dfd == nil)
+ return (nil, sys->sprint("can't open script %s: %r", path));
+
+ b := array[Scriptlim] of byte;
+ n := sys->read(dfd, b, len b);
+ if(n < 0)
+ return (nil, sys->sprint("can't read script %s: %r", path));
+
+ (nil, script) := sys->tokenize(string b[0:n], "\n");
+ return (script, nil);
+}
diff --git a/appl/cmd/ip/nppp/script.m b/appl/cmd/ip/nppp/script.m
new file mode 100644
index 00000000..a1f66e06
--- /dev/null
+++ b/appl/cmd/ip/nppp/script.m
@@ -0,0 +1,15 @@
+Script: module
+{
+ PATH: con "/dis/ip/nppp/script.dis";
+
+ ScriptInfo: adt {
+ path: string;
+ content: list of string;
+ timeout: int;
+ username: string;
+ password: string;
+ };
+
+ init: fn(m: Modem): string;
+ execute: fn(m: ref Modem->Device, scriptinfo: ref ScriptInfo): string;
+};