implement P9cpu;
include "sys.m";
sys: Sys;
include "draw.m";
include "arg.m";
include "sh.m";
sh: Sh;
include "fdrun.m";
fdrun: FDrun;
include "factotum.m";
include "encoding.m";
b64: Encoding;
include "keyring.m";
include "security.m";
# to do/test:
# - inferno command
# - garbage process collection
# issues around factotum with inferno:
#
# key retention - adding keys to secstore is not easy, as it
# has to be done manually, and keys obtained with
# getauthinfo are somewhat unwieldy.
# we don't want to have to do a getauthinfo
# every time we start factotum.
#
# algorithm negotiation - would it be reasonable to
# add algorithm specification to keyspec?
# e.g. proto=infauth role=server 'algs=rc4_256 sha1 md5'
# or proto=infauth role=client 'alg=rc4_256/sha1'
# if not, how can the desired/allowed algorithm(s) be specified?
MaxStr: con 128; # as defined in /sys/src/cmd/cpu.c
P9cpu: module {
init: fn(ctxt: ref Draw->Context, argv: list of string);
};
authmethods := array[] of {"p9", "netkey"};
finish: chan of int;
init(ctxt: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
sh = load Sh Sh->PATH;
b64 = load Encoding Encoding->BASE64PATH;
arg := load Arg Arg->PATH;
fdrun = load FDrun FDrun->PATH;
fdrun->init();
authmethod := "p9";
keyspec := "";
ealgs := "rc4_256 sha1";
wmexport := 0;
p9win := 0;
infernocmd := 0;
relaynotes := 1;
p9winopts: list of string;
system := "";
cmd := "";
arg->init(argv);
arg->setusage("9cpu [-wrIn] [-e crypto_algs] [-a auth_method] [-k keyspec] [-c cmd] [-h system]");
while((opt := arg->opt()) != 0){
case opt {
'a' =>
authmethod = arg->earg();
'k' =>
keyspec = arg->earg();
'e' =>
ealgs = arg->earg();
'w' =>
wmexport = 1;
'r' =>
p9win = 1;
'I' =>
infernocmd = 1;
wmexport = 1;
'n' =>
relaynotes = 0;
'x' =>
p9winopts = "-x"+arg->earg() :: p9winopts;
'y' =>
p9winopts = "-y"+arg->earg() :: p9winopts;
'h' =>
system = arg->earg();
'c' =>
cmd = arg->earg();
* =>
arg->usage();
}
}
argv = arg->argv();
if(argv != nil)
arg->usage();
for(i := 0; i < len authmethods; i++)
if(authmethod == authmethods[i])
break;
if(i == len authmethods)
fatal(sys->sprint("unknown authentication method %q", authmethod));
sys->pctl(Sys->FORKNS, nil);
(dfd, err) := rexcall(system, authmethod, keyspec, ealgs);
if(dfd == nil)
fatal(err);
if(p9win && cmd == nil)
cmd = "rio";
else if(infernocmd){
if(cmd == nil)
cmd = "sh -i";
cmd = "emu /dis/sh -c "+
shquoted("{"+
"bind '#U*' /n/local;"+
"wmimport -d /n/local/mnt/term/dev -w /n/local/mnt/term/mnt/wm sh -c "+
shquoted(cmd)+
"}"
);
}
finish = chan[1] of int;
if(cmd != nil){
# guard against limited size command buffer at other end.
if(len array of byte cmd + 3 > MaxStr){
writestr(dfd, "! . /mnt/term/dev/cpucmd", "command", 0);
spawn servefileproc(sync := chan of int, "/dev", "cpucmd", array of byte (cmd+"\n"));
<-sync;
}else
writestr(dfd, "! "+cmd, "command", 0);
}
cwd := sys->fd2path(sys->open(".", Sys->OREAD));
if(cwd == nil)
writestr(dfd, "NO", "dir", 0);
else
writestr(dfd, cwd, "dir", 0);
(ok, data) := readstr(dfd, '\0');
if(ok == -1)
fatal("waiting for FS");
if(len data < 2 || data[0:2] != "FS")
fatal("remote cpu: "+data);
exdir: string;
(ok, exdir) = readstr(dfd, '\0');
if(exdir != "/")
fatal("cannot export portion of namespace");
sys->write(dfd, array[] of {byte 'O', byte 'K'}, 2);
# set up the namespace that we wish to export
if(wmexport){
sys->pipe(p := array[2] of ref Sys->FD);
fdrun->run(ctxt, "wmexport"::nil, "0--", p[0:1], chan[1] of string);
sys->mount(p[1], nil, "/mnt/wm", Sys->MREPL, nil);
}
if(relaynotes && !p9win){
spawn relaynotesproc(sync := chan of int);
<-sync;
}
if(p9win){
fdrun->run(ctxt, "9win"::"-s"::p9winopts, "0--", array[] of {dfd}, status := chan of string);
err = <-status;
finish <-= 1;
if(err != nil)
raise "fail:"+err;
}else{
if(sys->export(dfd, "/", Sys->EXPWAIT) == -1)
sys->print("export error: %r\n");
finish <-= 1;
}
}
shquoted(s: string): string
{
return sh->quoted(ref Sh->Listnode(nil, s)::nil, 1);
}
rexcall(system, authmethod, keyspec, ealgs: string): (ref Sys->FD, string)
{
Negerr: con "negotiating authentication method";
na := netmkaddr(system, nil, "ncpu");
(ok, c) := sys->dial(na, nil);
if(ok == -1)
return (nil, sys->sprint("cannot dial %q: %r", na));
# plan 9 cpu protocol
a := authmethod;
if(ealgs != nil)
a += " " + ealgs;
writestr(c.dfd, a, Negerr, 0);
err: string;
(ok, err) = readstr(c.dfd, '\0');
if(ok == -1)
return (nil, Negerr);
if(err != nil)
return (nil, "readstr: "+err);
case authmethod {
"p9" =>
return p9auth(c.dfd, ealgs, keyspec);
"netkey" =>
return netkeyauth(c.dfd);
}
return (nil, "unknown auth method");
}
netkeyauth(fd: ref Sys->FD): (ref Sys->FD, string)
{
(ok, user) := ask("user["+getuser()+"]", getuser());
if(ok == -1)
return (nil, "terminated");
writestr(fd, user, "challenge/response", 1);
for(;;){
chall: string;
(ok, chall) = readstr(fd, '\0');
if(ok == -1)
return (nil, sys->sprint("readstr: %r"));
if(chall == nil)
return (fd, nil);
resp: string;
(ok, resp) = ask("challenge: "+chall+"; response", nil);
if(ok == -1)
break;
writestr(fd, resp, "challenge/response", 1);
}
return (nil, "terminated");
}
ask(q: string, default: string): (int, string)
{
sys->print("%s: ", q);
(ok, s) := readstr(sys->fildes(0), '\n');
if(ok == -1)
return (-1, nil);
if(s == nil)
return (0, default);
return (0, s);
}
p9auth(fd: ref Sys->FD, ealgs, keyspec: string): (ref Sys->FD, string)
{
factotum := load Factotum Factotum->PATH;
if(factotum == nil)
return (nil, sys->sprint("cannot load %q: %r", Factotum->PATH));
factotum->init();
keyring := load Keyring Keyring->PATH;
if(keyring == nil)
return (nil, sys->sprint("cannot load %q: %r", Keyring->PATH));
ai := factotum->proxy(fd, sys->open("/mnt/factotum/rpc", Sys->ORDWR),
sys->sprint("proto=p9any role=client %s", keyspec));
if(ai == nil)
return (nil, sys->sprint("factotum: %r"));
if(len ai.secret != 8)
return (nil, "expected different length of secret");
if(ealgs == nil)
return (fd, nil);
key := array[16] of byte;
key[4:] = ai.secret;
randombytes(key[0:4]);
if(sys->write(fd, key, 4) != 4)
return (nil, sys->sprint("write: %r"));
if(readn(fd, key[12:], 4) != 4)
return (nil, sys->sprint("read: %r"));
digest := array[Keyring->SHA1dlen] of byte;
keyring->sha1(key, len key, digest, nil);
(efd, err) := pushssl(fd, ealgs, mksecret(digest), mksecret(digest[10:]));
if(efd == nil)
return (nil, sys->sprint("cannot push ssl: %s", err));
return (efd, nil);
}
# plan 9 bug/strangeness: interpret hex string as base64 to use as secret.
mksecret(f: array of byte): array of byte
{
return b64->dec(
sys->sprint(
"%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
int f[0], int f[1], int f[2], int f[3], int f[4],
int f[5], int f[6], int f[7], int f[8], int f[9])
);
}
# set up shell window button; relay events from that
relaynotesproc(sync: chan of int)
{
shctl := sys->open("/chan/shctl", Sys->OWRITE);
if(shctl == nil){
sys->fprint(sys->fildes(2), "cpu: cannot relay notes: not in shell window\n");
sync <-= -1;
exit;
}
sys->fprint(shctl, "clear");
if(sys->fprint(shctl, "action Interrupt interrupt") == -1){
sys->fprint(sys->fildes(2), "cpu: cannot create interrupt button: %r\n");
sync <-= -1;
exit;
}
# create dummy placeholder file
fio := file2chan("/dev", "cpunote");
sys->bind("/chan/shctl", "/dev/cpunote", Sys->MREPL);
sync <-= 0;
finish <-= <-finish;
sys->fprint(shctl, "clear");
fio = nil;
}
file2chan(d, f: string): ref Sys->FileIO
{
if((fio := sys->file2chan(d, f)) == nil){
sys->bind("#s", "/dev", Sys->MBEFORE);
fio = sys->file2chan(d, f);
}
return fio;
}
servefileproc(sync: chan of int, d, f: string, data: array of byte)
{
fio := file2chan(d, f);
sync <-= sys->pctl(0, nil);
for(;;)alt{
(offset, nil, nil, rc) := <-fio.read =>
if(rc != nil){
if(offset > len data)
offset = len data;
rc <-= (data[offset:], nil);
}
(nil, nil, nil, wc) := <-fio.write =>
if(wc != nil)
wc <-= (-1, "permission denied");
<-finish =>
finish <-= 1;
exit;
}
}
pushssl(fd: ref Sys->FD, ealgs: string, secretin, secretout: array of byte): (ref Sys->FD, string)
{
ssl := load SSL SSL->PATH;
if(ssl == nil)
return (nil, sys->sprint("canot load %q: %r", SSL->PATH));
(err, c) := ssl->connect(fd);
if(err != nil)
return (nil, "can't connect ssl: " + err);
err = ssl->secret(c, secretin, secretout);
if(err != nil)
return (nil, "can't write secret: " + err);
if(sys->fprint(c.cfd, "alg %s", ealgs) < 0)
return (nil, sys->sprint("can't push algorithm %s: %r", ealgs));
return (c.dfd, nil);
}
writestr(fd: ref Sys->FD, s: string, thing: string, ignore: int)
{
x := array of byte s;
x0 := array[len x+1] of byte;
x0[0:] = x;
x0[len x] = byte 0;
n := sys->write(fd, x0, len x0);
if(!ignore && n != len x0)
fatal(sys->sprint("writing network: %s: %r", thing));
}
readstr(fd: ref Sys->FD, c: int): (int, string)
{
buf := array[128] of byte;
b := buf;
l := 0;
while(len b > 0){
n := sys->read(fd, b, 1);
if(n <= 0)
return (-1, nil);
if(int b[0] == c)
return (0, string buf[0:l]);
l++;
b = b[1:];
}
return (-1, nil);
}
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);
}
randombytes(buf: array of byte): int
{
fd := sys->open("/dev/notquiterandom", Sys->OREAD);
if(fd == nil)
return -1;
if(sys->read(fd, buf, len buf) != len buf)
return -1;
return 0;
}
getuser(): string
{
if ((s := readfile("/dev/user")) == nil)
return "none";
return s;
}
readfile(f: string): string
{
fd := sys->open(f, sys->OREAD);
if(fd == nil)
return nil;
buf := array[8192] of byte;
n := sys->read(fd, buf, len buf);
if(n < 0)
return nil;
return string buf[0:n];
}
readn(fd: ref Sys->FD, buf: array of byte, nb: int): int
{
for(nr := 0; nr < nb;){
n := sys->read(fd, buf[nr:], nb-nr);
if(n <= 0){
if(nr == 0)
return n;
break;
}
nr += n;
}
return nr;
}
fatal(err: string)
{
sys->fprint(sys->fildes(2), "cpu: %s\n", err);
raise "fail:error";
}
kill(pid: int)
{
sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
}
|