code: 9ferno

ref: da7d6df6faf18e289fe0f3f61524dcc7fddeef18
dir: /appl/cmd/auth/changelogin.b/

View raw version
implement Changelogin;

include "sys.m";
	sys: Sys;

include "daytime.m";
	daytime: Daytime;

include "draw.m";

include "keyring.m";
	kr: Keyring;

Changelogin: module
{
	init:	fn(ctxt: ref Draw->Context, argv: list of string);
};

stderr, stdin, stdout: ref Sys->FD;
keydb := "/mnt/keys";

init(nil: ref Draw->Context, args: list of string)
{
	ok: int;
	word: string;

	sys = load Sys Sys->PATH;
	kr = load Keyring Keyring->PATH;

	stdin = sys->fildes(0);
	stdout = sys->fildes(1);
	stderr = sys->fildes(2);

	argv0 := hd args;
	args = tl args;

	if(args == nil){
		sys->fprint(stderr, "usage: %s userid\n", argv0);
		raise "fail:usage";
	}

	daytime = load Daytime Daytime->PATH;
	if(daytime == nil) {
		sys->fprint(stderr, "%s: can't load Daytime: %r\n", argv0);
		raise "fail:load";
	}

	# get password
	id := hd args;
	(dbdir, secret, expiry, err) := getuser(id);
	if(dbdir == nil){
		if(err != nil){
			sys->fprint(stderr, "%s: can't get auth info for %s in %s: %s\n", argv0, id, keydb, err);
			raise "fail:no key";
		}
		sys->print("new account\n");
	}
	for(;;){
		if(secret != nil)
			sys->print("secret [default = don't change]: ");
		else
			sys->print("secret: ");
		(ok, word) = readline(stdin, "rawon");
		if(!ok)
			exit;
		if(word == "" && secret != nil)
			break;
		if(len word >= 8)
			break;
		sys->print("!secret must be at least 8 characters\n");
	}
	newsecret: array of byte;
	if(word != ""){
		# confirm password change
		word1 := word;
		sys->print("confirm: ");
		(ok, word) = readline(stdin, "rawon");
		if(!ok || word != word1) {
			sys->print("Entries do not match. Authinfo record unchanged.\n"); 
			raise "fail:mismatch";
		}

		pwbuf := array of byte word;
		newsecret = array[Keyring->SHA1dlen] of byte;
		kr->sha1(pwbuf, len pwbuf, newsecret, nil);
	}

	# get expiration time (midnight of date specified)
	maxdate := "17012038";			# largest date possible without incurring integer overflow
	now := daytime->now();
	tm := daytime->local(now);
	tm.sec = 59;
	tm.min = 59;
	tm.hour = 23;
	tm.year += 1;
	if(dbdir == nil)
		expsecs := daytime->tm2epoch(tm);	# set expiration date to 23:59:59 one year from today
	else
		expsecs = expiry;
	for(;;){
		defexpdate := "permanent";
		if(expsecs != 0) {
			otm := daytime->local(expsecs);
			defexpdate = sys->sprint("%2.2d%2.2d%4.4d", otm.mday, otm.mon+1, otm.year+1900);
		}
		sys->print("expires [DDMMYYYY/permanent, return = %s]: ", defexpdate);
		(ok, word) = readline(stdin, "rawoff");
		if(!ok)
			exit;
		if(word == "")
			word = defexpdate;
		if(word == "permanent"){
			expsecs = 0;
			break;
		}
		if(len word != 8){
			sys->print("!bad date format %s\n", word);
			continue;
		}
		tm.mday = int word[0:2];
		if(tm.mday > 31 || tm.mday < 1){
			sys->print("!bad day of month %d\n", tm.mday);
			continue;
		}
		tm.mon = int word[2:4] - 1;
		if(tm.mon > 11 || tm.mday < 0){
			sys->print("!bad month %d\n", tm.mon + 1);
			continue;
		}
		tm.year = int word[4:8] - 1900;
		if(tm.year < 70){
			sys->print("!bad year %d (year may be no earlier than 1970)\n", tm.year + 1900);
			continue;
		}
		expsecs = daytime->tm2epoch(tm);
		if(expsecs > now)
			break;
		else {
			newexpdate := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900);
			tm          = daytime->local(daytime->now());
			today      := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900);
			sys->print("!bad expiration date %s (must be between %s and %s)\n", newexpdate, today, maxdate);
			expsecs = now;
		}
	}
	newexpiry := expsecs;

