implement Listen; include "sys.m"; sys: Sys; include "draw.m"; include "arg.m"; include "iauth.m"; iauth: Iauth; include "sh.m"; sh: Sh; Context: import sh; Listen: module{ init: fn(nil: ref Draw->Context, argv: list of string); }; badmodule(p: string) { sys->fprint(stderr(), "listen: cannot load %s: %r\n", p); raise "fail:bad module"; } keyspec: string; setuid := 0; verbose := 0; initscript: string; addr: string; cmd: list of string; doauth := 1; trusted := 0; init(drawctxt: ref Draw->Context, argv: list of string) { sys = load Sys Sys->PATH; sh = load Sh Sh->PATH; if(sh == nil) badmodule(Sh->PATH); iauth = load Iauth Iauth->PATH; if(iauth == nil) badmodule(Iauth->PATH); iauth->init(); arg := load Arg Arg->PATH; if(arg == nil) badmodule(Arg->PATH); arg->init(argv); synchronous := 0; arg->setusage("listen [-i {initscript}] [-Ast] [-k keyfile] [-a alg]... addr command [arg...]"); while((opt := arg->opt()) != 0){ case opt{ 'A' => doauth = 0; 'k' => keyspec = arg->earg(); 'i' => initscript = arg->earg(); 'v' => verbose = 1; 's' => synchronous = 1; 't' => trusted = 1; 'p' => setuid = 1; * => arg->usage(); } } if(!trusted){ sys->unmount(nil, "/mnt/keys"); # sys->unmount(nil, "/mnt/factotum"); # become none? } argv = arg->argv(); n := len argv; if(n < 2) arg->usage(); arg = nil; addr = hd argv; cmd = tl argv; argv = nil; sync := chan[1] of string; spawn listen(drawctxt, sync); e := <-sync; if(e != nil) raise "fail:" + e; if(synchronous){ e = <-sync; if(e != nil) raise "fail:" + e; } } listen(drawctxt: ref Draw->Context, sync: chan of string) { { listen1(drawctxt, sync); } exception e{ "fail:*" => sync <-= e; } } listen1(drawctxt: ref Draw->Context, sync: chan of string) { sys->pctl(Sys->FORKFD, nil); ctxt := Context.new(drawctxt); (ok, acon) := sys->announce(addr); if(ok == -1){ sys->fprint(stderr(), "listen: failed to announce on '%s': %r\n", addr); sync <-= "cannot announce"; exit; } ctxt.set("user", nil); if(initscript != nil){ ctxt.setlocal("net", ref Sh->Listnode(nil, acon.dir) :: nil); ctxt.run(ref Sh->Listnode(nil, initscript) :: nil, 0); initscript = nil; } # make sure the shell command is parsed only once; # also checks for syntax errors. shargv := sh->stringlist2list(cmd); if((hd cmd) != nil && (hd cmd)[0] == '{'){ (c1, e) := sh->parse(hd cmd); if(c1 == nil){ sys->fprint(stderr(), "listen: %s\n", e); sync <-= "parse error"; exit; } shargv = ref Sh->Listnode(c1, hd cmd) :: tl shargv; } sync <-= nil; listench := chan of (int, Sys->Connection); authch := chan of (string, Sys->Connection); spawn listener(listench, acon, addr); for(;;){ user := ""; ccon: Sys->Connection; alt{ (lok, c) := <-listench => if(lok == -1){ sync <-= "listen"; exit; } if(doauth){ spawn authenticator(authch, c, addr); continue; } ccon = c; (user, ccon) = <-authch => ; } if(user != nil) ctxt.set("user", sh->stringlist2list(user :: nil)); ctxt.set("net", ref Sh->Listnode(nil, ccon.dir) :: nil); # XXX could do this in a separate process too, to # allow new connections to arrive and start authenticating # while the shell command is still running. sys->dup(ccon.dfd.fd, 0); sys->dup(ccon.dfd.fd, 1); ccon.dfd = ccon.cfd = nil; ctxt.run(shargv, 0); sys->dup(2, 0); sys->dup(2, 1); } } listener(listench: chan of (int, Sys->Connection), c: Sys->Connection, addr: string) { for(;;){ (ok, nc) := sys->listen(c); if(ok == -1){ sys->fprint(stderr(), "listen: listen error on '%s': %r\n", addr); listench <-= (-1, nc); exit; } if(verbose) sys->fprint(stderr(), "listen: got connection on %s from %s", addr, readfile(nc.dir + "/remote")); nc.dfd = sys->open(nc.dir + "/data", Sys->ORDWR); if(nc.dfd == nil) sys->fprint(stderr(), "listen: cannot open %s: %r\n", nc.dir + "/data"); else{ if(nc.cfd != nil) sys->fprint(nc.cfd, "keepalive"); listench <-= (ok, nc); } } } authenticator(authch: chan of (string, Sys->Connection), c: Sys->Connection, addr: string) { err: string; (c.dfd, err) = iauth->auth(keyspec+" proto=infauth role=server", c.dfd, setuid); if(c.dfd == nil){ sys->fprint(stderr(), "listen: auth on %s failed: %s\n", addr, err); return; } if(verbose) sys->fprint(stderr(), "listen: authenticated on %s as %s\n", addr, err); authch <-= (err, c); } stderr(): ref Sys->FD { return sys->fildes(2); } readfile(f: string): string { fd := sys->open(f, sys->OREAD); if(fd == nil) return nil; buf := array[1024] of byte; n := sys->read(fd, buf, len buf); if(n < 0) return nil; return string buf[0:n]; }