summaryrefslogtreecommitdiff
path: root/appl/cmd/auth/factotum
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/auth/factotum')
-rw-r--r--appl/cmd/auth/factotum/authio.m80
-rw-r--r--appl/cmd/auth/factotum/factotum.b978
-rw-r--r--appl/cmd/auth/factotum/feedkey.b321
-rw-r--r--appl/cmd/auth/factotum/mkfile27
-rw-r--r--appl/cmd/auth/factotum/proto/infauth.b362
-rw-r--r--appl/cmd/auth/factotum/proto/keyreps.b173
-rw-r--r--appl/cmd/auth/factotum/proto/keyreps.m23
-rw-r--r--appl/cmd/auth/factotum/proto/mkfile22
-rw-r--r--appl/cmd/auth/factotum/proto/p9any.b232
-rw-r--r--appl/cmd/auth/factotum/proto/pass.b29
-rw-r--r--appl/cmd/auth/factotum/rpc.b68
11 files changed, 2315 insertions, 0 deletions
diff --git a/appl/cmd/auth/factotum/authio.m b/appl/cmd/auth/factotum/authio.m
new file mode 100644
index 00000000..7c0565b5
--- /dev/null
+++ b/appl/cmd/auth/factotum/authio.m
@@ -0,0 +1,80 @@
+Authio: module
+{
+
+ Aattr, Aval, Aquery: con iota;
+
+ Attr: adt {
+ tag: int;
+ name: string;
+ val: string;
+
+ text: fn(a: self ref Attr): string;
+ };
+
+ Key: adt {
+ attrs: list of ref Attr;
+ secrets: list of ref Attr;
+ # proto: Authproto;
+
+ mk: fn(attrs: list of ref Attr): ref Key;
+ text: fn(k: self ref Key): string;
+ safetext: fn(k: self ref Key): string;
+ };
+
+ Fid: adt
+ {
+ fid: int;
+ pid: int;
+ err: string;
+ attrs: list of ref Attr;
+ write: chan of (array of byte, Sys->Rwrite);
+ read: chan of (int, Sys->Rread);
+ # proto: Authproto;
+ done: int;
+ ai: ref Authinfo;
+ };
+
+ Rpc: adt {
+ r: ref Fid;
+ cmd: int;
+ arg: array of byte;
+ nbytes: int;
+ rc: chan of (array of byte, string);
+ };
+
+ IO: adt {
+ f: ref Fid;
+ rpc: ref Rpc;
+
+ findkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string);
+ needkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string);
+ read: fn(io: self ref IO): array of byte;
+ readn: fn(io: self ref IO, n: int): array of byte;
+ write: fn(io: self ref IO, buf: array of byte, n: int): int;
+ toosmall: fn(io: self ref IO, n: int);
+ error: fn(io: self ref IO, s: string);
+ ok: fn(io: self ref IO);
+ done: fn(io: self ref IO, ai: ref Authinfo);
+ };
+
+ # need more ... ?
+ Authinfo: adt {
+ cuid: string; # caller id
+ suid: string; # server id
+ cap: string; # capability (only valid on server side)
+ secret: array of byte;
+ };
+
+ memrandom: fn(a: array of byte, n: int);
+ eqbytes: fn(a, b: array of byte): int;
+ netmkaddr: fn(addr, net, svc: string): string;
+ user: fn(): string;
+ lookattrval: fn(a: list of ref Attr, n: string): string;
+ parseline: fn(s: string): list of ref Attr;
+};
+
+Authproto: module
+{
+ init: fn(f: Authio): string;
+ interaction: fn(attrs: list of ref Authio->Attr, io: ref Authio->IO): string;
+};
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);
+}
diff --git a/appl/cmd/auth/factotum/feedkey.b b/appl/cmd/auth/factotum/feedkey.b
new file mode 100644
index 00000000..606f065a
--- /dev/null
+++ b/appl/cmd/auth/factotum/feedkey.b
@@ -0,0 +1,321 @@
+implement Feedkey;
+
+#
+# Copyright © 2004 Vita Nuova Holdings Limited
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "string.m";
+ str: String;
+
+Feedkey: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+config := array[] of {
+ "frame .f",
+ "button .f.done -command {send cmd done} -text {Done}",
+ "frame .f.key -bg white",
+ "pack .f.key .f.done .f",
+ "update"
+};
+
+Debug: con 0;
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ str = load String String->PATH;
+
+ needfile := "/mnt/factotum/needkey";
+ if(Debug)
+ needfile = "/dev/null";
+
+ needs := chan of list of ref Attr;
+ acks := chan of int;
+
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2});
+
+ fd := sys->open(needfile, Sys->ORDWR);
+ if(fd == nil)
+ err(sys->sprint("can't open %s: %r", needfile));
+ spawn needy(fd, needs, acks);
+ fd = nil;
+
+ ctlfile := "/mnt/factotum/ctl";
+ keyfd := sys->open(ctlfile, Sys->ORDWR);
+ if(keyfd == nil)
+ err(sys->sprint("can't open %s: %r", ctlfile));
+
+ tkclient->init();
+
+ spawn feedkey(ctxt, keyfd, needs, acks);
+}
+
+feedkey(ctxt: ref Draw->Context, keyfd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int)
+{
+ (top, tkctl) := tkclient->toplevel(ctxt, nil, "Need key", Tkclient->Appl);
+
+ cmd := chan of string;
+ tk->namechan(top, cmd, "cmd");
+
+ for(i := 0; i < len config; i++)
+ tkcmd(top, config[i]);
+ tkclient->startinput(top, "ptr" :: nil);
+ tkclient->onscreen(top, nil);
+ if(!Debug)
+ tkclient->wmctl(top, "task");
+
+ attrs: list of ref Attr;
+ for(;;) alt{
+ s :=<-tkctl or
+ s = <-top.ctxt.ctl or
+ s = <-top.wreq =>
+ tkclient->wmctl(top, s);
+ p := <-top.ctxt.ptr =>
+ tk->pointer(top, *p);
+ c := <-top.ctxt.kbd =>
+ tk->keyboard(top, c);
+
+ s := <-cmd =>
+ case s {
+ "done" =>
+ result := extract(top, ".f.key", attrs);
+ if(Debug)
+ sys->print("result: %s\n", attrtext(result));
+ if(sys->fprint(keyfd, "key %s", attrtext(result)) < 0)
+ sys->fprint(sys->fildes(2), "feedkey: can't install key %q: %r\n", attrtext(result));
+ acks <-= 0;
+ tkclient->wmctl(top, "task");
+ tk->cmd(top, "pack forget .f.key");
+ * =>
+ sys->fprint(sys->fildes(2), "feedkey: odd command: %q\n", s);
+ }
+
+ attrs = <-needs =>
+ if(attrs == nil)
+ exit;
+ tkclient->startinput(top, "kbd" :: nil);
+ tkcmd(top, "destroy .f.key");
+ tkcmd(top, "frame .f.key -bg white");
+ populate(top, ".f.key", attrs);
+ tkcmd(top, "pack forget .f.done");
+ tkcmd(top, "pack .f.key .f.done .f");
+ tkcmd(top, "update");
+ tkclient->wmctl(top, "unhide");
+ }
+}
+
+err(s: string)
+{
+ sys->fprint(sys->fildes(2), "feedkey: %s\n", s);
+ raise "fail:error";
+}
+
+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];
+}
+
+tkcmd(top: ref Tk->Toplevel, cmd: string): string
+{
+ if(0)
+ sys->print("tk: %q\n", cmd);
+ r := tk->cmd(top, cmd);
+ if(r != nil && r[0] == '!')
+ sys->fprint(sys->fildes(2), "feedkey: tk: %q on %q\n", r, cmd);
+ return r;
+}
+
+populate(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr)
+{
+ c := 0;
+ for(al := attrs; al != nil; al = tl al){
+ a := hd al;
+ if(a.name == nil)
+ tkcmd(top, sys->sprint("entry %s.n%d -bg yellow", tag, c));
+ else
+ tkcmd(top, sys->sprint("label %s.n%d -bg white -text '%s", tag, c, a.name));
+ tkcmd(top, sys->sprint("label %s.e%d -bg white -text ' = ", tag, c));
+ case a.tag {
+ Aquery =>
+ show := "";
+ if(a.name != nil && a.name[0] == '!')
+ show = " -show {•}";
+ tkcmd(top, sys->sprint("entry %s.v%d%s -bg yellow", tag, c, show));
+ if(a.val == nil && a.name == "user")
+ a.val = user();
+ tkcmd(top, sys->sprint("%s.v%d insert 0 '%s", tag, c, a.val));
+ tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag));
+ Aval =>
+ if(a.name != nil){
+ val := a.val;
+ if(a.name[0] == '!')
+ val = "..."; # just in case
+ tkcmd(top, sys->sprint("label %s.v%d -bg white -text %s", tag, c, val));
+ }else
+ tkcmd(top, sys->sprint("entry %s.v%d -bg yellow", tag, c));
+ tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag));
+ Aattr =>
+ tkcmd(top, sys->sprint("grid %s.n%d x x -in %s -sticky w -pady 1", tag, c, tag));
+ }
+ c++;
+ }
+}
+
+extract(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr): list of ref Attr
+{
+ c := 0;
+ nl: list of ref Attr;
+ for(al := attrs; al != nil; al = tl al){
+ a := ref *hd al;
+ if(a.tag == Aquery){
+ a.val = tkcmd(top, sys->sprint("%s.v%d get", tag, c));
+ if(a.name == nil)
+ a.name = tk->cmd(top, sys->sprint("%s.n%d get", tag, c)); # name might start with `!'
+ if(a.name != nil){
+ a.tag = Aval;
+ nl = a :: nl;
+ }
+ }else
+ nl = a :: nl;
+ c++;
+ }
+ return nl;
+}
+
+reverse[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+needy(fd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int)
+{
+ if(Debug){
+ for(;;){
+ needs <-= parseline("proto=pass user? server=fred.com service=ftp confirm !password?");
+ <-acks;
+ }
+ }
+
+ buf := array[512] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0){
+ s := string buf[0:n];
+ for(i := 0; i < len s; i++)
+ if(s[i] == ' ')
+ break;
+ if(i >= len s)
+ continue;
+ attrs := parseline(s[i+1:]);
+ nl: list of ref Attr;
+ tag: ref Attr;
+ for(; attrs != nil; attrs = tl attrs){
+ a := hd attrs;
+ if(a.name == "tag")
+ tag = a;
+ else
+ nl = a :: nl;
+ }
+ if(nl == nil)
+ continue;
+ attrs = reverse(ref Attr(Aquery, nil, nil) :: ref Attr(Aquery, nil, nil) :: nl); # add a few blank
+ if(attrs != nil && tag != nil && tag.val != nil){
+ needs <-= attrs;
+ <-acks;
+ sys->fprint(fd, "tag=%d", int tag.val);
+ }
+ }
+ if(n < 0)
+ sys->fprint(sys->fildes(2), "feedkey: error reading needkey: %r\n");
+ needs <-= nil;
+}
+
+# need a library module
+
+Aattr, Aval, Aquery: con iota;
+
+Attr: adt {
+ tag: int;
+ name: string;
+ val: string;
+
+ text: fn(a: self ref Attr): string;
+};
+
+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 sys->sprint("%q=%q", 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;
+}
diff --git a/appl/cmd/auth/factotum/mkfile b/appl/cmd/auth/factotum/mkfile
new file mode 100644
index 00000000..1979a14c
--- /dev/null
+++ b/appl/cmd/auth/factotum/mkfile
@@ -0,0 +1,27 @@
+<../../../../mkconfig
+
+DIRS=\
+ proto\
+
+TARG=\
+ factotum.dis\
+ feedkey.dis\
+ rpc.dis\
+
+SYSMODULES=\
+ arg.m\
+ keyring.m\
+ security.m\
+ rand.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ string.m\
+
+MODULES=\
+ authio.m\
+
+DISBIN=$ROOT/dis/auth
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
diff --git a/appl/cmd/auth/factotum/proto/infauth.b b/appl/cmd/auth/factotum/proto/infauth.b
new file mode 100644
index 00000000..244979bc
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/infauth.b
@@ -0,0 +1,362 @@
+implement Authproto;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "keyring.m";
+ keyring: Keyring;
+ IPint: import keyring;
+ SK, PK, Certificate, DigestState: import Keyring;
+include "security.m";
+include "bufio.m";
+include "sexprs.m";
+ sexprs: Sexprs;
+ Sexp: import sexprs;
+include "spki.m";
+ spki: SPKI;
+include "daytime.m";
+ daytime: Daytime;
+include "keyreps.m";
+ keyreps: Keyreps;
+ Keyrep: import keyreps;
+include "../authio.m";
+ authio: Authio;
+ Aattr, Aval, Aquery: import Authio;
+ Attr, IO, Key, Authinfo: import authio;
+
+# at end of authentication, sign a hash of the authenticated username and
+# a secret known only to factotum. that certificate can act as
+# a later proof that this factotum has authenticated that user,
+# and hence factotum will disclose certificates that allow disclosure
+# only to that username.
+
+Debug: con 0;
+
+Maxmsg: con 4000;
+
+Error0, Error1: exception(string);
+
+init(f: Authio): string
+{
+ authio = f;
+ sys = load Sys Sys->PATH;
+ spki = load SPKI SPKI->PATH;
+ spki->init();
+ sexprs = load Sexprs Sexprs->PATH;
+ sexprs->init();
+ keyring = load Keyring Keyring->PATH;
+ daytime = load Daytime Daytime->PATH;
+ keyreps = load Keyreps Keyreps->PATH;
+ keyreps->init();
+ return nil;
+}
+
+interaction(attrs: list of ref Attr, io: ref IO): string
+{
+ ai: ref Authinfo;
+ (key, err) := io.findkey(attrs, "proto=infauth");
+ if(key == nil)
+ return err;
+ info: ref Keyring->Authinfo;
+ (info, err) = keytoauthinfo(key);
+ if(info == nil)
+ return err;
+ anysigner := int authio->lookattrval(key.attrs, "anysigner");
+ rattrs: list of ref Sexp;
+ {
+ # send auth protocol version number
+ sendmsg(io, array of byte "1");
+
+ # get auth protocol version number
+ if(int string getmsg(io) != 1)
+ raise Error0("incompatible authentication protocol");
+
+ # generate alpha**r0
+ p := info.p;
+ low := p.shr(p.bits()/4);
+ r0 := rand(low, p, Random->NotQuiteRandom);
+ αr0 := info.alpha.expmod(r0, p);
+ # trim(αr0); the IPint library should do this for us, i think.
+
+ # send alpha**r0 mod p, mycert, and mypk
+ sendmsg(io, array of byte αr0.iptob64());
+ sendmsg(io, array of byte keyring->certtostr(info.cert));
+ sendmsg(io, array of byte keyring->pktostr(info.mypk));
+
+ # get alpha**r1 mod p, hiscert, hispk
+ αr1 := IPint.b64toip(string getmsg(io));
+
+ # trying a fast one
+ if(p.cmp(αr1) <= 0)
+ raise Error0("implausible parameter value");
+
+ # if alpha**r1 == alpha**r0, someone may be trying a replay
+ if(αr0.eq(αr1))
+ raise Error0("possible replay attack");
+
+ hiscert := keyring->strtocert(string getmsg(io));
+ if(hiscert == nil && !anysigner)
+ raise Error0(sys->sprint("bad certificate: %r"));
+
+ buf := getmsg(io);
+ hispk := keyring->strtopk(string buf);
+ if(!anysigner){
+ # verify their public key
+ if(verify(info.spk, hiscert, buf) == 0)
+ raise Error0("pk doesn't match certificate"); # likely the signers don't match.
+
+ # check expiration date - in seconds of epoch
+ if(hiscert.exp != 0 && hiscert.exp <= now())
+ raise Error0("certificate expired");
+ }
+ buf = nil;
+
+ # sign alpha**r0 and alpha**r1 and send
+ αcert := sign(info.mysk, "sha", 0, array of byte (αr0.iptob64() + αr1.iptob64()));
+ sendmsg(io, array of byte keyring->certtostr(αcert));
+
+ # get signature of alpha**r1 and alpha**r0 and verify
+ αcert = keyring->strtocert(string getmsg(io));
+ if(αcert == nil)
+ raise Error0("alpha**r1 doesn't match certificate");
+
+ if(verify(hispk, αcert, array of byte (αr1.iptob64() + αr0.iptob64())) == 0)
+ raise Error0(sys->sprint("bad certificate: %r"));
+
+ ai = ref Authinfo;
+ # we are now authenticated and have a common secret, alpha**(r0*r1)
+ if(!anysigner)
+ rattrs = sl(ss("signer") :: principal(info.spk) :: nil) :: rattrs;
+ rattrs = sl(ss("remote-pk") :: principal(hispk) :: nil) :: rattrs;
+ rattrs = sl(ss("local-pk") :: principal(info.mypk) :: nil) :: rattrs;
+ rattrs = sl(ss("secret") :: sb(αr1.expmod(r0, p).iptobytes()) :: nil) :: rattrs;
+ ai.suid = hispk.owner;
+ ai.cuid = info.mypk.owner;
+ sendmsg(io, array of byte "OK");
+ }exception e{
+ Error0 =>
+ err = e;
+ senderr(io, e);
+ break;
+ Error1 =>
+ senderr(io, "missing your authentication data");
+ x: string = e;
+ return "remote: "+x;
+ }
+
+ {
+ while(string getmsg(io) != "OK")
+ ;
+ }exception e{
+ Error0 =>
+ return e;
+ Error1 =>
+ x: string = e;
+ return "remote: "+x;
+ }
+ if(err != nil)
+ return err;
+
+ return negotiatecrypto(io, key, ai, rattrs);
+}
+
+negotiatecrypto(io: ref IO, key: ref Key, ai: ref Authinfo, attrs: list of ref Sexp): string
+{
+ role := authio->lookattrval(key.attrs, "role");
+ alg: string;
+ {
+ if(role == "client"){
+ alg = authio->lookattrval(key.attrs, "alg");
+ if(alg == nil)
+ alg = "md5/rc4_256";
+ sendmsg(io, array of byte alg);
+ }else if(role == "server"){
+ alg = string getmsg(io);
+ if(!algcompatible(alg, sys->tokenize(authio->lookattrval(key.attrs, "algs"), " ").t1))
+ raise Error0("unsupported client algorithm");
+ }
+ }exception e{
+ Error0 or
+ Error1 =>
+ return e;
+ }
+
+ if(alg != nil)
+ attrs = sl(ss("alg") :: ss(alg) :: nil) :: attrs;
+ ai.secret = sl(attrs).pack();
+
+ io.done(ai);
+ return nil;
+}
+
+algcompatible(nil: string, nil: list of string): int
+{
+ return 1; # XXX
+}
+
+principal(pk: ref Keyring->PK): ref Sexp
+{
+ return spki->(Keyrep.pk(pk).mkkey()).sexp();
+}
+
+ipint(i: int): ref IPint
+{
+ return IPint.inttoip(i);
+}
+
+rand(p, q: ref IPint, nil: int): ref IPint
+{
+ if(p.cmp(q) > 0)
+ (p, q) = (q, p);
+ diff := q.sub(p);
+ q = nil;
+ if(diff.cmp(ipint(2)) < 0){
+ sys->print("rand range must be at least 2");
+ return IPint.inttoip(0);
+ }
+ l := diff.bits();
+ T := ipint(1).shl(l);
+ l = ((l + 7) / 8) * 8;
+ slop := T.div(diff).t1;
+ r: ref IPint;
+ do{
+ r = IPint.random(0, l);
+ }while(r.cmp(slop) < 0);
+ r = r.div(diff).t1.add(p);
+ return r;
+}
+
+now(): int
+{
+ return daytime->now();
+}
+
+Hashfn: type ref fn(a: array of byte, alen: int, digest: array of byte, state: ref DigestState): ref DigestState;
+
+hashalg(ha: string): Hashfn
+{
+ case ha {
+ "sha" or
+ "sha1" =>
+ return keyring->sha1;
+ "md4" =>
+ return keyring->md4;
+ "md5" =>
+ return keyring->md5;
+ }
+ return nil;
+}
+
+sign(sk: ref SK, ha: string, exp: int, buf: array of byte): ref Certificate
+{
+ state := hashalg(ha)(buf, len buf, nil, nil);
+ return keyring->sign(sk, exp, state, ha);
+}
+
+verify(pk: ref PK, cert: ref Certificate, buf: array of byte): int
+{
+ state := hashalg(cert.ha)(buf, len buf, nil, nil);
+ return keyring->verify(pk, cert, state);
+}
+
+getmsg(io: ref IO): array of byte raises (Error0, Error1)
+{
+ while((buf := io.read()) == nil || (n := len buf) < 5)
+ io.toosmall(5);
+ if(len buf != 5)
+ raise Error0("io error: (impossible?) msg length " + string n);
+ h := string buf;
+ if(h[0] == '!')
+ m := int h[1:];
+ else
+ m = int h;
+ while((buf = io.read()) == nil || (n = len buf) < m)
+ io.toosmall(m);
+ if(len buf != m)
+ raise Error0("io error: (impossible?) msg length " + string m);
+ if(h[0] == '!'){
+sys->print("got remote error: %s, len %d\n", string buf, len string buf);
+ raise Error1(string buf);
+ }
+ return buf;
+}
+
+sendmsg(io: ref IO, buf: array of byte)
+{
+ h := sys->aprint("%4.4d\n", len buf);
+ io.write(h, len h);
+ io.write(buf, len buf);
+}
+
+senderr(io: ref IO, e: string)
+{
+ buf := array of byte e;
+ h := sys->aprint("!%3.3d\n", len buf);
+ io.write(h, len h);
+ io.write(buf, len buf);
+}
+
+keytoauthinfo(key:ref Key): (ref Keyring->Authinfo, string)
+{
+ if((s := authio->lookattrval(key.secrets, "!authinfo")) == nil){
+ # XXX could look up authinfo by hash at this point
+ return (nil, "no authinfo attribute");
+ }
+
+ return strtoauthinfo(s);
+}
+
+strtoauthinfo(s: string): (ref Keyring->Authinfo, string)
+{
+ (se, err, nil) := Sexp.parse(s);
+ if(se == nil)
+ return (nil, err);
+ els := se.els();
+ if(len els != 5)
+ return (nil, "bad authinfo contents");
+ ai := ref Keyring->Authinfo;
+ if((ai.spk = keyring->strtopk((hd els).astext())) == nil)
+ return (nil, "bad signer public key");
+ els = tl els;
+ if((ai.cert = keyring->strtocert((hd els).astext())) == nil)
+ return (nil, "bad certificate");
+ els = tl els;
+ if((ai.mysk = keyring->strtosk((hd els).astext())) == nil)
+ return (nil, "bad secret/public key");
+ if((ai.mypk = keyring->sktopk(ai.mysk)) == nil)
+ return (nil, "cannot make pk from sk");
+ els = tl els;
+ if((ai.alpha = IPint.bytestoip((hd els).asdata())) == nil)
+ return (nil, "bad value for alpha");
+ els = tl els;
+ if((ai.p = IPint.bytestoip((hd els).asdata())) == nil)
+ return (nil, "bad value for p");
+ return (ai, nil);
+}
+
+authinfotostr(ai: ref Keyring->Authinfo): string
+{
+ return (ref Sexp.List(
+ ss(keyring->pktostr(ai.spk)) ::
+ ss(keyring->certtostr(ai.cert)) ::
+ ss(keyring->sktostr(ai.mysk)) ::
+ sb(ai.alpha.iptobytes()) ::
+ sb(ai.p.iptobytes()) ::
+ nil
+ )).b64text();
+}
+
+ss(s: string): ref Sexp.String
+{
+ return ref Sexp.String(s, nil);
+}
+
+sb(d: array of byte): ref Sexp.Binary
+{
+ return ref Sexp.Binary(d, nil);
+}
+
+sl(l: list of ref Sexp): ref Sexp
+{
+ return ref Sexp.List(l);
+}
diff --git a/appl/cmd/auth/factotum/proto/keyreps.b b/appl/cmd/auth/factotum/proto/keyreps.b
new file mode 100644
index 00000000..5fdac2c0
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/keyreps.b
@@ -0,0 +1,173 @@
+implement Keyreps;
+include "sys.m";
+ sys: Sys;
+include "keyring.m";
+ kr: Keyring;
+ IPint: import kr;
+include "sexprs.m";
+include "spki.m";
+include "encoding.m";
+ base64: Encoding;
+include "keyreps.m";
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ base64 = load Encoding Encoding->BASE64PATH;
+}
+
+keyextract(flds: list of string, names: list of (string, int)): list of (string, ref IPint)
+{
+ a := array[len flds] of ref IPint;
+ for(i := 0; i < len a; i++){
+ a[i] = IPint.b64toip(hd flds);
+ flds = tl flds;
+ }
+ rl: list of (string, ref IPint);
+ for(; names != nil; names = tl names){
+ (n, p) := hd names;
+ if(p < len a)
+ rl = (n, a[p]) :: rl;
+ }
+ return revt(rl);
+}
+
+Keyrep.pk(pk: ref Keyring->PK): ref Keyrep.PK
+{
+ s := kr->pktostr(pk);
+ (nf, flds) := sys->tokenize(s, "\n");
+ if((nf -= 2) < 0)
+ return nil;
+ case hd flds {
+ "rsa" =>
+ return ref Keyrep.PK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("e",1), ("n",0)}));
+ "elgamal" or "dsa" =>
+ return ref Keyrep.PK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2)}));
+ * =>
+ return nil;
+ }
+}
+
+Keyrep.sk(pk: ref Keyring->SK): ref Keyrep.SK
+{
+ s := kr->pktostr(pk);
+ (nf, flds) := sys->tokenize(s, "\n");
+ if((nf -= 2) < 0)
+ return nil;
+ case hd flds {
+ "rsa" =>
+ return ref Keyrep.SK(hd flds, hd tl flds,
+ keyextract(tl tl flds,list of {("e",1), ("n",0), ("!dk",2), ("!p",3), ("!q",4), ("!kp",5), ("!kq",6), ("!c2",7)}));
+ "elgamal" or "dsa" =>
+ return ref Keyrep.SK(hd flds, hd tl flds,
+ keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2), ("!secret",3)}));
+ * =>
+ return nil;
+ }
+}
+
+Keyrep.get(k: self ref Keyrep, n: string): ref IPint
+{
+ for(el := k.els; el != nil; el = tl el)
+ if((hd el).t0 == n)
+ return (hd el).t1;
+ return nil;
+}
+
+Keyrep.getb(k: self ref Keyrep, n: string): array of byte
+{
+ v := k.get(n);
+ if(v == nil)
+ return nil;
+ return pre0(v.iptobebytes());
+}
+
+pre0(a: array of byte): array of byte
+{
+ for(i:=0; i<len a-1; i++)
+ if(a[i] != a[i+1] && (a[i] != byte 0 || (int a[i+1] & 16r80) != 0))
+ break;
+ if(i > 0)
+ a = a[i:];
+ if(len a < 1 || (int a[0] & 16r80) == 0)
+ return a;
+ b := array[len a + 1] of byte;
+ b[0] = byte 0;
+ b[1:] = a;
+ return b;
+}
+
+Keyrep.mkpk(k: self ref Keyrep): (ref Keyring->PK, int)
+{
+ case k.alg {
+ "rsa" =>
+ e := k.get("e");
+ n := k.get("n");
+ return (kr->strtopk(sys->sprint("rsa\n%s\n%s\n%s\n", k.owner, n.iptob64(), e.iptob64())), n.bits());
+ * =>
+ raise "Keyrep: unknown algorithm" + k.alg;
+ }
+}
+
+Keyrep.mksk(k: self ref Keyrep): ref Keyring->SK
+{
+ case k.alg {
+ "rsa" =>
+ e := k.get("e");
+ n := k.get("n");
+ dk := k.get("!dk");
+ p := k.get("!p");
+ q := k.get("!q");
+ kp := k.get("!kp");
+ kq := k.get("!kq");
+ c12 := k.get("!c2");
+ return kr->strtosk(sys->sprint("rsa\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
+ k.owner, n.iptob64(), e.iptob64(), dk.iptob64(), p.iptob64(), q.iptob64(),
+ kp.iptob64(), kq.iptob64(), c12.iptob64()));
+ * =>
+ raise "Keyrep: unknown algorithm";
+ }
+}
+
+Keyrep.eq(k1: self ref Keyrep, k2: ref Keyrep): int
+{
+ # n⁲ but n is small
+ for(l1 := k1.els; l1 != nil; l1 = tl l1){
+ (n, v1) := hd l1;
+ v2 := k2.get(n);
+ if(v2 == nil || !v1.eq(v2))
+ return 0;
+ }
+ for(l2 := k2.els; l2 != nil; l2 = tl l2)
+ if(k1.get((hd l2).t0) == nil)
+ return 0;
+ return 1;
+}
+
+Keyrep.mkkey(kr: self ref Keyrep): ref SPKI->Key
+{
+ k := ref SPKI->Key;
+ (k.pk, k.nbits) = kr.mkpk();
+ k.sk = kr.mksk();
+ return k;
+}
+
+sig2icert(sig: ref SPKI->Signature, signer: string, exp: int): ref Keyring->Certificate
+{
+ if(sig.sig == nil)
+ return nil;
+ s := sys->sprint("%s\n%s\n%s\n%d\n%s\n", "rsa", sig.hash.alg, signer, exp, base64->enc((hd sig.sig).t1));
+#sys->print("alg %s *** %s\n", sig.sa, base64->enc((hd sig.sig).t1));
+ return kr->strtocert(s);
+}
+
+revt[S,T](l: list of (S,T)): list of (S,T)
+{
+ rl: list of (S,T);
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
diff --git a/appl/cmd/auth/factotum/proto/keyreps.m b/appl/cmd/auth/factotum/proto/keyreps.m
new file mode 100644
index 00000000..ddfd7f0d
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/keyreps.m
@@ -0,0 +1,23 @@
+Keyreps: module
+{
+ PATH: con "/dis/lib/spki/keyreps.dis";
+ init: fn();
+ Keyrep: adt {
+ alg: string;
+ owner: string;
+ els: list of (string, ref Keyring->IPint);
+ pick{ # keeps a type distance between public and private keys
+ PK =>
+ SK =>
+ }
+
+ pk: fn(pk: ref Keyring->PK): ref Keyrep.PK;
+ sk: fn(sk: ref Keyring->SK): ref Keyrep.SK;
+ mkpk: fn(k: self ref Keyrep): (ref Keyring->PK, int);
+ mksk: fn(k: self ref Keyrep): ref Keyring->SK;
+ get: fn(k: self ref Keyrep, n: string): ref Keyring->IPint;
+ getb: fn(k: self ref Keyrep, n: string): array of byte;
+ eq: fn(k1: self ref Keyrep, k2: ref Keyrep): int;
+ mkkey: fn(k: self ref Keyrep): ref SPKI->Key;
+ };
+};
diff --git a/appl/cmd/auth/factotum/proto/mkfile b/appl/cmd/auth/factotum/proto/mkfile
new file mode 100644
index 00000000..efdd73da
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/mkfile
@@ -0,0 +1,22 @@
+<../../../../../mkconfig
+
+TARG=\
+ p9any.dis\
+ pass.dis\
+
+SYSMODULES=\
+ factotum.m\
+ keyring.m\
+ security.m\
+ rand.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ string.m\
+
+MODULES=\
+ ../authio.m\
+
+DISBIN=$ROOT/dis/auth/proto
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/auth/factotum/proto/p9any.b b/appl/cmd/auth/factotum/proto/p9any.b
new file mode 100644
index 00000000..1668a701
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/p9any.b
@@ -0,0 +1,232 @@
+implement Authproto;
+
+# currently includes p9sk1
+
+include "sys.m";
+ sys: Sys;
+ Rread, Rwrite: import Sys;
+
+include "draw.m";
+
+include "keyring.m";
+ kr: Keyring;
+
+include "auth9.m";
+ auth9: Auth9;
+ ANAMELEN, AERRLEN, DOMLEN, DESKEYLEN, CHALLEN, SECRETLEN: import Auth9;
+ TICKREQLEN, TICKETLEN, AUTHENTLEN: import Auth9;
+ Ticketreq, Ticket, Authenticator: import auth9;
+
+include "../authio.m";
+ authio: Authio;
+ Aattr, Aval, Aquery: import Authio;
+ Attr, IO, Key, Authinfo: import authio;
+ netmkaddr, eqbytes, memrandom: import authio;
+
+include "encoding.m";
+ base16: Encoding;
+
+Debug: con 0;
+
+# init, addkey, closekey, write, read, close, keyprompt
+
+init(f: Authio): string
+{
+ authio = f;
+ sys = load Sys Sys->PATH;
+ kr = load Keyring Keyring->PATH;
+ auth9 = load Auth9 Auth9->PATH;
+ auth9->init();
+ base16 = load Encoding Encoding->BASE16PATH;
+ return nil;
+}
+
+version := 1;
+
+interaction(attrs: list of ref Attr, io: ref IO): string
+{
+ return p9any(io);
+}
+
+p9any(io: ref IO): string
+{
+ while((buf := io.read()) == nil || (n := len buf) == 0 || buf[n-1] != byte 0)
+ io.toosmall(2048);
+ s := string buf[0:n-1];
+ if(Debug)
+ sys->print("s: %q\n", s);
+ (nil, flds) := sys->tokenize(s, " \t");
+ if(flds != nil && len hd flds >= 2 && (hd flds)[0:2] == "v."){
+ if(hd flds == "v.2"){
+ version = 2;
+ flds = tl flds;
+ if(Debug)
+ sys->print("version 2\n");
+ }else
+ return "p9any: unknown version";
+ }
+ doms: list of string;
+ for(; flds != nil; flds = tl flds){
+ (nf, subf) := sys->tokenize(hd flds, "@");
+ if(nf == 2 && hd subf == "p9sk1")
+ doms = hd tl subf :: doms;
+ }
+ if(doms == nil)
+ return "p9any: unsupported protocol";
+ if(Debug){
+ for(l := doms; l != nil; l = tl l)
+ sys->print("dom: %q\n", hd l);
+ }
+ r := array of byte ("p9sk1 "+hd doms);
+ buf[0:] = r;
+ buf[len r] = byte 0;
+ io.write(buf, len r + 1);
+ if(version == 2){
+ b := io.readn(3);
+ if(b == nil || b[0] != byte 'O' || b[1] != byte 'K' || b[2] != byte 0)
+ return "p9any: AS protocol botch: not OK";
+ if(Debug)
+ sys->print("OK\n");
+ }
+ return p9sk1client(io, hd doms);
+}
+
+#p9sk1:
+# C->S: nonce-C
+# S->C: nonce-S, uid-S, domain-S
+# C->A: nonce-S, uid-S, domain-S, uid-C, factotum-C
+# A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n}
+# C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter}
+# S->C: Kn{nonce-C, counter}
+
+#asserts that uid-S and uid-C share new secret Kn
+#increment the counter to reuse the ticket.
+
+p9sk1client(io: ref IO, udom: string): string
+{
+
+ # C->S: nonce-C
+ cchal := array[CHALLEN] of byte;
+ memrandom(cchal, CHALLEN);
+ if(io.write(cchal, len cchal) != len cchal)
+ return sys->sprint("p9sk1: can't write cchal: %r");
+
+ # S->C: nonce-S, uid-S, domain-S
+ trbuf := io.readn(TICKREQLEN);
+ if(trbuf == nil)
+ return sys->sprint("p9sk1: can't read ticketreq: %r");
+
+ (nil, tr) := Ticketreq.unpack(trbuf);
+ if(tr == nil)
+ return "p9sk1: can't unpack ticket request";
+ if(Debug)
+ sys->print("ticketreq: type=%d authid=%q authdom=%q chal= hostid=%q uid=%q\n",
+ tr.rtype, tr.authid, tr.authdom, tr.hostid, tr.uid);
+
+ (mykey, diag) := io.findkey(nil, sys->sprint("dom=%q proto=p9sk1 user? !password?", udom));
+ if(mykey == nil)
+ return "can't find key: "+diag;
+ ukey: array of byte;
+ if((a := authio->lookattrval(mykey.secrets, "!hex")) != nil){
+ ukey = base16->dec(a);
+ if(len ukey != DESKEYLEN)
+ return "p9sk1: invalid !hex key";
+ }else if((a = authio->lookattrval(mykey.secrets, "!password")) != nil)
+ ukey = auth9->passtokey(a);
+ else
+ return "no !password (or !hex) in key";
+
+ # A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n}
+ user := authio->lookattrval(mykey.attrs, "user");
+ if(user == nil)
+ user = authio->user(); # shouldn't happen
+ tr.rtype = Auth9->AuthTreq;
+ tr.hostid = user;
+ tr.uid = tr.hostid; # not speaking for anyone else
+ (tick, serverbits) := getastickets(tr, ukey);
+ if(tick == nil)
+ return sys->sprint("p9sk1: getasticket failed: %r");
+ if(tick.num != Auth9->AuthTc)
+ return "p9sk1: getasticket: failed: wrong key?";
+ if(Debug)
+ sys->print("ticket: num=%d chal= cuid=%q suid=%q key=\n", tick.num, tick.cuid, tick.suid);
+
+ # C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter}
+ ar := ref Authenticator;
+ ar.num = Auth9->AuthAc;
+ ar.chal = tick.chal;
+ ar.id = 0;
+ obuf := array[TICKETLEN+AUTHENTLEN] of byte;
+ obuf[0:] = serverbits;
+ obuf[TICKETLEN:] = ar.pack(tick.key);
+ if(io.write(obuf, len obuf) != len obuf)
+ return "p9sk1: error writing authenticator: %r";
+
+ # S->C: Kn{nonce-C, counter}
+ sbuf := io.readn(AUTHENTLEN);
+ if(sbuf == nil)
+ return sys->sprint("p9sk1: can't read server's authenticator: %r");
+ (nil, ar) = Authenticator.unpack(sbuf, tick.key);
+ if(ar.num != Auth9->AuthAs || !eqbytes(ar.chal, cchal) || ar.id != 0)
+ return "invalid authenticator from server";
+
+ ai := ref Authinfo(tick.cuid, tick.suid, nil, auth9->des56to64(tick.key));
+ io.done(ai);
+
+ return nil;
+}
+
+getastickets(tr: ref Ticketreq, key: array of byte): (ref Ticket, array of byte)
+{
+ afd := authdial(nil, tr.authdom);
+ if(afd == nil)
+ return (nil, nil);
+ return auth9->_asgetticket(afd, tr, key);
+}
+
+#
+# where to put the following functions?
+#
+
+csgetvalue(netroot: string, keytag: string, keyval: string, needtag: string): string
+{
+ cs := "/net/cs";
+ if(netroot != nil)
+ cs = netroot+"/cs";
+ fd := sys->open(cs, Sys->ORDWR); # TO DO: choice of root
+ if(fd == nil)
+ return nil;
+ if(sys->fprint(fd, "!%s=%s %s=*", keytag, keyval, needtag) < 0)
+ return nil;
+ sys->seek(fd, big 0, 0);
+ buf := array[1024] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0){
+ al := authio->parseline(string buf[0:n]); # assume the conventions match factotum's
+ for(; al != nil; al = tl al)
+ if((hd al).name == needtag)
+ return (hd al).val;
+ }
+ return nil;
+}
+
+authdial(netroot: string, dom: string): ref Sys->FD
+{
+ p: string;
+ if(dom != nil){
+ # look up an auth server in an authentication domain
+ p = csgetvalue(netroot, "authdom", dom, "auth");
+
+ # if that didn't work, just try the IP domain
+ if(p == nil)
+ p = csgetvalue(netroot, "dom", dom, "auth");
+ if(p == nil)
+ p = "$auth"; # temporary ...
+ if(p == nil){
+ sys->werrstr("no auth server found for "+dom);
+ return nil;
+ }
+ }else
+ p = "$auth"; # look for one relative to my machine
+ (nil, conn) := sys->dial(netmkaddr(p, netroot, "ticket"), nil);
+ return conn.dfd;
+}
diff --git a/appl/cmd/auth/factotum/proto/pass.b b/appl/cmd/auth/factotum/proto/pass.b
new file mode 100644
index 00000000..9c4462b3
--- /dev/null
+++ b/appl/cmd/auth/factotum/proto/pass.b
@@ -0,0 +1,29 @@
+implement Authproto;
+
+include "sys.m";
+ sys: Sys;
+
+include "../authio.m";
+ authio: Authio;
+ Attr, IO: import authio;
+
+init(f: Authio): string
+{
+ sys = load Sys Sys->PATH;
+ authio = f;
+ return nil;
+}
+
+interaction(attrs: list of ref Attr, io: ref Authio->IO): string
+{
+ (key, err) := io.findkey(attrs, "user? !password?");
+ if(key == nil)
+ return err;
+ user := authio->lookattrval(key.attrs, "user");
+ if(user == nil)
+ return "unknown user";
+ pass := authio->lookattrval(key.secrets, "!password");
+ a := sys->aprint("%q %q", user, pass);
+ io.write(a, len a);
+ return nil;
+}
diff --git a/appl/cmd/auth/factotum/rpc.b b/appl/cmd/auth/factotum/rpc.b
new file mode 100644
index 00000000..220980a8
--- /dev/null
+++ b/appl/cmd/auth/factotum/rpc.b
@@ -0,0 +1,68 @@
+implement Rpcio;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "arg.m";
+
+Rpcio: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: rpc\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ cantload(Bufio->PATH);
+
+ file := "/mnt/factotum/rpc";
+ if(len args > 1)
+ file = hd tl args;
+ rfd := sys->open(file, Sys->ORDWR);
+ if(rfd == nil){
+ sys->fprint(sys->fildes(2), "rpc: can't open %s: %r\n", file);
+ raise "fail:load";
+ }
+ f := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ for(;;){
+ sys->print("> ");
+ s := f.gets('\n');
+ if(s == nil)
+ break;
+ rpc(rfd, s[0:len s-1]);
+ }
+}
+
+cantload(s: string)
+{
+ sys->fprint(sys->fildes(2), "csquery: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+rpc(f: ref Sys->FD, addr: string)
+{
+ b := array of byte addr;
+ if(sys->write(f, b, len b) > 0){
+ sys->seek(f, big 0, Sys->SEEKSTART);
+ buf := array[256] of byte;
+ if((n := sys->read(f, buf, len buf)) > 0)
+ sys->print("%s\n", string buf[0:n]);
+ if(n >= 0)
+ return;
+ }
+ sys->print("!%r\n");
+}