code: purgatorio

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

View raw version
implement Secstorec;

#
# interact with the Plan 9 secstore
#

include "sys.m";
	sys: Sys;

include "draw.m";

include "dial.m";
	dial: Dial;

include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

include "secstore.m";
	secstore: Secstore;

include "arg.m";

Secstorec: module
{
	init:	fn(nil: ref Draw->Context, nil: list of string);
};

Maxfilesize: con 128*1024;

stderr: ref Sys->FD;
conn: ref Dial->Connection;
seckey: array of byte;
filekey: array of byte;
file: array of byte;
verbose := 0;

init(nil: ref Draw->Context, args: list of string)
{
	sys = load Sys Sys->PATH;
	bufio = load Bufio Bufio->PATH;
	secstore = load Secstore Secstore->PATH;
	dial = load Dial Dial->PATH;

	sys->pctl(Sys->FORKFD, nil);
	stderr = sys->fildes(2);
	secstore->init();
	secstore->privacy();

	addr := "net!$auth!secstore";
	user := readfile("/dev/user");
	arg := load Arg Arg->PATH;
	arg->init(args);
	arg->setusage("auth/secstore [-iv] [-k key] [-p pin] [-s net!server!secstore] [-u user] [{drptx} file ...]");
	iflag := 0;
	pass, pin: string;
	while((o := arg->opt()) != 0)
		case o {
		'i' => iflag = 1;
		'k' => pass = arg->earg();
		'v' => verbose = 1;
		's' =>	addr = arg->earg();
		'u' => user = arg->earg();
		'p' => pin = arg->earg();
		* =>
			arg->usage();
		}
	args = arg->argv();
	op := -1;
	if(args != nil){
		if(len hd args != 1)
			arg->usage();
		op = (hd args)[0];
		args = tl args;
		case op {
		'd' or 'r' or 'p' or 'x' =>
			if(args == nil)
				arg->usage();
		't' =>
			;
		* =>
			arg->usage();
		}
	}
	arg = nil;

	if(iflag){
		buf := array[Secstore->Maxmsg] of byte;
		stdin := sys->fildes(0);
		for(nr := 0; nr < len buf && (n := sys->read(stdin, buf, len buf-nr)) > 0;)
			nr += n;
		s := string buf[0:nr];
		secstore->erasekey(buf[0:nr]);
		(nf, flds) := sys->tokenize(s, "\n");
		for(i := 0; i < len s; i++)
			s[i] = 0;
		if(nf < 1)
			error("no password on standard input");
		pass = hd flds;
		if(nf > 1)
			pin = hd tl flds;
	}
	conn: ref Dial->Connection;
Auth:
	for(;;){
		if(!iflag)
			pass = readpassword("secstore password");
		if(pass == nil)
			exit;
		erase();
		seckey = secstore->mkseckey(pass);
		filekey = secstore->mkfilekey(pass);
		for(i := 0; i < len pass; i++)
			pass[i] = 0;	# clear it
		conn = secstore->dial(dial->netmkaddr(addr, "net", "secstore"));
		if(conn == nil)
			error(sys->sprint("can't connect to secstore: %r"));
		(srvname, diag) := secstore->auth(conn, user, seckey);
		if(srvname == nil){
			secstore->bye(conn);
			sys->fprint(stderr, "secstore: authentication failed: %s\n",  diag);
			if(iflag)
				raise "fail:auth";
			continue;
		}
		case diag {
		"" =>
			if(verbose)
				sys->fprint(stderr, "server: %s\n", srvname);
			secstore->erasekey(seckey);
			seckey = nil;
			break Auth;
		"need pin" =>
			if(!iflag){
				pin = readpassword("STA PIN+SecureID");
				if(len pin == 0){
					sys->fprint(stderr, "cancelled");
					exit;
				}
			}else if(pin == nil)
				raise "fail:no pin";
			if(secstore->sendpin(conn, pin) < 0){
				sys->fprint(stderr, "secstore: pin rejected: %r\n");
				if(iflag)
					raise "fail:bad pin";
				continue;
			}
		}
	}
	if(op == 't'){
		erase();	# no longer need the keys
		entries := secstore->files(conn);
		for(; entries != nil; entries = tl entries){
			(name, size, date, hash, nil) := hd entries;
			if(args != nil){
				for(l := args; l != nil; l = tl l)
					if((hd args) == name)
						break;
				if(args == nil)
					continue;
			}
			if(verbose)
				sys->print("%-14q %10d %s %s\n", name, size, date, hash);
			else
				sys->print("%q\n", name);
		}
		exit;
	}
	for(; args != nil; args = tl args){
		fname := hd args;
		case op {
		'd' =>
			checkname(fname, 1);
			if(secstore->remove(conn, fname) < 0)
				error(sys->sprint("can't remove %q: %r", fname));
			verb('d', fname);
		'p' =>
			checkname(fname, 1);
			file = getfile(conn, fname, filekey);
			lines := secstore->lines(file);
			lno := 1;
			for(; lines != nil; lines = tl lines){
				l := hd lines;
				if(sys->write(sys->fildes(1), l, len l) != len l)
					sys->fprint(sys->fildes(2), "secstore (%s:%d): %r\n", fname, lno);
				lno++;
			}
			secstore->erasekey(file);
			file = nil;
			verb('p', fname);
		'x' =>
			checkname(fname, 1);
			file = getfile(conn, fname, filekey);
			ofd := sys->create(fname, Sys->OWRITE, 8r600);
			if(ofd == nil)
				error(sys->sprint("can't create %q: %r", fname));
			if(sys->write(ofd, file, len file) != len file)
				error(sys->sprint("error writing to %q: %r", fname));
			secstore->erasekey(file);
			file = nil;
			verb('x', fname);
		'r' =>
			checkname(fname, 1);
			fd := sys->open(fname, sys->OREAD);
			if(fd == nil)
				error(sys->sprint("open %q: %r", fname));
			(ok, dir) := sys->fstat(fd);
			if(ok != 0)
				error(sys->sprint("stat %q: %r", fname));
			if(int dir.length > Maxfilesize)
				error(sys->sprint("length %bd > Maxfilesize %d", dir.length, Maxfilesize));
			file = array[int dir.length] of byte;
			if(sys->readn(fd, file, len file) != len file)
				error(sys->sprint("short read: %r"));
			if(putfile(conn, fname, file, filekey) < 0)
				error(sys->sprint("putfile: %r"));
			secstore->erasekey(file);
			file = nil;
			verb('r', fname);
		* =>
			error(sys->sprint("op %c not implemented", op));
		}
	}
	erase();
}

