xref: /inferno-os/appl/cmd/listen.b (revision 62d7827bc358c000db9ff48fe61bd28ac352a884)
1implement Listen;
2include "sys.m";
3	sys: Sys;
4include "draw.m";
5include "arg.m";
6include "keyring.m";
7	keyring: Keyring;
8include "security.m";
9	auth: Auth;
10include "dial.m";
11	dial: Dial;
12include "sh.m";
13	sh: Sh;
14	Context: import sh;
15
16Listen: module {
17	init: fn(nil: ref Draw->Context, argv: list of string);
18};
19
20badmodule(p: string)
21{
22	sys->fprint(stderr(), "listen: cannot load %s: %r\n", p);
23	raise "fail:bad module";
24}
25
26serverkey: ref Keyring->Authinfo;
27verbose := 0;
28
29init(drawctxt: ref Draw->Context, argv: list of string)
30{
31	sys = load Sys Sys->PATH;
32	keyring = load Keyring Keyring->PATH;
33	auth = load Auth Auth->PATH;
34	if (auth == nil)
35		badmodule(Auth->PATH);
36	dial = load Dial Dial->PATH;
37	if (dial == nil)
38		badmodule(Dial->PATH);
39	sh = load Sh Sh->PATH;
40	if (sh == nil)
41		badmodule(Sh->PATH);
42	arg := load Arg Arg->PATH;
43	if (arg == nil)
44		badmodule(Arg->PATH);
45	auth->init();
46	algs: list of string;
47	arg->init(argv);
48	keyfile: string;
49	initscript: string;
50	doauth := 1;
51	synchronous := 0;
52	trusted := 0;
53	arg->setusage("listen [-i {initscript}] [-Ast] [-k keyfile] [-a alg]... addr command [arg...]");
54	while ((opt := arg->opt()) != 0) {
55		case opt {
56		'a' =>
57			algs = arg->earg() :: algs;
58		'A' =>
59			doauth = 0;
60		'f' or
61		'k' =>
62			keyfile = arg->earg();
63			if (! (keyfile[0] == '/' || (len keyfile > 2 &&  keyfile[0:2] == "./")))
64				keyfile = "/usr/" + user() + "/keyring/" + keyfile;
65		'i' =>
66			initscript = arg->earg();
67		'v' =>
68			verbose = 1;
69		's' =>
70			synchronous = 1;
71		't' =>
72			trusted = 1;
73		* =>
74			arg->usage();
75		}
76	}
77	if (doauth && algs == nil)
78		algs = getalgs();
79	if (algs != nil) {
80		if (keyfile == nil)
81			keyfile = "/usr/" + user() + "/keyring/default";
82		serverkey = keyring->readauthinfo(keyfile);
83		if (serverkey == nil) {
84			sys->fprint(stderr(), "listen: cannot read %s: %r\n", keyfile);
85			raise "fail:bad keyfile";
86		}
87	}
88	if(!trusted){
89		sys->unmount(nil, "/mnt/keys");	# should do for now
90		# become none?
91	}
92
93	argv = arg->argv();
94	n := len argv;
95	if (n < 2)
96		arg->usage();
97	arg = nil;
98
99	sync := chan[1] of string;
100	spawn listen(drawctxt, hd argv, tl argv, algs,  initscript, sync);
101	e := <-sync;
102	if(e != nil)
103		raise "fail:" + e;
104	if(synchronous){
105		e = <-sync;
106		if(e != nil)
107			raise "fail:" + e;
108	}
109}
110
111listen(drawctxt: ref Draw->Context, addr: string, argv: list of string,
112		algs: list of string, initscript: string, sync: chan of string)
113{
114	{
115		listen1(drawctxt, addr, argv, algs, initscript, sync);
116	} exception e {
117	"fail:*" =>
118		sync <-= e;
119	}
120}
121
122listen1(drawctxt: ref Draw->Context, addr: string, argv: list of string,
123		algs: list of string, initscript: string, sync: chan of string)
124{
125	sys->pctl(Sys->FORKFD, nil);
126
127	ctxt := Context.new(drawctxt);
128	acon := dial->announce(addr);
129	if (acon == nil) {
130		sys->fprint(stderr(), "listen: failed to announce on '%s': %r\n", addr);
131		sync <-= "cannot announce";
132		exit;
133	}
134	ctxt.set("user", nil);
135	if (initscript != nil) {
136		ctxt.setlocal("net", ref Sh->Listnode(nil, acon.dir) :: nil);
137		ctxt.run(ref Sh->Listnode(nil, initscript) :: nil, 0);
138		initscript = nil;
139	}
140
141	# make sure the shell command is parsed only once.
142	cmd := sh->stringlist2list(argv);
143	if((hd argv) != nil && (hd argv)[0] == '{'){
144		(c, e) := sh->parse(hd argv);
145		if(c == nil){
146			sys->fprint(stderr(), "listen: %s\n", e);
147			sync <-= "parse error";
148			exit;
149		}
150		cmd = ref Sh->Listnode(c, hd argv) :: tl cmd;
151	}
152
153	sync <-= nil;
154	listench := chan of (int, ref Dial->Connection);
155	authch := chan of (string, ref Dial->Connection);
156	spawn listener(listench, acon, addr);
157	for (;;) {
158		user := "";
159		ccon: ref Dial->Connection;
160		alt {
161		(lok, c) := <-listench =>
162			if (lok == -1){
163				sync <-= "listen";
164				exit;
165			}
166			if (algs != nil) {
167				spawn authenticator(authch, c, algs, addr);
168				continue;
169			}
170			ccon = c;
171		(user, ccon) = <-authch =>
172			;
173		}
174		if (user != nil)
175			ctxt.set("user", sh->stringlist2list(user :: nil));
176		ctxt.set("net", ref Sh->Listnode(nil, ccon.dir) :: nil);
177
178		# XXX could do this in a separate process too, to
179		# allow new connections to arrive and start authenticating
180		# while the shell command is still running.
181		sys->dup(ccon.dfd.fd, 0);
182		sys->dup(ccon.dfd.fd, 1);
183		ccon.dfd = ccon.cfd = nil;
184		ctxt.run(cmd, 0);
185		sys->dup(2, 0);
186		sys->dup(2, 1);
187	}
188}
189
190listener(listench: chan of (int, ref Dial->Connection), c: ref Dial->Connection, addr: string)
191{
192	for (;;) {
193		nc := dial->listen(c);
194		if (nc == nil) {
195			sys->fprint(stderr(), "listen: listen error on '%s': %r\n", addr);
196			listench <-= (-1, nc);
197			exit;
198		}
199		if (verbose)
200			sys->fprint(stderr(), "listen: got connection on %s from %s",
201					addr, readfile(nc.dir + "/remote"));
202		nc.dfd = dial->accept(nc);
203		if (nc.dfd == nil)
204			sys->fprint(stderr(), "listen: cannot open %s: %r\n", nc.dir + "/data");
205		else{
206			if(nc.cfd != nil)
207				sys->fprint(nc.cfd, "keepalive");
208			listench <-= (0, nc);
209		}
210	}
211}
212
213authenticator(authch: chan of (string, ref Dial->Connection),
214		c: ref Dial->Connection, algs: list of string, addr: string)
215{
216	err: string;
217	(c.dfd, err) = auth->server(algs, serverkey, c.dfd, 0);
218	if (c.dfd == nil) {
219		sys->fprint(stderr(), "listen: auth on %s failed: %s\n", addr, err);
220		return;
221	}
222	if (verbose)
223		sys->fprint(stderr(), "listen: authenticated on %s as %s\n", addr, err);
224	authch <-= (err, c);
225}
226
227stderr(): ref Sys->FD
228{
229	return sys->fildes(2);
230}
231
232user(): string
233{
234	u := readfile("/dev/user");
235	if (u == nil)
236		return "nobody";
237	return u;
238}
239
240readfile(f: string): string
241{
242	fd := sys->open(f, sys->OREAD);
243	if(fd == nil)
244		return nil;
245
246	buf := array[1024] of byte;
247	n := sys->read(fd, buf, len buf);
248	if(n < 0)
249		return nil;
250
251	return string buf[0:n];
252}
253
254getalgs(): list of string
255{
256	sslctl := readfile("#D/clone");
257	if (sslctl == nil) {
258		sslctl = readfile("#D/ssl/clone");
259		if (sslctl == nil)
260			return nil;
261		sslctl = "#D/ssl/" + sslctl;
262	} else
263		sslctl = "#D/" + sslctl;
264	(nil, algs) := sys->tokenize(readfile(sslctl + "/encalgs") + " " + readfile(sslctl + "/hashalgs"), " \t\n");
265	return "none" :: algs;
266}
267