diff options
Diffstat (limited to 'appl/cmd/auth/factotum/factotum.b')
| -rw-r--r-- | appl/cmd/auth/factotum/factotum.b | 978 |
1 files changed, 978 insertions, 0 deletions
diff --git a/appl/cmd/auth/factotum/factotum.b b/appl/cmd/auth/factotum/factotum.b new file mode 100644 index 00000000..5f5b02a3 --- /dev/null +++ b/appl/cmd/auth/factotum/factotum.b @@ -0,0 +1,978 @@ +implement Factotum, Authio; + +# +# Copyright © 2003-2004 Vita Nuova Holdings Limited +# + +include "sys.m"; + sys: Sys; + Rread, Rwrite: import Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "keyring.m"; + +include "authio.m"; + +include "arg.m"; + +Factotum: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +#confirm, log + +Files: adt { + ctl: ref Sys->FileIO; + rpc: ref Sys->FileIO; + proto: ref Sys->FileIO; + needkey: ref Sys->FileIO; +}; + +Debug: con 0; +debug := Debug; + +files: Files; +authio: Authio; + +keymanc: chan of (list of ref Attr, int, chan of (ref Key, string)); + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + authio = load Authio "$self"; + + svcname := "#sfactotum"; + mntpt := "/mnt/factotum"; + arg := load Arg Arg->PATH; + if(arg != nil){ + arg->init(args); + arg->setusage("auth/factotum [-d] [-m /mnt/factotum] [-s factotum]"); + while((o := arg->opt()) != 0) + case o { + 'd' => debug = 1; + 'm' => mntpt = arg->earg(); + 's' => svcname = "#s"+arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + } + sys->unmount(nil, mntpt); + if(sys->bind(svcname, mntpt, Sys->MREPL) < 0) + err(sys->sprint("can't bind %s on %s: %r", svcname, mntpt)); + files.ctl = sys->file2chan(mntpt, "ctl"); + files.rpc = sys->file2chan(mntpt, "rpc"); + files.proto = sys->file2chan(mntpt, "proto"); + files.needkey = sys->file2chan(mntpt, "needkey"); + if(files.ctl == nil || files.rpc == nil || files.proto == nil || files.needkey == nil) + err(sys->sprint("can't create %s/*: %r", mntpt)); + keymanc = chan of (list of ref Attr, int, chan of (ref Key, string)); + spawn factotumsrv(); +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return nil; + return string b[0:n]; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "factotum: %s\n", s); + raise "fail:error"; +} + +rlist: list of ref Fid; + +factotumsrv() +{ + sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKENV, nil); + if(!Debug) + privacy(); + allkeys := array[0] of ref Key; + pidc := chan of int; + donec := chan of ref Fid; +# keyc := chan of (list of ref Attr, chan of (ref Key, string)); + needfid := -1; + needed, needy: list of (int, list of ref Attr, chan of (ref Key, string)); + needread: Sys->Rread; + needtag := 0; + for(;;) X: alt{ + r := <-donec => + r.pid = 0; + cleanfid(r.fid); + + (off, nbytes, nil, rc) := <-files.ctl.read => + if(rc == nil) + break; + s := ""; + for(i := 0; i < len allkeys; i++) + if((k := allkeys[i]) != nil) + s += k.safetext()+"\n"; + rc <-= reads(s, off, nbytes); + (nil, data, nil, wc) := <-files.ctl.write => + if(wc == nil) + break; + (nf, flds) := sys->tokenize(string data, "\n\r"); + if(nf > 1){ + # compatibility with plan 9; has the advantage you can tell which key is wrong + wc <-= (0, "multiline write not allowed"); + break; + } + s := hd flds; + if(s == nil || s[0] == '#'){ + wc <-= (len data, nil); + break; + } + for(i := 0; i < len s && s[i] != ' '; i++){ + # skip + } + verb := s[0:i]; + if(i < len s) + i++; + s = s[i:]; + case verb { + "key" => + k := Key.mk(parseline(s)); + if(k == nil){ + wc <-= (len data, nil); # ignore it + break; + } + if(lookattrval(k.attrs, "proto") == nil){ + wc <-= (0, "key without proto"); + break; + } + allkeys = addkey(allkeys, k); + wc <-= (len data, nil); + "delkey" => + attrs := parseline(s); + for(al := attrs; al != nil; al = tl al){ + a := hd al; + if(a.name[0] == '!' && (a.val != nil || a.tag != Aquery)){ + wc <-= (0, "cannot specify values for private fields"); + break X; + } + } + if(delkey(allkeys, attrs) == 0) + wc <-= (0, "no matching keys"); + else + wc <-= (len data, nil); + "debug" => + wc <-= (len data, nil); + * => + wc <-= (0, "unknown ctl request"); + } + + (nil, nbytes, fid, rc) := <-files.rpc.read => + if(rc == nil) + break; + r := findfid(fid); + if(r == nil){ + rc <-= (nil, "unknown request"); + break; + } + alt{ + r.read <-= (nbytes, rc) => + ; + * => + rc <-= (nil, "concurrent rpc read not allowed"); + } + (nil, data, fid, wc) := <-files.rpc.write => + if(wc == nil){ + cleanfid(fid); + break; + } + r := findfid(fid); + if(r == nil){ + r = ref Fid(fid, 0, nil, nil, chan[1] of (array of byte, Rwrite), chan[1] of (int, Rread), 0, nil); + spawn request(r, pidc, donec); + r.pid = <-pidc; + rlist = r :: rlist; + } + # this non-blocking write avoids a potential deadlock situation that + # can happen when a proto module calls findkey at the same time + # a client tries to write to the rpc file. this might not be the correct fix! + alt{ + r.write <-= (data, wc) => + ; + * => + wc <-= (-1, "concurrent rpc write not allowed"); + } + + (off, nbytes, nil, rc) := <-files.proto.read => + if(rc == nil) + break; + rc <-= reads("pass\np9any\n", off, nbytes); # TO DO + (nil, nil, nil, wc) := <-files.proto.write => + if(wc != nil) + wc <-= (0, "illegal operation"); + + (nil, nil, fid, rc) := <-files.needkey.read => + if(rc == nil) + break; + if(needfid >= 0 && fid != needfid){ + rc <-= (nil, "file in use"); + break; + } + needfid = fid; + if(needy != nil){ + (tag, attr, kc) := hd needy; + needy = tl needy; + needed = (tag, attr, kc) :: needed; + rc <-= (sys->aprint("needkey tag=%ud %s", tag, attrtext(attr)), nil); + break; + } + if(needread != nil){ + rc <-= (nil, "already reading"); + break; + } + needread = rc; + (nil, data, fid, wc) := <-files.needkey.write => + if(wc == nil){ + if(needfid == fid){ + needfid = -1; # TO DO? give needkey errors back to request + needread = nil; + } + break; + } + if(needfid >= 0 && fid != needfid){ + wc <-= (0, "file in use"); + break; + } + needfid = fid; + tagline := parseline(string data); + if(len tagline != 1 || (t := lookattrval(tagline, "tag")) == nil){ + wc <-= (0, "no tag"); + break; + } + tag := int t; + nl: list of (int, list of ref Attr, chan of (ref Key, string)); + found := 0; + for(l := needed; l != nil; l = tl l){ + (ntag, attrs, kc) := hd l; + if(tag == ntag){ + found = 1; + k := findkey(allkeys, attrs); + if(k != nil) + kc <-= (k, nil); + else + kc <-= (nil, "needkey "+attrtext(attrs)); + while((l = tl l) != nil) + nl = hd l :: nl; + break; + } + nl = hd l :: nl; + } + if(found) + wc <-= (len data, nil); + else + wc <-= (0, "tag not found"); + + (attrs, required, kc) := <-keymanc => + # look for key and reply + k := findkey(allkeys, attrs); + if(k != nil){ + kc <-= (k, nil); + break; + }else if(!required || needfid == -1){ + kc <-= (nil, "needkey "+attrtext(attrs)); + break; + } + # query surrounding environment using needkey + if(needread != nil){ + needed = (needtag, attrs, kc) :: needed; + needread <-= (sys->aprint("needkey tag=%ud %s", needtag, attrtext(attrs)), nil); + needread = nil; + needtag++; + }else + needy = (needtag++, attrs, kc) :: needy; + } +} + +findfid(fid: int): ref Fid +{ + for(rl := rlist; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid == fid) + return r; + } + return nil; +} + +cleanfid(fid: int) +{ + rl := rlist; + rlist = nil; + for(; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid != fid) + rlist = r :: rlist; + else if(r.pid) + kill(r.pid); + } +} + +kill(pid: int) +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +privacy() +{ + fd := sys->open("#p/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "private") < 0) + sys->fprint(sys->fildes(2), "factotum: warning: unable to make memory private: %r\n"); +} + +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); +} + +Ogok, Ostart, Oread, Owrite, Oauthinfo, Oattr: con iota; + +ops := array[] of { + (Ostart, "start"), + (Oread, "read"), + (Owrite, "write"), + (Oauthinfo, "authinfo"), + (Oattr, "attr"), +}; + +request(r: ref Fid, pidc: chan of int, donec: chan of ref Fid) +{ + pidc <-= sys->pctl(0, nil); + rpc := rio(r); + while(rpc != nil){ + if(rpc.cmd == Ostart){ + (proto, attrs, e) := startproto(string rpc.arg); + if(e != nil){ + reply(rpc, "error "+e); + rpc = rio(r); + continue; + } + r.attrs = attrs; # saved for attr request + ok(rpc); + io := ref IO(r, nil); + { + err := proto->interaction(attrs, io); + if(debug && err != nil) + sys->fprint(sys->fildes(2), "factotum: failure: %s\n", err); + if(r.err == nil) + r.err = err; + r.done = 1; + }exception ex{ + "*" => + r.done = 0; + r.err = "exception "+ex; + } + if(r.err != nil) + io.error(r.err); + rpc = finish(r); + r.attrs = nil; + r.err = nil; + r.done = 0; + r.ai = nil; + }else + reply(rpc, "no current protocol"); + } + flushreq(r, donec); +} + +startproto(request: string): (Authproto, list of ref Attr, string) +{ + attrs := parseline(request); + if(Debug) + sys->print("-> %s <-\n", attrtext(attrs)); + p := lookattrval(attrs, "proto"); + if(p == nil) + return (nil, nil, "did not specify protocol"); + if(Debug) + sys->print("proto=%s\n", p); + if(any(p, "./")) # avoid unpleasantness + return (nil, nil, "illegal protocol: "+p); + proto := load Authproto "/dis/auth/proto/"+p+".dis"; + if(proto == nil) + return (nil, nil, sys->sprint("protocol %s: %r", p)); + if(Debug) + sys->print("start %s\n", p); + e: string; + { + e = proto->init(authio); + }exception ex{ + "*" => + e = "exception "+ex; + } + if(e != nil) + return (nil, nil, e); + return (proto, attrs, nil); +} + +finish(r: ref Fid): ref Rpc +{ + while((rpc := rio(r)) != nil) + case rpc.cmd { + Owrite => + phase(rpc, "protocol phase error"); + Oread => + if(r.err != nil) + reply(rpc, "error "+r.err); + else + done(rpc, r.ai); + Oauthinfo => + if(r.done){ + if(r.ai == nil) + reply(rpc, "error no authinfo available"); + else{ + a := packai(r.ai); + if(rpc.nbytes-3 < len a) + reply(rpc, sys->sprint("toosmall %d", len a + 3)); + else + okdata(rpc, a); + } + }else + reply(rpc, "error authentication unfinished"); + Ostart => + return rpc; + * => + reply(rpc, "error unexpected request"); + } + return nil; +} + +flushreq(r: ref Fid, donec: chan of ref Fid) +{ + for(;;) alt{ + donec <-= r => + exit; + (nil, wc) := <-r.write => + wc <-= (0, "write rpc protocol error"); + (nil, rc) := <-r.read => + rc <-= (nil, "read rpc protocol error"); + } +} + +rio(r: ref Fid): ref Rpc +{ + req: array of byte; + for(;;) alt{ + (data, wc) := <-r.write => + if(req != nil){ + wc <-= (0, "rpc pending; read to clear"); + break; + } + req = data; + wc <-= (len data, nil); + + (nbytes, rc) := <-r.read => + if(req == nil){ + rc <-= (nil, "no rpc pending"); + break; + } + (cmd, arg) := op(req, ops); + req = nil; + rpc := ref Rpc(r, cmd, arg, nbytes, rc); + case cmd { + Ogok => + reply(rpc, "error unknown rpc"); + break; + Oattr => + if(r.attrs == nil) + reply(rpc, "error no attributes"); + else + reply(rpc, "ok "+attrtext(r.attrs)); + break; + * => + return rpc; + } + } +} + +ok(rpc: ref Rpc) +{ + reply(rpc, "ok"); +} + +okdata(rpc: ref Rpc, a: array of byte) +{ + b := array[len a + 3] of byte; + b[0] = byte 'o'; + b[1] = byte 'k'; + b[2] = byte ' '; + b[3:] = a; + rpc.rc <-= (b, nil); +} + +done(rpc: ref Rpc, ai: ref Authinfo) +{ + rpc.r.ai = ai; + rpc.r.done = 1; + if(ai != nil) + reply(rpc, "done haveai"); + else + reply(rpc, "done"); +} + +phase(rpc: ref Rpc, s: string) +{ + reply(rpc, "phase "+s); +} + +needkey(rpc: ref Rpc, attrs: list of ref Attr) +{ + reply(rpc, "needkey "+attrtext(attrs)); +} + +reply(rpc: ref Rpc, s: string) +{ + rpc.rc <-= reads(s, 0, rpc.nbytes); +} + +puta(a: array of byte, n: int, v: array of byte): int +{ + if(n < 0) + return -1; + c := len v; + if(n+2+c > len a) + return -1; + a[n++] = byte c; + a[n++] = byte (c>>8); + a[n:] = v; + return n + len v; +} + +packai(ai: ref Authinfo): array of byte +{ + a := array[1024] of byte; + i := puta(a, 0, array of byte ai.cuid); + i = puta(a, i, array of byte ai.suid); + i = puta(a, i, array of byte ai.cap); + i = puta(a, i, ai.secret); + if(i < 0) + return nil; + return a[0:i]; +} + +op(a: array of byte, ops: array of (int, string)): (int, array of byte) +{ + arg: array of byte; + for(i := 0; i < len a; i++) + if(a[i] == byte ' '){ + if(i+1 < len a) + arg = a[i+1:]; + break; + } + s := string a[0:i]; + for(i = 0; i < len ops; i++){ + (cmd, name) := ops[i]; + if(s == name) + return (cmd, arg); + } + return (Ogok, arg); +} + +parseline(s: string): list of ref Attr +{ + fld := str->unquoted(s); + rfld := fld; + for(fld = nil; rfld != nil; rfld = tl rfld) + fld = (hd rfld) :: fld; + attrs: list of ref Attr; + for(; fld != nil; fld = tl fld){ + n := hd fld; + a := ""; + tag := Aattr; + for(i:=0; i<len n; i++) + if(n[i] == '='){ + a = n[i+1:]; + n = n[0:i]; + tag = Aval; + } + if(len n == 0) + continue; + if(tag == Aattr && len n > 1 && n[len n-1] == '?'){ + tag = Aquery; + n = n[0:len n-1]; + } + attrs = ref Attr(tag, n, a) :: attrs; + } + return attrs; +} + +Attr.text(a: self ref Attr): string +{ + case a.tag { + Aattr => + return a.name; + Aval => + return a.name+"="+a.val; + Aquery => + return a.name+"?"; + * => + return "??"; + } +} + +attrtext(attrs: list of ref Attr): string +{ + s := ""; + sp := 0; + for(; attrs != nil; attrs = tl attrs){ + if(sp) + s[len s] = ' '; + sp = 1; + s += (hd attrs).text(); + } + return s; +} + +lookattr(attrs: list of ref Attr, n: string): ref Attr +{ + for(; attrs != nil; attrs = tl attrs) + if((a := hd attrs).tag != Aquery && a.name == n) + return a; + return nil; +} + +lookattrval(attrs: list of ref Attr, n: string): string +{ + if((a := lookattr(attrs, n)) != nil) + return a.val; + return nil; +} + +anyattr(attrs: list of ref Attr, n: string): ref Attr +{ + for(; attrs != nil; attrs = tl attrs) + if((a := hd attrs).name == n) + return a; + return nil; +} + +reverse[T](l: list of T): list of T +{ + r: list of T; + for(; l != nil; l = tl l) + r = hd l :: r; + return r; +} + +setattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr +{ + # new attributes + nl: list of ref Attr; + for(rl := rv; rl != nil; rl = tl rl) + if(anyattr(lv, (hd rl).name) == nil) + nl = ref(*hd rl) :: nl; + + # new values + for(; lv != nil; lv = tl lv){ + a := lookattr(rv, (hd lv).name); # won't take queries + if(a != nil) + nl = ref *a :: nl; + } + + return reverse(nl); +} + +delattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr +{ + nl: list of ref Attr; + for(; lv != nil; lv = tl lv) + if(anyattr(rv, (hd lv).name) == nil) + nl = hd lv :: nl; + return reverse(nl); +} + +matchattr(attrs: list of ref Attr, pat: ref Attr): int +{ + return (b := lookattr(attrs, pat.name)) != nil && (pat.tag == Aquery || b.val == pat.val); +} + +matchattrs(pub: list of ref Attr, secret: list of ref Attr, pats: list of ref Attr): int +{ + for(pl := pats; pl != nil; pl = tl pl) + if(!matchattr(pub, hd pl) && !matchattr(secret, hd pl)) + return 0; + return 1; +} + +sortattrs(attrs: list of ref Attr): list of ref Attr +{ + a := array[len attrs] of ref Attr; + i := 0; + for(l := attrs; l != nil; l = tl l) + a[i++] = hd l; + shellsort(a); + for(i = 0; i < len a; i++) + l = a[i] :: l; + return l; +} + +# sort into decreasing order (we'll reverse the list) +shellsort(a: array of ref Attr) +{ + n := len a; + for(gap := n; gap > 0; ) { + gap /= 2; + max := n-gap; + ex: int; + do{ + ex = 0; + for(i := 0; i < max; i++) { + j := i+gap; + if(a[i].name > a[j].name || a[i].name == nil) { + t := a[i]; a[i] = a[j]; a[j] = t; + ex = 1; + } + } + }while(ex); + } +} + +findkey(keys: array of ref Key, attrs: list of ref Attr): ref Key +{ + if(Debug) + sys->print("findkey %q\n", attrtext(attrs)); + for(i := 0; i < len keys; i++) + if((k := keys[i]) != nil && matchattrs(k.attrs, k.secrets, attrs)) + return k; + return nil; +} + +delkey(keys: array of ref Key, attrs: list of ref Attr): int +{ + nk := 0; + for(i := 0; i < len keys; i++) + if((k := keys[i]) != nil) + if(matchattrs(k.attrs, k.secrets, attrs)){ + nk++; + keys[i] = nil; + } + return nk; +} + +Key.mk(attrs: list of ref Attr): ref Key +{ + k := ref Key; + for(; attrs != nil; attrs = tl attrs){ + a := hd attrs; + if(a.name != nil){ + if(a.name[0] == '!') + k.secrets = a :: k.secrets; + else + k.attrs = a :: k.attrs; + } + } + if(k.attrs != nil || k.secrets != nil) + return k; + return nil; +} + +addkey(keys: array of ref Key, k: ref Key): array of ref Key +{ + for(i := 0; i < len keys; i++) + if(keys[i] == nil){ + keys[i] = k; + return keys; + } + n := array[len keys+1] of ref Key; + n[0:] = keys; + n[len keys] = k; + return n; +} + +Key.text(k: self ref Key): string +{ + s := attrtext(k.attrs); + if(s != nil && k.secrets != nil) + s[len s] = ' '; + return s + attrtext(k.secrets); +} + +Key.safetext(k: self ref Key): string +{ + s := attrtext(sortattrs(k.attrs)); + sp := s != nil; + for(sl := k.secrets; sl != nil; sl = tl sl){ + if(sp) + s[len s] = ' '; + s += sys->sprint("%s?", (hd sl).name); + } + return s; +} + +any(s: string, t: string): int +{ + for(i := 0; i < len s; i++) + for(j := 0; j < len t; j++) + if(s[i] == t[j]) + return 1; + return 0; +} + +IO.findkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string) +{ + ea := parseline(extra); + for(; ea != nil; ea = tl ea) + attrs = hd ea :: attrs; + kc := chan of (ref Key, string); + keymanc <-= (attrs, 1, kc); # TO DO: 1 => 0 for not needed + return <-kc; +} + +IO.needkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string) +{ + ea := parseline(extra); + for(; ea != nil; ea = tl ea) + attrs = hd ea :: attrs; + kc := chan of (ref Key, string); + keymanc <-= (attrs, 1, kc); + return <-kc; +} + +IO.read(io: self ref IO): array of byte +{ + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + * => + phase(rpc, "protocol phase error"); + Oauthinfo => + reply(rpc, "error authentication unfinished"); + Owrite => + io.rpc = rpc; + if(rpc.arg == nil) + rpc.arg = array[0] of byte; + return rpc.arg; + } + exit; +} + +IO.readn(io: self ref IO, n: int): array of byte +{ + while((buf := io.read()) != nil && len buf < n) + io.toosmall(n); + return buf; +} + +IO.write(io: self ref IO, buf: array of byte, n: int): int +{ + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + Oread => + if(rpc.nbytes-3 >= n){ + okdata(rpc, buf[0:n]); + return n; + } + io.toosmall(n+3); + Oauthinfo => + reply(rpc, "error authentication unfinished"); + * => + phase(rpc, "protocol phase error"); + } + exit; +} + +IO.ok(io: self ref IO) +{ + if(io.rpc != nil){ + reply(io.rpc, "ok"); + io.rpc = nil; + } +} + +IO.toosmall(io: self ref IO, n: int) +{ + if(io.rpc != nil){ + reply(io.rpc, sys->sprint("toosmall %d", n)); + io.rpc = nil; + } +} + +IO.error(io: self ref IO, s: string) +{ + if(io.rpc != nil){ + io.rpc.rc <-= (nil, "error "+s); + io.rpc = nil; + } +} + +IO.done(io: self ref IO, ai: ref Authinfo) +{ + io.f.ai = ai; + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + Oread or Owrite => + done(rpc, ai); + return; + * => + phase(rpc, "protocol phase error"); + } +} + +memrandom(a: array of byte, n: int) +{ + if(0){ + # speed up testing + for(i := 0; i < len a; i++) + a[i] = byte i; + return; + } + fd := sys->open("/dev/notquiterandom", Sys->OREAD); + if(fd == nil) + err("can't open /dev/notquiterandom"); + if(sys->read(fd, a, n) != n) + err("can't read /dev/notquiterandom"); +} + +eqbytes(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} |