#	# get the free form field
#	if(pw != nil)
#		npw.other = pw.other;
#	else
#		npw.other = "";
#	sys->print("free form info [return = %s]: ", npw.other);
#	(ok, word) = readline(stdin,"rawoff");
#	if(!ok)
#		exit;
#	if(word != "")
#		npw.other = word;

	if(dbdir == nil){
		dbdir = keydb+"/"+id;
		fd := sys->create(dbdir, Sys->OREAD, Sys->DMDIR|8r700);
		if(fd == nil){
			sys->fprint(stderr, "%s: can't create account %s: %r\n", argv0, id);
			raise "fail:create user";
		}
	}
	changed := 0;
	if(!eq(newsecret, secret)){
		if(putsecret(dbdir, newsecret) < 0){
			sys->fprint(stderr, "%s: can't update secret for %s: %r\n", argv0, id);
			raise "fail:update";
		}
		changed = 1;
	}
	if(newexpiry != expiry){
		if(putexpiry(dbdir, newexpiry) < 0){
			sys->fprint(stderr, "%s: can't update expiry time for %s: %r\n", argv0, id);
			raise "fail:update";
		}
		changed = 1;
	}
	sys->print("change written\n");
}

getuser(id: string): (string, array of byte, int, string)
{
	(ok, nil) := sys->stat(keydb);
	if(ok < 0)
		return (nil, nil, 0, sys->sprint("can't stat %s: %r", id));
	dbdir := keydb+"/"+id;
	(ok, nil) = sys->stat(dbdir);
	if(ok < 0)
		return (nil, nil, 0, nil);
	fd := sys->open(dbdir+"/secret", Sys->OREAD);
	if(fd == nil)
		return (nil, nil, 0, sys->sprint("can't open %s/secret: %r", id));
	d: Sys->Dir;
	(ok, d) = sys->fstat(fd);
	if(ok < 0)
		return (nil, nil, 0, 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, 0, sys->sprint("error reading %s/secret: %r", id));
	}
	fd = sys->open(dbdir+"/expire", Sys->OREAD);
	if(fd == nil)
		return (nil, nil, 0, sys->sprint("can't open %s/expiry: %r", id));
	b := array[32] of byte;
	n := sys->read(fd, b, len b);
	if(n <= 0)
		return (nil, nil, 0, sys->sprint("error reading %s/expiry: %r", id));
	return (dbdir, secret, int string b[0:n], nil);
}

eq(a, b: array of byte): int
{
	if(len a != len b)
		return 0;
	for(i := 0; i < len a; i++)
		if(a[i] != b[i])
			return 0;
	return 1;
}

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);
}

putexpiry(dir: string, expiry: int): int
{
	fd := sys->open(dir+"/expire", Sys->OWRITE);
	if(fd == nil)
		return -1;
	return sys->fprint(fd, "%d", expiry);
}

readline(io: ref Sys->FD, mode: string): (int, string)
{
	r : int;
	line : string;
	buf := array[8192] of byte;
	fdctl : ref Sys->FD;
	rawoff := array of byte "rawoff";

	#
	# Change console mode to rawon
	#
	if(mode == "rawon"){
		fdctl = sys->open("/dev/consctl", sys->OWRITE);
		if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){
			sys->fprint(stderr, "unable to change console mode ⇒ %r\n");
			return (0,nil);
		}
	}

	#
	# Read up to the CRLF
	#
	line = "";
	for(;;) {
		r = sys->read(io, buf, len buf);
		if(r <= 0){
			sys->fprint(stderr, "error read from console mode ⇒ %r\n");
			if(mode == "rawon")
				sys->write(fdctl,rawoff,6);
			return (0, nil);
		}

		line += string buf[0:r];
		if ((len line >= 1) && (line[(len line)-1] == '\n')){
			if(mode == "rawon"){
				r = sys->write(stdout,array of byte "\n",1);
				if(r <= 0) {
					sys->write(fdctl,rawoff,6);
					return (0, nil);
				}
			}
			break;
		}
		else {
			if(mode == "rawon"){
				#r = sys->write(stdout, array of byte "*",1);
				if(r <= 0) {
					sys->write(fdctl,rawoff,6);
					return (0, nil);
				}
			}
		}
	}

	if(mode == "rawon")
		sys->write(fdctl,rawoff,6);

	# Total success!
	return (1, line[0:len line - 1]);
}