xref: /inferno-os/appl/cmd/auth/changelogin.b (revision 3f1f06c5d12b24c4061e5123acabf72348ff45a2)
1implement Changelogin;
2
3include "sys.m";
4	sys: Sys;
5
6include "daytime.m";
7	daytime: Daytime;
8
9include "draw.m";
10
11include "keyring.m";
12	kr: Keyring;
13
14Changelogin: module
15{
16	init:	fn(ctxt: ref Draw->Context, argv: list of string);
17};
18
19stderr, stdin, stdout: ref Sys->FD;
20keydb := "/mnt/keys";
21
22init(nil: ref Draw->Context, args: list of string)
23{
24	ok: int;
25	word: string;
26
27	sys = load Sys Sys->PATH;
28	kr = load Keyring Keyring->PATH;
29
30	stdin = sys->fildes(0);
31	stdout = sys->fildes(1);
32	stderr = sys->fildes(2);
33
34	argv0 := hd args;
35	args = tl args;
36
37	if(args == nil){
38		sys->fprint(stderr, "usage: %s userid\n", argv0);
39		raise "fail:usage";
40	}
41
42	daytime = load Daytime Daytime->PATH;
43	if(daytime == nil) {
44		sys->fprint(stderr, "%s: can't load Daytime: %r\n", argv0);
45		raise "fail:load";
46	}
47
48	# get password
49	id := hd args;
50	(dbdir, secret, expiry, err) := getuser(id);
51	if(dbdir == nil){
52		if(err != nil){
53			sys->fprint(stderr, "%s: can't get auth info for %s in %s: %s\n", argv0, id, keydb, err);
54			raise "fail:no key";
55		}
56		sys->print("new account\n");
57	}
58	for(;;){
59		if(secret != nil)
60			sys->print("secret [default = don't change]: ");
61		else
62			sys->print("secret: ");
63		(ok, word) = readline(stdin, "rawon");
64		if(!ok)
65			exit;
66		if(word == "" && secret != nil)
67			break;
68		if(len word >= 8)
69			break;
70		sys->print("!secret must be at least 8 characters\n");
71	}
72	newsecret: array of byte;
73	if(word != ""){
74		# confirm password change
75		word1 := word;
76		sys->print("confirm: ");
77		(ok, word) = readline(stdin, "rawon");
78		if(!ok || word != word1) {
79			sys->print("Entries do not match. Authinfo record unchanged.\n");
80			raise "fail:mismatch";
81		}
82
83		pwbuf := array of byte word;
84		newsecret = array[Keyring->SHA1dlen] of byte;
85		kr->sha1(pwbuf, len pwbuf, newsecret, nil);
86	}
87
88	# get expiration time (midnight of date specified)
89	maxdate := "17012038";			# largest date possible without incurring integer overflow
90	now := daytime->now();
91	tm := daytime->local(now);
92	tm.sec = 59;
93	tm.min = 59;
94	tm.hour = 23;
95	tm.year += 1;
96	if(dbdir == nil)
97		expsecs := daytime->tm2epoch(tm);	# set expiration date to 23:59:59 one year from today
98	else
99		expsecs = expiry;
100	for(;;){
101		defexpdate := "permanent";
102		if(expsecs != 0) {
103			otm := daytime->local(expsecs);
104			defexpdate = sys->sprint("%2.2d%2.2d%4.4d", otm.mday, otm.mon+1, otm.year+1900);
105		}
106		sys->print("expires [DDMMYYYY/permanent, return = %s]: ", defexpdate);
107		(ok, word) = readline(stdin, "rawoff");
108		if(!ok)
109			exit;
110		if(word == "")
111			word = defexpdate;
112		if(word == "permanent"){
113			expsecs = 0;
114			break;
115		}
116		if(len word != 8){
117			sys->print("!bad date format %s\n", word);
118			continue;
119		}
120		tm.mday = int word[0:2];
121		if(tm.mday > 31 || tm.mday < 1){
122			sys->print("!bad day of month %d\n", tm.mday);
123			continue;
124		}
125		tm.mon = int word[2:4] - 1;
126		if(tm.mon > 11 || tm.mday < 0){
127			sys->print("!bad month %d\n", tm.mon + 1);
128			continue;
129		}
130		tm.year = int word[4:8] - 1900;
131		if(tm.year < 70){
132			sys->print("!bad year %d (year may be no earlier than 1970)\n", tm.year + 1900);
133			continue;
134		}
135		expsecs = daytime->tm2epoch(tm);
136		if(expsecs > now)
137			break;
138		else {
139			newexpdate := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900);
140			tm          = daytime->local(daytime->now());
141			today      := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900);
142			sys->print("!bad expiration date %s (must be between %s and %s)\n", newexpdate, today, maxdate);
143			expsecs = now;
144		}
145	}
146	newexpiry := expsecs;
147
148#	# get the free form field
149#	if(pw != nil)
150#		npw.other = pw.other;
151#	else
152#		npw.other = "";
153#	sys->print("free form info [return = %s]: ", npw.other);
154#	(ok, word) = readline(stdin,"rawoff");
155#	if(!ok)
156#		exit;
157#	if(word != "")
158#		npw.other = word;
159
160	if(dbdir == nil){
161		dbdir = keydb+"/"+id;
162		fd := sys->create(dbdir, Sys->OREAD, Sys->DMDIR|8r700);
163		if(fd == nil){
164			sys->fprint(stderr, "%s: can't create account %s: %r\n", argv0, id);
165			raise "fail:create user";
166		}
167	}
168	changed := 0;
169	if(!eq(newsecret, secret)){
170		if(putsecret(dbdir, newsecret) < 0){
171			sys->fprint(stderr, "%s: can't update secret for %s: %r\n", argv0, id);
172			raise "fail:update";
173		}
174		changed = 1;
175	}
176	if(newexpiry != expiry){
177		if(putexpiry(dbdir, newexpiry) < 0){
178			sys->fprint(stderr, "%s: can't update expiry time for %s: %r\n", argv0, id);
179			raise "fail:update";
180		}
181		changed = 1;
182	}
183	sys->print("change written\n");
184}
185
186getuser(id: string): (string, array of byte, int, string)
187{
188	(ok, nil) := sys->stat(keydb);
189	if(ok < 0)
190		return (nil, nil, 0, sys->sprint("can't stat %s: %r", id));
191	dbdir := keydb+"/"+id;
192	(ok, nil) = sys->stat(dbdir);
193	if(ok < 0)
194		return (nil, nil, 0, nil);
195	fd := sys->open(dbdir+"/secret", Sys->OREAD);
196	if(fd == nil)
197		return (nil, nil, 0, sys->sprint("can't open %s/secret: %r", id));
198	d: Sys->Dir;
199	(ok, d) = sys->fstat(fd);
200	if(ok < 0)
201		return (nil, nil, 0, sys->sprint("can't stat %s/secret: %r", id));
202	l := int d.length;
203	secret: array of byte;
204	if(l > 0){
205		secret = array[l] of byte;
206		if(sys->read(fd, secret, len secret) != len secret)
207			return (nil, nil, 0, sys->sprint("error reading %s/secret: %r", id));
208	}
209	fd = sys->open(dbdir+"/expire", Sys->OREAD);
210	if(fd == nil)
211		return (nil, nil, 0, sys->sprint("can't open %s/expiry: %r", id));
212	b := array[32] of byte;
213	n := sys->read(fd, b, len b);
214	if(n <= 0)
215		return (nil, nil, 0, sys->sprint("error reading %s/expiry: %r", id));
216	return (dbdir, secret, int string b[0:n], nil);
217}
218
219eq(a, b: array of byte): int
220{
221	if(len a != len b)
222		return 0;
223	for(i := 0; i < len a; i++)
224		if(a[i] != b[i])
225			return 0;
226	return 1;
227}
228
229putsecret(dir: string, secret: array of byte): int
230{
231	fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600);
232	if(fd == nil)
233		return -1;
234	return sys->write(fd, secret, len secret);
235}
236
237putexpiry(dir: string, expiry: int): int
238{
239	fd := sys->open(dir+"/expire", Sys->OWRITE);
240	if(fd == nil)
241		return -1;
242	return sys->fprint(fd, "%d", expiry);
243}
244
245readline(io: ref Sys->FD, mode: string): (int, string)
246{
247	r : int;
248	line : string;
249	buf := array[8192] of byte;
250	fdctl : ref Sys->FD;
251	rawoff := array of byte "rawoff";
252
253	#
254	# Change console mode to rawon
255	#
256	if(mode == "rawon"){
257		fdctl = sys->open("/dev/consctl", sys->OWRITE);
258		if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){
259			sys->fprint(stderr, "unable to change console mode");
260			return (0,nil);
261		}
262	}
263
264	#
265	# Read up to the CRLF
266	#
267	line = "";
268	for(;;) {
269		r = sys->read(io, buf, len buf);
270		if(r <= 0){
271			sys->fprint(stderr, "error read from console mode");
272			if(mode == "rawon")
273				sys->write(fdctl,rawoff,6);
274			return (0, nil);
275		}
276
277		line += string buf[0:r];
278		if ((len line >= 1) && (line[(len line)-1] == '\n')){
279			if(mode == "rawon"){
280				r = sys->write(stdout,array of byte "\n",1);
281				if(r <= 0) {
282					sys->write(fdctl,rawoff,6);
283					return (0, nil);
284				}
285			}
286			break;
287		}
288		else {
289			if(mode == "rawon"){
290				#r = sys->write(stdout, array of byte "*",1);
291				if(r <= 0) {
292					sys->write(fdctl,rawoff,6);
293					return (0, nil);
294				}
295			}
296		}
297	}
298
299	if(mode == "rawon")
300		sys->write(fdctl,rawoff,6);
301
302	# Total success!
303	return (1, line[0:len line - 1]);
304}
305