xref: /plan9-contrib/sys/src/cmd/auth/keyfs.c (revision 219b2ee8daee37f4aad58d63f21287faa8e4ffdc)
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5 #include <bio.h>
6 #include "authsrv.h"
7 
8 char authkey[DESKEYLEN];
9 
10 typedef struct Fid	Fid;
11 typedef struct User	User;
12 
13 enum{
14 	Qroot,
15 	Quser,
16 	Qkey,
17 	Qlog,
18 	Qstatus,
19 	Qexpire,
20 	Qwarnings,
21 	Qmax,
22 
23 	Nuser	= 512,
24 	MAXBAD	= 50,			/* max number of bad attempts before diabling the account */
25 };
26 
27 enum{
28 	Sok,
29 	Sdisabled,
30 	Smax,
31 };
32 
33 struct Fid{
34 	int	fid;
35 	ulong	qtype;
36 	User	*user;
37 	int	busy;
38 	Fid	*next;
39 };
40 
41 struct User{
42 	char	name[NAMELEN];
43 	char	key[DESKEYLEN];
44 	ulong	expire;			/* 0 == never */
45 	uchar	status;
46 	uchar	bad;			/* number of consecutive bad authentication attempts */
47 	int	ref;
48 	char	removed;
49 	uchar	warnings;
50 	ulong	uniq;
51 	User	*link;
52 };
53 
54 char	*qinfo[Qmax] = {
55 	[Qroot]		"keys",
56 	[Quser]		".",
57 	[Qkey]		"key",
58 	[Qlog]		"log",
59 	[Qexpire]	"expire",
60 	[Qstatus]	"status",
61 	[Qwarnings]	"warnings",
62 };
63 
64 char	*status[Smax] = {
65 	[Sok]		"ok",
66 	[Sdisabled]	"disabled",
67 };
68 
69 Fid	*fids;
70 User	*users[Nuser];
71 char	*userkeys;
72 int	nuser;
73 ulong	uniq = 1;
74 Fcall	rhdr,
75 	thdr;
76 int	usepass;
77 char	*warnarg;
78 
79 int	readusers(void);
80 ulong	hash(char*);
81 Fid	*findfid(int);
82 User	*finduser(char*);
83 User	*installuser(char*);
84 int	removeuser(User*);
85 void	insertuser(User*);
86 void	writeusers(void);
87 void	io(int, int);
88 void	*emalloc(ulong);
89 Qid	mkqid(User*, ulong);
90 void	dostat(User*, ulong, void*);
91 int	newkeys(void);
92 void	warning(void);
93 
94 char	*Auth(Fid*), *Attach(Fid*), *Nop(Fid*), *Session(Fid*),
95 	*Flush(Fid*), *Clone(Fid*), *Walk(Fid*),
96 	*Clwalk(Fid *), *Open(Fid*), *Create(Fid*),
97 	*Read(Fid *), *Write(Fid*), *Clunk(Fid*),
98 	*Remove(Fid *), *Stat(Fid*), *Wstat(Fid*);
99 char 	*(*fcalls[])(Fid*) = {
100 	[Tattach]	Attach,
101 	[Tclone]	Clone,
102 	[Tclunk]	Clunk,
103 	[Tclwalk]	Clwalk,
104 	[Tcreate]	Create,
105 	[Tflush]	Flush,
106 	[Tnop]		Nop,
107 	[Topen]		Open,
108 	[Tread]		Read,
109 	[Tremove]	Remove,
110 	[Tsession]	Session,
111 	[Tstat]		Stat,
112 	[Twalk]		Walk,
113 	[Twrite]	Write,
114 	[Twstat]	Wstat,
115 };
116 
117 void
118 main(int argc, char *argv[])
119 {
120 	char *mntpt;
121 	int p[2];
122 
123 	mntpt = "/mnt/keys";
124 	ARGBEGIN{
125 	case 'm':
126 		mntpt = ARGF();
127 		break;
128 	case 'w':
129 		warnarg = ARGF();
130 		break;
131 	case 'p':
132 		usepass = 1;
133 		break;
134 	}ARGEND
135 	argv0 = "keyfs";
136 
137 	userkeys = "/adm/keys";
138 	if(argc > 0)
139 		userkeys = argv[0];
140 
141 	if(pipe(p) < 0)
142 		error("can't make pipe: %r");
143 
144 	switch(fork()){
145 	case 0:
146 		io(p[1], p[1]);
147 		exits(0);
148 	case -1:
149 		error("fork");
150 	default:
151 		if(mount(p[0], mntpt, MREPL|MCREATE, "") < 0)
152 			error("can't mount: %r");
153 		exits(0);
154 	}
155 }
156 
157 char *
158 Nop(Fid *f)
159 {
160 	USED(f);
161 	return 0;
162 }
163 
164 char *
165 Session(Fid *f)
166 {
167 	USED(f);
168 	return 0;
169 }
170 
171 char *
172 Flush(Fid *f)
173 {
174 	USED(f);
175 	return 0;
176 }
177 
178 char *
179 Auth(Fid *f)
180 {
181 	if(f->busy)
182 		Clunk(f);
183 	f->user = 0;
184 	f->qtype = Qroot;
185 	f->busy = 1;
186 	strcpy(thdr.chal, "none");
187 	return 0;
188 }
189 
190 char *
191 Attach(Fid *f)
192 {
193 	if(f->busy)
194 		Clunk(f);
195 	f->user = 0;
196 	f->qtype = Qroot;
197 	f->busy = 1;
198 	thdr.qid = mkqid(f->user, f->qtype);
199 	return 0;
200 }
201 
202 char *
203 Clone(Fid *f)
204 {
205 	Fid *nf;
206 
207 	if(!f->busy)
208 		return "permission denied";
209 	nf = findfid(rhdr.newfid);
210 	if(nf->busy)
211 		Clunk(nf);
212 	thdr.fid = rhdr.newfid;
213 	nf->busy = 1;
214 	nf->qtype = f->qtype;
215 	if(nf->user = f->user)
216 		nf->user->ref++;
217 	return 0;
218 }
219 
220 char *
221 Walk(Fid *f)
222 {
223 	char *name;
224 	int i, max;
225 
226 	if(!f->busy)
227 		return "permission denied";
228 	name = rhdr.name;
229 	switch(f->qtype){
230 	case Qroot:
231 		f->user = finduser(name);
232 		if(!f->user)
233 			return "file does not exist";
234 		f->user->ref++;
235 		f->qtype = Quser;
236 		break;
237 	case Quser:
238 		max = Qmax;
239 		for(i = Quser + 1; i < Qmax; i++)
240 			if(strcmp(name, qinfo[i]) == 0){
241 				f->qtype = i;
242 				break;
243 			}
244 		if(i == max)
245 			return "file not found";
246 		break;
247 	default:
248 		return "file is not a directory";
249 	}
250 	thdr.qid = mkqid(f->user, f->qtype);
251 	return 0;
252 }
253 
254 char *
255 Clunk(Fid *f)
256 {
257 	f->busy = 0;
258 	if(f->user && --f->user->ref == 0 && f->user->removed)
259 		free(f->user);
260 	f->user = 0;
261 	return 0;
262 }
263 
264 char *
265 Clwalk(Fid *f)
266 {
267 	Fid *nf;
268 	char *err;
269 
270 	if(!f->busy)
271 		return "permission denied";
272 	nf = findfid(rhdr.newfid);
273 	thdr.fid = rhdr.newfid;
274 	if(nf->busy)
275 		Clunk(nf);
276 	nf->busy = 1;
277 	nf->qtype = f->qtype;
278 	if(nf->user = f->user)
279 		nf->user->ref++;
280 	if(err = Walk(nf))
281 		Clunk(nf);
282 	return err;
283 }
284 
285 char *
286 Open(Fid *f)
287 {
288 	int mode;
289 
290 	if(!f->busy)
291 		return "permission denied";
292 	mode = rhdr.mode;
293 	if(f->qtype == Quser && (mode & (OWRITE|OTRUNC)))
294 		return "user already exists";
295 	thdr.qid = mkqid(f->user, f->qtype);
296 	return 0;
297 }
298 
299 char *
300 Create(Fid *f)
301 {
302 	char *name;
303 	long perm;
304 
305 	if(!f->busy)
306 		return "permission denied";
307 	name = rhdr.name;
308 	if(f->user){
309 		return "permission denied";
310 	}else{
311 		perm = rhdr.perm;
312 		if(!(perm & CHDIR))
313 			return "permission denied";
314 		if(!memchr(name, '\0', NAMELEN))
315 			return "bad file name";
316 		if(finduser(name))
317 			return "user already exists";
318 		f->user = installuser(name);
319 		f->user->ref++;
320 		f->qtype = Quser;
321 	}
322 	thdr.qid = mkqid(f->user, f->qtype);
323 	writeusers();
324 	return 0;
325 }
326 
327 char *
328 Read(Fid *f)
329 {
330 	User *u;
331 	char *data;
332 	ulong off, n;
333 	int i, j, max;
334 
335 	if(!f->busy)
336 		return "permission denied";
337 	n = rhdr.count;
338 	off = rhdr.offset;
339 	thdr.count = 0;
340 	data = thdr.data;
341 	switch(f->qtype){
342 	case Qroot:
343 		if(off % DIRLEN || n % DIRLEN)
344 			return "unaligned directory read";
345 		off /= DIRLEN;
346 		n /= DIRLEN;
347 		j = 0;
348 		for(i = 0; i < Nuser; i++)
349 			for(u = users[i]; u; j++, u = u->link){
350 				if(j < off)
351 					continue;
352 				if(j - off >= n)
353 					break;
354 				dostat(u, Quser, data);
355 				data += DIRLEN;
356 			}
357 		thdr.count = data - thdr.data;
358 		return 0;
359 	case Quser:
360 		if(off % DIRLEN || n % DIRLEN)
361 			return "unaligned directory read";
362 		off /= DIRLEN;
363 		n /= DIRLEN;
364 		max = Qmax;
365 		max -= Quser + 1;
366 		for(i = 0; i < max; i++){
367 			if(i < off)
368 				continue;
369 			if(i - off >= n)
370 				break;
371 			dostat(f->user, i - off + Quser + 1, data);
372 			data += DIRLEN;
373 		}
374 		thdr.count = data - thdr.data;
375 		return 0;
376 	case Qkey:
377 		if(f->user->status != Sok)
378 			return "user disabled";
379 		if(f->user->expire != 0 && f->user->expire < time(0))
380 			return "user expired";
381 		if(off != 0)
382 			return 0;
383 		if(n > DESKEYLEN)
384 			n = DESKEYLEN;
385 		memmove(thdr.data, f->user->key, n);
386 		thdr.count = n;
387 		return 0;
388 	case Qstatus:
389 		if(off != 0){
390 			thdr.count = 0;
391 			return 0;
392 		}
393 		if(f->user->status == Sok && f->user->expire && f->user->expire < time(0))
394 			sprint(thdr.data, "expired\n");
395 		else
396 			sprint(thdr.data, "%s\n", status[f->user->status]);
397 		thdr.count = strlen(thdr.data);
398 		return 0;
399 	case Qexpire:
400 		if(off != 0){
401 			thdr.count = 0;
402 			return 0;
403 		}
404 		if(!f->user->expire)
405 			strcpy(data, "never\n");
406 		else
407 			sprint(data, "%lud\n", f->user->expire);
408 		if(n > strlen(data))
409 			n = strlen(data);
410 		thdr.count = n;
411 		return 0;
412 	case Qlog:
413 		if(off != 0){
414 			thdr.count = 0;
415 			return 0;
416 		}
417 		sprint(data, "%d\n", f->user->bad);
418 		if(n > strlen(data))
419 			n = strlen(data);
420 		thdr.count = n;
421 		return 0;
422 	case Qwarnings:
423 		if(off != 0){
424 			thdr.count = 0;
425 			return 0;
426 		}
427 		sprint(data, "%lud\n", f->user->warnings);
428 		if(n > strlen(data))
429 			n = strlen(data);
430 		thdr.count = n;
431 		return 0;
432 	default:
433 		return "permission denied";
434 	}
435 }
436 
437 char *
438 Write(Fid *f)
439 {
440 	char *data, *p;
441 	ulong n, expire;
442 	int i;
443 
444 	if(!f->busy)
445 		return "permission denied";
446 	n = rhdr.count;
447 	data = rhdr.data;
448 	switch(f->qtype){
449 	case Qkey:
450 		if(n != DESKEYLEN)
451 			return "garbled write data";
452 		memmove(f->user->key, data, DESKEYLEN);
453 		thdr.count = DESKEYLEN;
454 		break;
455 	case Qstatus:
456 		data[n] = '\0';
457 		if(p = strchr(data, '\n'))
458 			*p = '\0';
459 		for(i = 0; i < Smax; i++)
460 			if(strcmp(data, status[i]) == 0){
461 				f->user->status = i;
462 				break;
463 			}
464 		if(i == Smax)
465 			return "unknown status";
466 		f->user->bad = 0;
467 		thdr.count = n;
468 		break;
469 	case Qexpire:
470 		data[n] = '\0';
471 		if(p = strchr(data, '\n'))
472 			*p = '\0';
473 		else
474 			p = &data[n];
475 		if(strcmp(data, "never") == 0)
476 			expire = 0;
477 		else{
478 			expire = strtoul(data, &data, 10);
479 			if(data != p)
480 				return "bad expiration date";
481 		}
482 		f->user->expire = expire;
483 		f->user->warnings = 0;
484 		thdr.count = n;
485 		break;
486 	case Qlog:
487 		data[n] = '\0';
488 		if(strcmp(data, "good") == 0)
489 			f->user->bad = 0;
490 		else
491 			f->user->bad++;
492 		if(f->user->bad >= MAXBAD){
493 			f->user->status = Sdisabled;
494 			break;
495 		}
496 		return 0;
497 	case Qwarnings:
498 		data[n] = '\0';
499 		f->user->warnings = strtoul(data, 0, 10);
500 		thdr.count = n;
501 		break;
502 	case Qroot:
503 	case Quser:
504 	default:
505 		return "permission denied";
506 	}
507 	writeusers();
508 	return 0;
509 }
510 
511 char *
512 Remove(Fid *f)
513 {
514 	if(!f->busy)
515 		return "permission denied";
516 	if(f->qtype == Qwarnings)
517 		f->user->warnings = 0;
518 	else if(f->qtype == Quser)
519 		removeuser(f->user);
520 	else
521 		return "permission denied";
522 	Clunk(f);
523 	writeusers();
524 	return 0;
525 }
526 
527 char *
528 Stat(Fid *f)
529 {
530 	if(!f->busy)
531 		return "stat on unattached fid";
532 	dostat(f->user, f->qtype, thdr.stat);
533 	return 0;
534 }
535 
536 char *
537 Wstat(Fid *f)
538 {
539 	Dir d;
540 
541 	if(!f->busy || f->qtype != Quser)
542 		return "permission denied";
543 	if(convM2D(rhdr.stat, &d) == 0)
544 		return "bad stat buffer";
545 	if(!memchr(d.name, '\0', NAMELEN))
546 		return "bad user name";
547 	if(finduser(d.name))
548 		return "user already exists";
549 	if(!removeuser(f->user))
550 		return "user previously removed";
551 	strncpy(f->user->name, d.name, NAMELEN);
552 	insertuser(f->user);
553 	writeusers();
554 	return 0;
555 }
556 
557 Qid
558 mkqid(User *u, ulong qtype)
559 {
560 	Qid q;
561 
562 	q.vers = 0;
563 	q.path = qtype;
564 	if(u)
565 		q.path |= u->uniq * 0x100;
566 	if(qtype == Quser || qtype == Qroot)
567 		q.path |= CHDIR;
568 	return q;
569 }
570 
571 void
572 dostat(User *user, ulong qtype, void *p)
573 {
574 	Dir d;
575 
576 	if(qtype == Quser)
577 		strncpy(d.name, user->name, NAMELEN);
578 	else
579 		strncpy(d.name, qinfo[qtype], NAMELEN);
580 	strncpy(d.uid, "auth", NAMELEN);
581 	strncpy(d.gid, "auth", NAMELEN);
582 	d.qid = mkqid(user, qtype);
583 	if(d.qid.path & CHDIR)
584 		d.mode = 0777|CHDIR;
585 	else
586 		d.mode = 0666;
587 	d.atime = d.mtime = time(0);
588 	d.length = d.hlength = 0;
589 	convD2M(&d, p);
590 }
591 
592 int
593 passline(Biobuf *b, void *vbuf)
594 {
595 	char *buf = vbuf;
596 
597 	if(Bread(b, buf, KEYDBLEN) != KEYDBLEN)
598 		return 0;
599 	decrypt(authkey, buf, KEYDBLEN);
600 	buf[NAMELEN-1] = '\0';
601 	return 1;
602 }
603 
604 void
605 writeusers(void)
606 {
607 	User *u;
608 	Biobuf *b;
609 	char buf[KEYDBLEN], *p;
610 	ulong expire;
611 	int i;
612 
613 	b = Bopen(userkeys, OWRITE);
614 	if(!b){
615 		fprint(2, "keyfs: can't write keys file\n");
616 		return;
617 	}
618 	for(i = 0; i < Nuser; i++)
619 		for(u = users[i]; u; u = u->link){
620 			strncpy(buf, u->name, NAMELEN);
621 			memmove(buf+NAMELEN, u->key, DESKEYLEN);
622 			p = buf + NAMELEN + DESKEYLEN;
623 			*p++ = u->status;
624 			*p++ = u->warnings;
625 			expire = u->expire;
626 			*p++ = expire;
627 			*p++ = expire >> 8;
628 			*p++ = expire >> 16;
629 			*p = expire >> 24;
630 			encrypt(authkey, buf, KEYDBLEN);
631 			Bwrite(b, buf, sizeof buf);
632 		}
633 	Bterm(b);
634 }
635 
636 int
637 readusers(void)
638 {
639 	Biobuf *b;
640 	User *u;
641 	char buf[KEYDBLEN];
642 	uchar *p;
643 	int nu;
644 
645 	if(usepass)
646 		getpass(authkey, 0);
647 	else
648 		if(!getauthkey(authkey))
649 			print("keyfs: warning: can't read /dev/key\n");
650 
651 	nu = 0;
652 	b = Bopen(userkeys, OREAD);
653 	if(b){
654 		while(passline(b, buf)){
655 			u = finduser(buf);
656 			if(u == 0)
657 				u = installuser(buf);
658 			memmove(u->key, buf+NAMELEN, DESKEYLEN);
659 			p = (uchar*)buf + NAMELEN + DESKEYLEN;
660 			u->status = *p++;
661 			u->warnings = *p++;
662 			if(u->status >= Smax)
663 				fprint(2, "keyfs: warning: bad status in key file\n");
664 			u->expire = p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
665 			nu++;
666 		}
667 		Bterm(b);
668 	}
669 	print("%d keys read\n", nu);
670 	return b != 0;
671 }
672 
673 User *
674 installuser(char *name)
675 {
676 	User *u;
677 	int h;
678 
679 	h = hash(name);
680 	u = emalloc(sizeof *u);
681 	strncpy(u->name, name, NAMELEN);
682 	u->removed = 0;
683 	u->ref = 0;
684 	u->expire = 0;
685 	u->status = Sok;
686 	u->bad = 0;
687 	u->warnings = 0;
688 	u->uniq = uniq++;
689 	u->link = users[h];
690 	users[h] = u;
691 	return u;
692 }
693 
694 User *
695 finduser(char *name)
696 {
697 	User *u;
698 
699 	for(u = users[hash(name)]; u; u = u->link)
700 		if(strcmp(name, u->name) == 0)
701 			return u;
702 	return 0;
703 }
704 
705 int
706 removeuser(User *user)
707 {
708 	User *u, **last;
709 	char *name;
710 
711 	user->removed = 1;
712 	name = user->name;
713 	last = &users[hash(name)];
714 	for(u = *last; u; u = *last){
715 		if(strcmp(name, u->name) == 0){
716 			*last = u->link;
717 			return 1;
718 		}
719 		last = &u->link;
720 	}
721 	return 0;
722 }
723 
724 void
725 insertuser(User *user)
726 {
727 	int h;
728 
729 	user->removed = 0;
730 	h = hash(user->name);
731 	user->link = users[h];
732 	users[h] = user;
733 }
734 
735 ulong
736 hash(char *s)
737 {
738 	ulong h;
739 
740 	h = 0;
741 	while(*s)
742 		h = (h << 1) ^ *s++;
743 	return h % Nuser;
744 }
745 
746 Fid *
747 findfid(int fid)
748 {
749 	Fid *f, *ff;
750 
751 	ff = 0;
752 	for(f = fids; f; f = f->next)
753 		if(f->fid == fid)
754 			return f;
755 		else if(!ff && !f->busy)
756 			ff = f;
757 	if(ff){
758 		ff->fid = fid;
759 		return ff;
760 	}
761 	f = emalloc(sizeof *f);
762 	f->fid = fid;
763 	f->busy = 0;
764 	f->user = 0;
765 	f->next = fids;
766 	fids = f;
767 	return f;
768 }
769 
770 void
771 io(int in, int out)
772 {
773 	char mdata[MAXFDATA + MAXMSG], *err;
774 	int n;
775 	static long lastwarning;
776 
777 	for(;;){
778 		n = read(in, mdata, sizeof mdata);
779 		if(n == 0)
780 			continue;
781 		if(n < 0)
782 			error("mount read %d", n);
783 		if(convM2S(mdata, &rhdr, n) == 0)
784 			continue;
785 
786 		if(newkeys())
787 			readusers();
788 
789 		thdr.data = mdata + MAXMSG;
790 		thdr.fid = rhdr.fid;
791 		if(!fcalls[rhdr.type])
792 			err = "fcall request";
793 		else
794 			err = (*fcalls[rhdr.type])(findfid(rhdr.fid));
795 		thdr.tag = rhdr.tag;
796 		thdr.type = rhdr.type+1;
797 		if(err){
798 			thdr.type = Rerror;
799 			strncpy(thdr.ename, err, ERRLEN);
800 		}
801 		n = convS2M(&thdr, mdata);
802 		if(write(out, mdata, n) != n)
803 			error("mount write");
804 
805 		if(warnarg && time(0) - lastwarning > 24*60*60){
806 			warning();
807 			lastwarning = time(0);
808 		}
809 	}
810 }
811 
812 int
813 newkeys(void)
814 {
815 	Dir d;
816 	static long ftime;
817 
818 	if(dirstat(userkeys, &d) < 0)
819 		return 0;
820 	if(d.mtime > ftime) {
821 		ftime = d.mtime;
822 		return 1;
823 	}
824 	return 0;
825 }
826 
827 void *
828 emalloc(ulong n)
829 {
830 	void *p;
831 
832 	if(p = malloc(n))
833 		return p;
834 	error("out of memory");
835 	return 0;		/* not reached */
836 }
837 
838 void
839 warning(void)
840 {
841 	char buf[64];
842 
843 	snprint(buf, sizeof buf, "-%s", warnarg);
844 	switch(rfork(RFPROC|RFNOTEG|RFNOWAIT|RFNAMEG|RFENVG|RFFDG)){
845 	case 0:
846 		execl("/bin/auth/warning", "warning", warnarg, 0);
847 		fprint(2, "keyfs: can't exec warning\n");
848 		exits(0);
849 	}
850 }
851