xref: /inferno-os/appl/lib/factotum.b (revision 934fd753d60353d854b3450c1cfe1c7a8e713579)
1implement Factotum;
2
3#
4# client interface to factotum
5#
6# this is a near transliteration of Plan 9 code, subject to the Lucent Public License 1.02
7#
8
9include "sys.m";
10	sys: Sys;
11
12include "string.m";
13
14include "factotum.m";
15
16debug := 0;
17
18init()
19{
20	sys = load Sys Sys->PATH;
21}
22
23setdebug(i: int)
24{
25	debug = i;
26}
27
28getaia(a: array of byte, n: int): (int, array of byte)
29{
30	if(len a - n < 2)
31		return (-1, nil);
32	c := (int a[n+1]<<8) | int a[n+0];
33	n += 2;
34	if(len a - n < c)
35		return  (-1, nil);
36	b := array[c] of byte;		# could avoid copy if known not to alias
37	b[0:] = a[n: n+c];
38	return (n+c, b);
39}
40
41getais(a: array of byte, n: int): (int, string)
42{
43	(n, a) = getaia(a, n);
44	return (n, string a);
45}
46
47Authinfo.unpack(a: array of byte): (int, ref Authinfo)
48{
49	ai := ref Authinfo;
50	n: int;
51	(n, ai.cuid) = getais(a, 0);
52	(n, ai.suid) = getais(a, n);
53	(n, ai.cap) = getais(a, n);
54	(n, ai.secret) = getaia(a, n);
55	if(n < 0)
56		return (-1, nil);
57	return (n, ai);
58}
59
60open(): ref Sys->FD
61{
62	return sys->open("/mnt/factotum/rpc", Sys->ORDWR);
63}
64
65mount(fd: ref Sys->FD, mnt: string, flags: int, aname: string, keyspec: string): (int, ref Authinfo)
66{
67	ai: ref Authinfo;
68	afd := sys->fauth(fd, aname);
69	if(debug && afd == nil){
70		sys->print("fauth %s: %r\n", aname);
71		return (-1, nil);
72	}
73	if(afd != nil){
74		ai = proxy(afd, open(), "proto=p9any role=client "+keyspec);
75		if(debug && ai == nil){
76			sys->print("proxy failed: %r\n");
77			return (-1, nil);
78		}
79	}
80	return (sys->mount(fd, afd, mnt, flags, aname), ai);
81}
82
83dump(a: array of byte): string
84{
85	s := sys->sprint("[%d]", len a);
86	for(i := 0; i < len a; i++){
87		c := int a[i];
88		if(c >= ' ' && c <= 16r7E)
89			s += sys->sprint("%c", c);
90		else
91			s += sys->sprint("\\x%.2ux", c);
92	}
93	return s;
94}
95
96verbof(buf: array of byte): (string, array of byte)
97{
98	n := len buf;
99	for(i:=0; i<n && buf[i] != byte ' '; i++)
100		;
101	s := string buf[0:i];
102	if(i < n)
103		i++;
104	buf = buf[i:];
105	case  s {
106	"ok" or "error" or "done" or "phase" or
107	"protocol" or "needkey" or "toosmall" or "internal" =>
108		return (s, buf);
109	* =>
110		sys->werrstr(sys->sprint("malformed rpc response: %q", s));
111		return ("rpc failure", buf);
112	}
113}
114
115dorpc(fd: ref Sys->FD, verb: string, val: array of byte): (string, array of byte)
116{
117	(o, a) := rpc(fd, verb, val);
118	if(o != "needkey" && o != "badkey")
119		return (o, a);
120	return ("no key", a);	# don't know how to get key
121}
122
123rpc(afd: ref Sys->FD, verb: string, a: array of byte): (string, array of byte)
124{
125	va := array of byte verb;
126	l := len va;
127	na := len a;
128	if(na+l+1 > AuthRpcMax){
129		sys->werrstr("rpc too big");
130		return ("toobig", nil);
131	}
132	buf := array[na+l+1] of byte;
133	buf[0:] = va;
134	buf[l] = byte ' ';
135	buf[l+1:] = a;
136	if(debug)
137		sys->print("rpc: ->%s %s\n", verb, dump(a));
138	if((n:=sys->write(afd, buf, len buf)) != len buf){
139		if(n >= 0)
140			sys->werrstr("rpc short write");
141		return ("rpc failure", nil);
142	}
143	buf = array[AuthRpcMax] of byte;
144	if((n=sys->read(afd, buf, len buf)) < 0){
145		if(debug)
146			sys->print("<- (readerr) %r\n");
147		return ("rpc failure", nil);
148	}
149	if(n < len buf)
150		buf[n] = byte 0;
151	buf = buf[0:n];
152
153	#
154	# Set error string for good default behavior.
155	#
156	s: string;
157	(t, r) := verbof(buf);
158	if(debug)
159		sys->print("<- %s %#q\n", t, dump(r));
160	case t {
161	"ok" or
162	"rpc failure" =>
163		;	# don't touch
164	"error" =>
165		if(len r == 0)
166			s = "unspecified rpc error";
167		else
168			s = sys->sprint("%s", string r);
169	"needkey" =>
170		s = sys->sprint("needkey %s", string r);
171	"badkey" =>
172		(nf, flds) := sys->tokenize(string r, "\n");
173		if(nf < 2)
174			s = sys->sprint("badkey %q", string r);
175		else
176			s = sys->sprint("badkey %q", hd tl flds);
177		break;
178	"phase" =>
179		s = sys->sprint("phase error: %q", string r);
180	* =>
181		s = sys->sprint("unknown rpc type %q (bug in rpc.c)", t);
182	}
183	if(s != nil)
184		sys->werrstr(s);
185	return (t, r);
186}
187
188Authinfo.read(fd: ref Sys->FD): ref Authinfo
189{
190	(o, a) := rpc(fd, "authinfo", nil);
191	if(o != "ok")
192		return nil;
193	(n, ai) := Authinfo.unpack(a);
194	if(n <= 0)
195		sys->werrstr("bad auth info from factotum");
196	return ai;
197}
198
199proxy(fd: ref Sys->FD, afd: ref Sys->FD, params: string): ref Authinfo
200{
201	readc := chan of (array of byte, chan of (int, string));
202	writec := chan of (array of byte, chan of (int, string));
203	donec := chan of (ref Authinfo, string);
204	spawn genproxy(readc, writec, donec, afd, params);
205	for(;;)alt{
206	(buf, reply) := <-readc =>
207		n := sys->read(fd, buf, len buf);
208		if(n == -1)
209			reply <-= (-1, sys->sprint("%r"));
210		else
211			reply <-= (n, nil);
212	(buf, reply) := <-writec =>
213		n := sys->write(fd, buf, len buf);
214		if(n == -1)
215			reply <-= (-1, sys->sprint("%r"));
216		else
217			reply <-= (n, nil);
218	(authinfo, err) := <-donec =>
219		if(authinfo == nil)
220			sys->werrstr(err);
221		return authinfo;
222	}
223}
224
225#
226# do what factotum says
227#
228genproxy(
229	readc: chan of (array of byte, chan of (int, string)),
230	writec: chan of (array of byte, chan of (int, string)),
231	donec: chan of (ref Authinfo, string),
232	afd: ref Sys->FD,
233	params: string)
234{
235	if(afd == nil){
236		donec <-= (nil, "no authentication fd");
237		return;
238	}
239
240	pa := array of byte params;
241	(o, a) := dorpc(afd, "start", pa);
242	if(o != "ok"){
243		donec <-= (nil, sys->sprint("proxy start: %r"));
244		return;
245	}
246
247	ai: ref Authinfo;
248	err: string;
249done:
250	for(;;){
251		(o, a) = dorpc(afd, "read", nil);
252		case o {
253		"done" =>
254			if(len a > 0 && a[0] == byte 'h' && string a == "haveai")
255				ai = Authinfo.read(afd);
256			else
257				ai = ref Authinfo;	# auth succeeded but empty authinfo
258			break done;
259		"ok" =>
260			writec <-= (a[0:len a], reply := chan of (int, string));
261			(n, e) := <-reply;
262			if(n != len a){
263				err = "proxy write fd: "+e;
264				break done;
265			}
266		"phase" =>
267			buf := array[AuthRpcMax] of {* => byte 0};
268			n := 0;
269			for(;;){
270				(o, a) = dorpc(afd, "write", buf[0:n]);
271				if(o != "toosmall")
272					break;
273				c := int string a;
274				if(c > AuthRpcMax)
275					break;
276				readc <-= (buf[n:c], reply := chan of (int, string));
277				(m, e) := <-reply;
278				if(m <= 0){
279					err = e;
280					if(m == 0)
281						err = sys->sprint("proxy short read");
282					break done;
283				}
284				n += m;
285			}
286			if(o != "ok"){
287				err = sys->sprint("proxy rpc write: %r");
288				break done;
289			}
290		* =>
291			err = sys->sprint("proxy rpc: %r");
292			break done;
293		}
294	}
295	donec <-= (ai, err);
296}
297
298#
299# insecure passwords, role=client
300#
301
302getuserpasswd(keyspec: string): (string, string)
303{
304	str := load String String->PATH;
305	if(str == nil)
306		return (nil, nil);
307	fd := open();
308	if(fd == nil)
309		return (nil, nil);
310	if(((o, a) := dorpc(fd, "start", array of byte keyspec)).t0 != "ok" ||
311	   ((o, a) = dorpc(fd, "read", nil)).t0 != "ok"){
312		sys->werrstr("factotum: "+o);
313		return (nil, nil);
314	}
315	flds := str->unquoted(string a);
316	if(len flds != 2){
317		sys->werrstr("odd response from factotum");
318		return (nil, nil);
319	}
320	return (hd flds, hd tl flds);
321}
322
323#
324# challenge/response, role=server
325#
326
327challenge(keyspec: string): ref Challenge
328{
329	c := ref Challenge;
330	if((c.afd = open()) == nil)
331		return nil;
332	if(rpc(c.afd, "start", array of byte keyspec).t0 != "ok")
333		return nil;
334	(w, val) := rpc(c.afd, "read", nil);
335	if(w != "ok")
336		return nil;
337	c.chal = string val;
338	return c;
339}
340
341response(c: ref Challenge, resp: string): ref Authinfo
342{
343	if(c.afd == nil){
344		sys->werrstr("auth_response: connection not open");
345		return nil;
346	}
347	if(resp == nil){
348		sys->werrstr("auth_response: nil response");
349		return nil;
350	}
351
352	if(c.user != nil){
353		if(rpc(c.afd, "write", array of byte c.user).t0 != "ok"){
354			# we're out of phase with factotum; give up
355			c.afd = nil;
356			return nil;
357		}
358	}
359
360	if(rpc(c.afd, "write", array of byte resp).t0 != "ok"){
361		# don't close the connection; we might try again
362		return nil;
363	}
364
365	(w, val) := rpc(c.afd, "read", nil);
366	if(w != "done"){
367		sys->werrstr(sys->sprint("unexpected factotum reply: %q %q", w, string val));
368		c.afd = nil;
369		return nil;
370	}
371	ai := Authinfo.read(c.afd);
372	c.afd = nil;
373	return ai;
374}
375
376#
377# challenge/response, role=client
378#
379
380respond(chal: string, keyspec: string): (string, string)
381{
382	if((afd := open()) == nil)
383		return (nil, nil);
384
385	if(dorpc(afd, "start", array of byte keyspec).t0 != "ok" ||
386	   dorpc(afd, "write", array of byte chal).t0 != "ok")
387		return (nil, nil);
388	(o, resp) := dorpc(afd, "read", nil);
389	if(o != "ok")
390		return (nil, nil);
391
392	return (string resp, findattrval(rpcattrs(afd), "user"));
393}
394
395rpcattrs(afd: ref Sys->FD): list of ref Attr
396{
397	(o, a) := rpc(afd, "attr", nil);
398	if(o != "ok")
399		return nil;
400	return parseattrs(string a);
401}
402
403#
404# attributes
405#
406
407parseattrs(s: string): list of ref Attr
408{
409	str := load String String->PATH;
410	fld := str->unquoted(s);
411	rfld := fld;
412	for(fld = nil; rfld != nil; rfld = tl rfld)
413		fld = (hd rfld) :: fld;
414	attrs: list of ref Attr;
415	for(; fld != nil; fld = tl fld){
416		n := hd fld;
417		a := "";
418		tag := Aattr;
419		for(i:=0; i<len n; i++)
420			if(n[i] == '='){
421				a = n[i+1:];
422				n = n[0:i];
423				tag = Aval;
424			}
425		if(len n == 0)
426			continue;
427		if(tag == Aattr && len n > 1 && n[len n-1] == '?'){
428			tag = Aquery;
429			n = n[0:len n-1];
430		}
431		attrs = ref Attr(tag, n, a) :: attrs;
432	}
433	# TO DO: eliminate answered queries
434	return attrs;
435}
436
437Attr.text(a: self ref Attr): string
438{
439	case a.tag {
440	Aattr =>
441		return a.name;
442	Aval =>
443		return sys->sprint("%q=%q", a.name, a.val);
444	Aquery =>
445		return sys->sprint("%q?", a.name);
446	* =>
447		return "??";
448	}
449}
450
451attrtext(attrs: list of ref Attr): string
452{
453	s := "";
454	for(; attrs != nil; attrs = tl attrs){
455		if(s != nil)
456			s[len s] = ' ';
457		s += (hd attrs).text();
458	}
459	return s;
460}
461
462findattr(attrs: list of ref Attr, n: string): ref Attr
463{
464	for(; attrs != nil; attrs = tl attrs)
465		if((a := hd attrs).tag != Aquery && a.name == n)
466			return a;
467	return nil;
468}
469
470findattrval(attrs: list of ref Attr, n: string): string
471{
472	if((a := findattr(attrs, n)) != nil)
473		return a.val;
474	return nil;
475}
476
477delattr(l: list of ref Attr, n: string): list of ref Attr
478{
479	rl: list of ref Attr;
480	for(; l != nil; l = tl l)
481		if((hd l).name != n)
482			rl = hd l :: rl;
483	return rev(rl);
484}
485
486copyattrs(l: list of ref Attr): list of ref Attr
487{
488	rl: list of ref Attr;
489	for(; l != nil; l = tl l)
490		rl = hd l :: rl;
491	return rev(rl);
492}
493
494takeattrs(l: list of ref Attr, names: list of string): list of ref Attr
495{
496	rl: list of ref Attr;
497	for(; l != nil; l = tl l){
498		n := (hd l).name;
499		for(nl := names; nl != nil; nl = tl nl)
500			if((hd nl) == n){
501				rl = hd l :: rl;
502				break;
503			}
504	}
505	return rev(rl);
506}
507
508publicattrs(l: list of ref Attr): list of ref Attr
509{
510	rl: list of ref Attr;
511	for(; l != nil; l = tl l){
512		a := hd l;
513		if(a.tag != Aquery || a.val == nil)
514			rl = a :: rl;
515	}
516	return rev(rl);
517}
518
519rev[T](l: list of T): list of T
520{
521	rl: list of T;
522	for(; l != nil; l = tl l)
523		rl = hd l :: rl;
524	return rl;
525}
526