code: purgatorio

ref: d916a4c3823f55227ffae35738c2497256e307b5
dir: /appl/cmd/auth/factotum/factotum.b/

View raw version
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";

include "readdir.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 (list 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++;
			'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 (list 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 == 0)
		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 (list 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;
		}
		if(flds == nil || (hd flds)[0] == '#'){
			wc <-= (len data, nil);
			break;
		}
		s := hd flds;
		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 verb");
		}

	(nil, nbytes, fid, rc) := <-files.rpc.read =>
		if(rc == nil)
			break;
		r := findfid(fid);
		if(r == nil){
			rc <-= (nil, "no rpc pending");
			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(readprotos(), off, nbytes);
	(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 (list 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, 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
		kl := findkeys(allkeys, attrs);
		if(kl != nil){
			kc <-= (kl, 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);
}

readprotos(): string
{
	readdir := load Readdir Readdir->PATH;
	if(readdir == nil)
		return "unknown\n";
	(dirs, nil) := readdir->init("/dis/auth/proto", Readdir->NAME|Readdir->COMPACT);
	s := "";
	for(i := 0; i < len dirs; i++){
		n := dirs[i].name;
		if(len n > 4 && n[len n-4:] == ".dis")
			s += n[0: len n-4]+"\n";
	}
	return s;
}

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 > 1)
		sys->print("-> %s <-\n", attrtext(attrs));
	p := lookattrval(attrs, "proto");
	if(p == nil)
		return (nil, nil, "did not specify protocol");
	if(debug > 1)
		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);
}

ignored(s: string): int
{
	return s == "role" || s == "disabled";
}

matchattr(attrs: list of ref Attr, pat: ref Attr): int
{
	return (b := lookattr(attrs, pat.name)) != nil && (pat.tag == Aquery || b.val == pat.val) ||
			ignored(pat.name);
}

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

findkeys(keys: array of ref Key, attrs: list of ref Attr): list of ref Key
{
	if(debug)
		sys->print("findkey %q\n", attrtext(attrs));
	kl: list of ref Key;
	for(i := 0; i < len keys; i++)
		if((k := keys[i]) != nil && matchattrs(k.attrs, k.secrets, attrs))
			kl = k :: kl;
	return reverse(kl);
}

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(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string)
{
	(kl, err) := io.findkeys(attrs, extra);
	if(kl != nil)
		return (hd kl, err);
	return (nil, err);
}

IO.findkeys(nil: self ref IO, attrs: list of ref Attr, extra: string): (list of ref Key, string)
{
	ea := parseline(extra);
	for(; ea != nil; ea = tl ea)
		attrs = hd ea :: attrs;
	kc := chan of (list 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 (list of ref Key, string);
	keymanc <-= (attrs, 1, kc);
	(kl, err) := <-kc;
	if(kl != nil)
		return (hd kl, err);
	return (nil, err);
}

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.rpc = rpc;
			io.toosmall(n+3);
		Oauthinfo =>
			reply(rpc, "error authentication unfinished");
		* =>
			phase(rpc, "protocol phase error");
		}
	exit;
}

IO.rdwr(io: self ref IO): array of byte
{
	io.ok();
	while((rpc := rio(io.f)) != nil)
		case rpc.cmd {
		Oread =>
			io.rpc = rpc;
			if(rpc.nbytes >= 3)
				return nil;
			io.toosmall(128+3);		# make them read something
		Owrite =>
			io.rpc = rpc;
			if(rpc.arg == nil)
				rpc.arg = array[0] of byte;
			return rpc.arg;
		Oauthinfo =>
			reply(rpc, "error authentication unfinished");
		* =>
			phase(rpc, "protocol phase error");
		}
	exit;
}

IO.reply2read(io: self ref IO, buf: array of byte, n: int): int
{
	if(io.rpc == nil)
		return 0;
	rpc := io.rpc;
	if(rpc.cmd != Oread){
		io.rpc = nil;
		phase(rpc, "internal phase error");
		return 0;
	}
	if(rpc.nbytes-3 < n){
		io.toosmall(n+3);
		return 0;
	}
	io.rpc = nil;
	okdata(rpc, buf[0:n]);
	return 1;
}

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