checkname(s: string, noslash: int): string
{
	tail := s;
	for(i := 0; i < len s; i++){
		if(s[i] == '/'){
			if(noslash)
				break;
			tail = s[i+1:];
		}
		if(s[i] == '\n' || s[i] <= ' ')
			break;
	}
	if(s == nil || tail == nil || i < len s || s == "..")
		error(sys->sprint("can't use %q as a secstore file name", s));	# server checks as well, of course
	return tail;
}

verb(op: int, n: string)
{
	if(verbose)
		sys->fprint(stderr, "%c %q\n", op, n);
}

getfile(conn: ref Dial->Connection, fname: string, key: array of byte): array of byte
{
	f := secstore->getfile(conn, fname, 0);
	if(f == nil)
		error(sys->sprint("can't fetch %q: %r", fname));
	if(fname != "."){
		f = secstore->decrypt(f, key);
		if(f == nil)
			error(sys->sprint("can't decrypt %q: %r", fname));
	}
	return f;
}

putfile(conn: ref Dial->Connection, fname: string, data, key: array of byte): int
{
	data = secstore->encrypt(data, key);
	if(data == nil)
		return -1;
	return secstore->putfile(conn, fname, data);
}

erase()
{
	if(secstore != nil){
		secstore->erasekey(seckey);
		secstore->erasekey(filekey);
		secstore->erasekey(file);
	}
}

error(s: string)
{
	erase();
	sys->fprint(stderr, "secstore: %s\n", s);
	raise "fail:error";
}

readpassword(prompt: string): string
{
	cons := sys->open("/dev/cons", Sys->ORDWR);
	if(cons == nil)
		return nil;
	stdin := bufio->fopen(cons, Sys->OREAD);
	if(stdin == nil)
		return nil;
	cfd := sys->open("/dev/consctl", Sys->OWRITE);
	if (cfd == nil || sys->fprint(cfd, "rawon") <= 0)
		sys->fprint(stderr, "secstore: warning: cannot hide typed password\n");
L:
	for(;;){
		sys->fprint(cons, "%s: ", prompt);
		s := "";
		while ((c := stdin.getc()) >= 0){
			case c {
			'\n' or ('d'&8r037) =>
				sys->fprint(cons, "\n");
				return s;
			'\b' or 8r177 =>
				if(len s > 0)
					s = s[0:len s - 1];
			'u' & 8r037 =>
				sys->fprint(cons, "\n");
				continue L;
			* =>
				s[len s] = c;
			}
		}
		break;
	}
	return nil;
}

readfile(f: string): string
{
	fd := sys->open(f, Sys->OREAD);
	if(fd == nil)
		return "";
	buf := array[Sys->NAMEMAX] of byte;
	n := sys->read(fd, buf, len buf);
	if(n < 0)
		return "";
	return string buf[0:n]; 
}