code: purgatorio

ref: 71292834bbafcf6bc409cfd7691f9e6650df025b
dir: /appl/cmd/auth/keysrv.b/

View raw version
implement Keysrv;

#
# remote access to keys (currently only to change secret)
#
# Copyright © 2003 Vita Nuova Holdings Limited.  All rights reserved.
#

include "sys.m";
	sys: Sys;

include "draw.m";

include "keyring.m";
	kr: Keyring;

include "security.m";
	auth: Auth;

include "arg.m";

keydb := "/mnt/keys";

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

usage()
{
	sys->fprint(sys->fildes(2), "usage: keysrv\n");
	raise "fail:usage";
}

init(nil: ref Draw->Context, args: list of string)
{
	sys = load Sys Sys->PATH;
	if(sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil) < 0)
		err(sys->sprint("can't fork name space: %r"));

	keyfile := "/usr/"+user()+"/keyring/default";

	arg := load Arg Arg->PATH;
	if(arg == nil)
		err("can't load Arg");
	arg->init(args);
	while((o := arg->opt()) != 0)
		case o {
		'k' =>
			keyfile = arg->arg();
		* =>
			usage();
		}
	args = arg->argv();
	arg = nil;

	kr = load Keyring Keyring->PATH;
	if(kr == nil)
		err("can't load Keyring");

	auth = load Auth Auth->PATH;
	if(auth == nil)
		err("can't load Auth");
	auth->init();

	ai := kr->readauthinfo(keyfile);
	if(ai == nil)
		err(sys->sprint("can't read server key file %s: %r", keyfile));

	(fd, id_or_err) := auth->server("sha1" :: "rc4_256" :: nil, ai, sys->fildes(0), 0);
	if(fd == nil)
		err(sys->sprint("can't authenticate: %s", id_or_err));

	if(sys->bind("#s", "/mnt/keysrv", Sys->MREPL) < 0)
		err(sys->sprint("can't bind #s on /mnt/keysrv: %r"));
	srv := sys->file2chan("/mnt/keysrv", "secret");
	if(srv == nil)
		err(sys->sprint("can't create file2chan on /mnt/keysrv: %r"));
	exitc := chan of int;
	spawn worker(srv, id_or_err, exitc);
	if(sys->export(fd, "/mnt/keysrv", Sys->EXPWAIT) < 0){
		exitc <-= 1;
		err(sys->sprint("can't export %s: %r", "/mnt/keysrv"));
	}
	exitc <-= 1;
}

err(s: string)
{
	sys->fprint(sys->fildes(2), "keysrv: %s\n", s);
	raise "fail:error";
}

user(): string
{
	fd := sys->open("/dev/user", Sys->OREAD);
	if(fd == nil)
		err(sys->sprint("can't open /dev/user: %r"));

	buf := array[Sys->NAMEMAX] of byte;
	n := sys->read(fd, buf, len buf);
	if(n < 0)
		err(sys->sprint("error reading /dev/user: %r"));

	return string buf[0:n];	
}

worker(file: ref Sys->FileIO, user: string, exitc: chan of int)
{
	(keydir, secret, err) := getuser(user);
	if(keydir == nil || secret == nil){
		if(err == nil)
			err = "no existing secret";		# can't change it remotely until set
	}
	(nil, hash) := hashkey(secret);
	for(;;)alt{
	<-exitc =>
		exit;
	(nil, nil, nil, rc) := <-file.read =>
		if(rc == nil)
			break;
		if(err != nil){
			rc <-= (nil, err);
			break;
		}
		rc <-= (nil, nil);
	(nil, data, nil, wc) := <-file.write =>
		if(wc == nil)
			break;
		if(err != nil){
			wc <-= (0, err);
			break;
		}
		for(i := 0; i < len data; i++)
			if(data[i] == byte ' ')
				break;
		if(string data[0:i] != hash){
			wc <-= (0, "wrong secret");
			break;
		}
		if(++i >= len data){
			wc <-= (0, nil);
			break;
		}
		if(len data - i < 8){
			wc <-= (0, "unacceptable secret");
			break;
		}
		if(putsecret(keydir, data[i:]) < 0){
			wc <-= (0, sys->sprint("can't update secret: %r"));
			break;
		}
		wc <-= (len data, nil);
	}
}

hashkey(a: array of byte): (array of byte, string)
{
	hash := array[Keyring->SHA1dlen] of byte;
	kr->sha1(a, len a, hash, nil);
	s := "";
	for(i := 0; i < len hash; i++)
		s += sys->sprint("%2.2ux", int hash[i]);
	return (hash, s);
}

getuser(id: string): (string, array of byte, string)
{
	(ok, nil) := sys->stat(keydb);
	if(ok < 0)
		return (nil, nil, sys->sprint("can't stat %s: %r", id));
	dbdir := keydb+"/"+id;
	(ok, nil) = sys->stat(dbdir);
	if(ok < 0)
		return (nil, nil, sys->sprint("user not registered: %s", id));
	fd := sys->open(dbdir+"/secret", Sys->OREAD);
	if(fd == nil)
		return (nil, nil, sys->sprint("can't open %s/secret: %r", id));
	d: Sys->Dir;
	(ok, d) = sys->fstat(fd);
	if(ok < 0)
		return (nil, nil, sys->sprint("can't stat %s/secret: %r", id));
	l := int d.length;
	secret: array of byte;
	if(l > 0){
		secret = array[l] of byte;
		if(sys->read(fd, secret, len secret) != len secret)
			return (nil, nil, sys->sprint("error reading %s/secret: %r", id));
	}
	return (dbdir, secret, nil);
}

putsecret(dir: string, secret: array of byte): int
{
	fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600);
	if(fd == nil)
		return -1;
	return sys->write(fd, secret, len secret);
}