xref: /plan9-contrib/sys/src/9/port/auth.c (revision 219b2ee8daee37f4aad58d63f21287faa8e4ffdc)
1 #include	"u.h"
2 #include	"../port/lib.h"
3 #include	"mem.h"
4 #include	"dat.h"
5 #include	"fns.h"
6 #include	"io.h"
7 #include	"../port/error.h"
8 
9 typedef struct Crypt	Crypt;
10 struct Crypt
11 {
12 	Crypt		*next;
13 	Ticket		t;
14 	Authenticator	a;
15 	char		tbuf[TICKETLEN];	/* remote ticket */
16 };
17 
18 typedef struct Session	Session;
19 struct Session
20 {
21 	Lock;
22 	QLock	send;
23 	Crypt	*cache;			/* cache of tickets */
24 	char	cchal[CHALLEN];		/* client challenge */
25 	char	schal[CHALLEN];		/* server challenge */
26 	char	authid[NAMELEN];	/* server encryption uid */
27 	char	authdom[DOMLEN];	/* server encryption domain */
28 	ulong	cid;			/* challenge id */
29 	int	valid;
30 };
31 
32 struct
33 {
34 	Lock;
35 	Crypt		*free;
36 } cryptalloc;
37 
38 char	eve[NAMELEN] = "bootes";
39 char	evekey[DESKEYLEN];
40 char	hostdomain[DOMLEN];
41 
42 /*
43  *  return true if current user is eve
44  */
45 int
46 iseve(void)
47 {
48 	return strcmp(eve, u->p->user) == 0;
49 }
50 
51 /*
52  * crypt entries are allocated from a pool rather than allocated using malloc so
53  * the memory can be protected from reading by devproc. The base and top of the
54  * crypt arena is stored in palloc for devproc.
55  */
56 Crypt*
57 newcrypt(void)
58 {
59 	Crypt *c;
60 
61 	lock(&cryptalloc);
62 	if(cryptalloc.free) {
63 		c = cryptalloc.free;
64 		cryptalloc.free = c->next;
65 		unlock(&cryptalloc);
66 		memset(c, 0, sizeof(Crypt));
67 		return c;
68 	}
69 
70 	cryptalloc.free = xalloc(sizeof(Crypt)*conf.nproc);
71 	if(cryptalloc.free == 0)
72 		panic("newcrypt");
73 
74 	for(c = cryptalloc.free; c < cryptalloc.free+conf.nproc-1; c++)
75 		c->next = c+1;
76 
77 	palloc.cmembase = (ulong)cryptalloc.free;
78 	palloc.cmemtop = palloc.cmembase+(sizeof(Crypt)*conf.nproc);
79 	unlock(&cryptalloc);
80 	return newcrypt();
81 }
82 
83 void
84 freecrypt(Crypt *c)
85 {
86 	lock(&cryptalloc);
87 	c->next = cryptalloc.free;
88 	cryptalloc.free = c;
89 	unlock(&cryptalloc);
90 }
91 
92 /*
93  *  return the info received in the session message on this channel.
94  *  if no session message has been exchanged, do it.
95  */
96 long
97 sysfsession(ulong *arg)
98 {
99 	int i, n;
100 	Chan *c;
101 	Crypt *cp;
102 	Session *s;
103 	Ticketreq tr;
104 	Fcall f;
105 	char buf[MAXMSG];
106 
107 	validaddr(arg[1], TICKREQLEN, 1);
108 	c = fdtochan(arg[0], OWRITE, 0, 1);
109 	if(waserror()){
110 		close(c);
111 		nexterror();
112 	}
113 
114 	/* add a session structure to the channel if it has none */
115 	lock(c);
116 	s = c->session;
117 	if(s == 0){
118 		s = malloc(sizeof(Session));
119 		if(s == 0){
120 			unlock(c);
121 			error(Enomem);
122 		}
123 		c->session = s;
124 	}
125 	unlock(c);
126 
127 	qlock(&s->send);
128 	if(s->valid == 0){
129 		/*
130 		 *  Exchange a session message with the server.
131 		 */
132 		for(i = 0; i < CHALLEN; i++)
133 			s->cchal[i] = nrand(256);
134 
135 		f.tag = NOTAG;
136 		f.type = Tsession;
137 		memmove(f.chal, s->cchal, CHALLEN);
138 		n = convS2M(&f, buf);
139 
140 		/*
141 		 *  If an error occurs reading or writing,
142 		 *  this probably is a mount of a mount so turn off
143 		 *  authentication.
144 		 */
145 		if(waserror())
146 			goto noauth;
147 
148 		if((*devtab[c->type].write)(c, buf, n, 0) != n)
149 			error(Esession);
150 		n = (*devtab[c->type].read)(c, buf, sizeof buf, 0);
151 		/* OK is sometimes sent as a Datakit sign-on */
152 		if(n == 2 && buf[0] == 'O' && buf[1] == 'K')
153 			n = (*devtab[c->type].read)(c, buf, sizeof buf, 0);
154 
155 		poperror();
156 
157 		if(convM2S(buf, &f, n) == 0){
158 			qunlock(&s->send);
159 			error(Esession);
160 		}
161 		switch(f.type){
162 		case Rsession:
163 			memmove(s->schal, f.chal, CHALLEN);
164 			memmove(s->authid, f.authid, NAMELEN);
165 			memmove(s->authdom, f.authdom, DOMLEN);
166 			break;
167 		case Rerror:
168 			qunlock(&s->send);
169 			error(f.ename);
170 		default:
171 			qunlock(&s->send);
172 			error(Esession);
173 		}
174 noauth:
175 		s->valid = 1;
176 	}
177 	qunlock(&s->send);
178 
179 	/*
180 	 *  If server requires no ticket, or user is "none", or a ticket
181 	 *  is already cached, zero the request type
182 	 */
183 	tr.type = AuthTreq;
184 	if(strcmp(u->p->user, "none") == 0 || s->authid[0] == 0)
185 		tr.type = 0;
186 	else{
187 		lock(s);
188 		for(cp = s->cache; cp; cp = cp->next)
189 			if(strcmp(cp->t.cuid, u->p->user) == 0){
190 				tr.type = 0;
191 				break;
192 			}
193 		unlock(s);
194 	}
195 
196 	/*  create ticket request */
197 	memmove(tr.chal, s->schal, CHALLEN);
198 	memmove(tr.authid, s->authid, NAMELEN);
199 	memmove(tr.authdom, s->authdom, DOMLEN);
200 	memmove(tr.uid, u->p->user, NAMELEN);
201 	memmove(tr.hostid, eve, NAMELEN);
202 	convTR2M(&tr, (char*)arg[1]);
203 
204 	close(c);
205 	poperror();
206 	return 0;
207 }
208 
209 /*
210  *  attach tickets to a session
211  */
212 long
213 sysfauth(ulong *arg)
214 {
215 	Chan *c;
216 	char *ta;
217 	Session *s;
218 	Crypt *cp, *ncp, **l;
219 	char tbuf[2*TICKETLEN];
220 
221 	validaddr(arg[1], 2*TICKETLEN, 0);
222 	c = fdtochan(arg[0], OWRITE, 0, 1);
223 	s = c->session;
224 	if(s == 0)
225 		error("fauth must follow fsession");
226 	cp = newcrypt();
227 	if(waserror()){
228 		freecrypt(cp);
229 		nexterror();
230 	}
231 
232 	/*  ticket supplied, use it */
233 	ta = (char*)arg[1];
234 	memmove(tbuf, ta, 2*TICKETLEN);
235 	convM2T(tbuf, &cp->t, evekey);
236 	if(cp->t.num != AuthTc)
237 		error("bad AuthTc in ticket");
238 	if(strncmp(u->p->user, cp->t.cuid, NAMELEN) != 0)
239 		error("bad uid in ticket");
240 	if(memcmp(cp->t.chal, s->schal, CHALLEN) != 0)
241 		error("bad chal in ticket");
242 	memmove(cp->tbuf, tbuf+TICKETLEN, TICKETLEN);
243 
244 	/* string onto local list, replace old version */
245 	lock(s);
246 	l = &s->cache;
247 	for(ncp = s->cache; ncp; ncp = *l){
248 		if(strcmp(ncp->t.cuid, u->p->user) == 0){
249 			*l = ncp->next;
250 			freecrypt(ncp);
251 			break;
252 		}
253 		l = &ncp->next;
254 	}
255 	cp->next = s->cache;
256 	s->cache = cp;
257 	unlock(s);
258 	poperror();
259 	return 0;
260 }
261 
262 /*
263  *  free a session created by fsession
264  */
265 void
266 freesession(Session *s)
267 {
268 	Crypt *cp, *next;
269 
270 	for(cp = s->cache; cp; cp = next) {
271 		next = cp->next;
272 		freecrypt(cp);
273 	}
274 	free(s);
275 }
276 
277 /*
278  *  called by mattach() to fill in the Tattach message
279  */
280 ulong
281 authrequest(Session *s, Fcall *f)
282 {
283 	Crypt *cp;
284 	ulong id, dofree;
285 
286 	/* no authentication if user is "none" or if no ticket required by remote */
287 	if(s == 0 || s->authid[0] == 0 || strcmp(u->p->user, "none") == 0){
288 		memset(f->ticket, 0, TICKETLEN);
289 		memset(f->auth, 0, AUTHENTLEN);
290 		return 0;
291 	}
292 
293 	/* look for ticket in cache */
294 	dofree = 0;
295 	lock(s);
296 	for(cp = s->cache; cp; cp = cp->next)
297 		if(strcmp(cp->t.cuid, u->p->user) == 0)
298 			break;
299 
300 	id = s->cid++;
301 	unlock(s);
302 
303 	if(cp == 0){
304 		/*
305 		 *  create a ticket using hostkey, this solves the
306 		 *  chicken and egg problem
307 		 */
308 		cp = newcrypt();
309 		cp->t.num = AuthTs;
310 		memmove(cp->t.chal, s->schal, CHALLEN);
311 		memmove(cp->t.cuid, u->p->user, NAMELEN);
312 		memmove(cp->t.suid, u->p->user, NAMELEN);
313 		memmove(cp->t.key, evekey, DESKEYLEN);
314 		convT2M(&cp->t, f->ticket, evekey);
315 		dofree = 1;
316 	} else
317 		memmove(f->ticket, cp->tbuf, TICKETLEN);
318 
319 	/* create an authenticator */
320 	memmove(cp->a.chal, s->schal, CHALLEN);
321 	cp->a.num = AuthAc;
322 	cp->a.id = id;
323 	convA2M(&cp->a, f->auth, cp->t.key);
324 	if(dofree)
325 		freecrypt(cp);
326 	return id;
327 }
328 
329 /*
330  *  called by mattach() to check the Rattach message
331  */
332 void
333 authreply(Session *s, ulong id, Fcall *f)
334 {
335 	Crypt *cp;
336 
337 	if(s == 0)
338 		return;
339 
340 	lock(s);
341 	for(cp = s->cache; cp; cp = cp->next)
342 		if(strcmp(cp->t.cuid, u->p->user) == 0)
343 			break;
344 	unlock(s);
345 
346 	/* we're getting around authentication */
347 	if(s == 0 || cp == 0 || s->authid[0] == 0 || strcmp(u->p->user, "none") == 0)
348 		return;
349 
350 	convM2A(f->rauth, &cp->a, cp->t.key);
351 	if(cp->a.num != AuthAs){
352 		print("bad encryption type\n");
353 		error("server lies");
354 	}
355 	if(memcmp(cp->a.chal, s->cchal, sizeof(cp->a.chal))){
356 		print("bad returned challenge\n");
357 		error("server lies");
358 	}
359 	if(cp->a.id != id){
360 		print("bad returned id\n");
361 		error("server lies");
362 	}
363 }
364 
365 /*
366  *  called by devcons() for #c/authenticate
367  *
368  *  The protocol is
369  *	1) read ticket request from #c/authenticate
370  *	2) write ticket+authenticator to #c/authenticate. if it matches
371  *	  the challenge the user is changed to the suid field of the ticket
372  *	3) read authenticator (to confirm this is the server advertised)
373  */
374 long
375 authread(Chan *c, char *a, int n)
376 {
377 	Crypt *cp;
378 	int i;
379 	Ticketreq tr;
380 
381 	if(c->aux == 0){
382 		/*
383 		 *  first read returns a ticket request
384 		 */
385 		if(n != TICKREQLEN)
386 			error(Ebadarg);
387 		c->aux = newcrypt();
388 		cp = c->aux;
389 
390 		memset(&tr, 0, sizeof(tr));
391 		tr.type = AuthTreq;
392 		strcpy(tr.hostid, eve);
393 		strcpy(tr.authid, eve);
394 		strcpy(tr.authdom, hostdomain);
395 		strcpy(tr.uid, u->p->user);
396 		for(i = 0; i < CHALLEN; i++)
397 			tr.chal[i] = nrand(256);
398 		memmove(cp->a.chal, tr.chal, CHALLEN);
399 		convTR2M(&tr, a);
400 	} else {
401 		/*
402 		 *  subsequent read returns an authenticator
403 		 */
404 		if(n != AUTHENTLEN)
405 			error(Ebadarg);
406 		cp = c->aux;
407 
408 		cp->a.num = AuthAs;
409 		memmove(cp->a.chal, cp->t.chal, CHALLEN);
410 		cp->a.id = 0;
411 		convA2M(&cp->a, cp->tbuf, cp->t.key);
412 		memmove(a, cp->tbuf, AUTHENTLEN);
413 
414 		freecrypt(cp);
415 		c->aux = 0;
416 	}
417 	return n;
418 }
419 
420 long
421 authwrite(Chan *c, char *a, int n)
422 {
423 	Crypt *cp;
424 
425 	if(n != TICKETLEN+AUTHENTLEN)
426 		error(Ebadarg);
427 	if(c->aux == 0)
428 		error(Ebadarg);
429 	cp = c->aux;
430 
431 	memmove(cp->tbuf, a, TICKETLEN);
432 	convM2T(cp->tbuf, &cp->t, evekey);
433 	if(cp->t.num != AuthTs || memcmp(cp->a.chal, cp->t.chal, CHALLEN))
434 		error(Eperm);
435 
436 	memmove(cp->tbuf, a+TICKETLEN, AUTHENTLEN);
437 	convM2A(cp->tbuf, &cp->a, cp->t.key);
438 	if(cp->a.num != AuthAc || memcmp(cp->a.chal, cp->t.chal, CHALLEN))
439 		error(Eperm);
440 
441 	memmove(u->p->user, cp->t.suid, NAMELEN);
442 	return n;
443 }
444 
445 /*
446  *  called by devcons() for #c/authcheck
447  *
448  *  a write of a ticket+authenticator [+challenge+id] succeeds if they match
449  */
450 long
451 authcheck(Chan *c, char *a, int n)
452 {
453 	Crypt *cp;
454 	char *chal;
455 	ulong id;
456 
457 	if(n != TICKETLEN+AUTHENTLEN && n != TICKETLEN+AUTHENTLEN+CHALLEN+4)
458 		error(Ebadarg);
459 	if(c->aux == 0)
460 		c->aux = newcrypt();
461 	cp = c->aux;
462 
463 	memmove(cp->tbuf, a, TICKETLEN);
464 	convM2T(cp->tbuf, &cp->t, evekey);
465 	if(cp->t.num != AuthTc)
466 		error(Ebadarg);
467 	if(strcmp(u->p->user, cp->t.cuid))
468 		error(cp->t.cuid);
469 
470 	memmove(cp->tbuf, a+TICKETLEN, AUTHENTLEN);
471 	convM2A(cp->tbuf, &cp->a, cp->t.key);
472 	if(n == TICKETLEN+AUTHENTLEN+CHALLEN+4){
473 		uchar *p = (uchar *)&a[TICKETLEN+AUTHENTLEN+CHALLEN];
474 		id = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
475 		chal = &a[TICKETLEN+AUTHENTLEN];
476 	}else{
477 		id = 0;
478 		chal = cp->t.chal;
479 	}
480 	if(cp->a.num != AuthAs || memcmp(chal, cp->a.chal, CHALLEN) || cp->a.id != id)
481 		error(Eperm);
482 
483 	return n;
484 }
485 
486 /*
487  *  called by devcons() for #c/authenticator
488  *
489  *  a read after a write of a ticket (or ticket+id) returns an authenticator
490  *  for that ticket.
491  */
492 long
493 authentwrite(Chan *c, char *a, int n)
494 {
495 	Crypt *cp;
496 
497 	if(n != TICKETLEN && n != TICKETLEN+4)
498 		error(Ebadarg);
499 	if(c->aux == 0)
500 		c->aux = newcrypt();
501 	cp = c->aux;
502 
503 	memmove(cp->tbuf, a, TICKETLEN);
504 	convM2T(cp->tbuf, &cp->t, evekey);
505 	if(cp->t.num != AuthTc || strcmp(cp->t.cuid, u->p->user)){
506 		freecrypt(cp);
507 		c->aux = 0;
508 		error(Ebadarg);
509 	}
510 	if(n == TICKETLEN+4){
511 		uchar *p = (uchar *)&a[TICKETLEN];
512 		cp->a.id = p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
513 	}else
514 		cp->a.id = 0;
515 
516 	return n;
517 }
518 long
519 authentread(Chan *c, char *a, int n)
520 {
521 	Crypt *cp;
522 
523 	cp = c->aux;
524 	if(cp == 0)
525 		error("authenticator read must follow a write");
526 
527 	cp->a.num = AuthAc;
528 	memmove(cp->a.chal, cp->t.chal, CHALLEN);
529 	convA2M(&cp->a, cp->tbuf, cp->t.key);
530 	memmove(a, cp->tbuf, AUTHENTLEN);
531 
532 	return n;
533 }
534 
535 void
536 authclose(Chan *c)
537 {
538 	if(c->aux)
539 		freecrypt(c->aux);
540 	c->aux = 0;
541 }
542 
543 /*
544  *  called by devcons() for key device
545  */
546 long
547 keyread(char *a, int n, long offset)
548 {
549 	if(n<DESKEYLEN || offset != 0)
550 		error(Ebadarg);
551 	if(!cpuserver || !iseve())
552 		error(Eperm);
553 	memmove(a, evekey, DESKEYLEN);
554 	return DESKEYLEN;
555 }
556 
557 long
558 keywrite(char *a, int n)
559 {
560 	if(n != DESKEYLEN)
561 		error(Ebadarg);
562 	if(!iseve())
563 		error(Eperm);
564 	memmove(evekey, a, DESKEYLEN);
565 	return DESKEYLEN;
566 }
567 
568 /*
569  *  called by devcons() for user device
570  *
571  *  anyone can become none
572  */
573 long
574 userwrite(char *a, int n)
575 {
576 	if(n >= NAMELEN)
577 		error(Ebadarg);
578 	if(strcmp(a, "none") != 0)
579 		error(Eperm);
580 	memset(u->p->user, 0, NAMELEN);
581 	strcpy(u->p->user, "none");
582 	return n;
583 }
584 
585 /*
586  *  called by devcons() for host owner/domain
587  *
588  *  writing hostowner also sets user
589  */
590 long
591 hostownerwrite(char *a, int n)
592 {
593 	char buf[NAMELEN];
594 
595 	if(!iseve())
596 		error(Eperm);
597 	if(n >= NAMELEN)
598 		error(Ebadarg);
599 	memset(buf, 0, NAMELEN);
600 	strncpy(buf, a, n);
601 	if(buf[0] == 0)
602 		error(Ebadarg);
603 	memmove(eve, buf, NAMELEN);
604 	memmove(u->p->user, buf, NAMELEN);
605 	return n;
606 }
607 
608 long
609 hostdomainwrite(char *a, int n)
610 {
611 	char buf[DOMLEN];
612 
613 	if(!iseve())
614 		error(Eperm);
615 	if(n >= DOMLEN)
616 		error(Ebadarg);
617 	memset(buf, 0, DOMLEN);
618 	strncpy(buf, a, n);
619 	if(buf[0] == 0)
620 		error(Ebadarg);
621 	memmove(hostdomain, buf, DOMLEN);
622 	return n;
623 }
624 
625 void
626 wipekeys(void)
627 {
628 	memset(evekey, 0, sizeof(evekey));
629 	memset((void*)palloc.cmembase, 0, palloc.cmemtop - palloc.cmembase);
630 }
631