]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/auth/keyfs.c
show line numbers in dtracy type errors
[plan9front.git] / sys / src / cmd / auth / keyfs.c
1 /*
2  * keyfs
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <ctype.h>
7 #include <fcall.h>
8 #include <bio.h>
9 #include <libsec.h>
10 #include <authsrv.h>
11 #include "authcmdlib.h"
12
13 #pragma varargck        type    "W"     char*
14
15 Authkey authkey;
16 int     keydbaes;
17 uchar   zeros[16];
18
19 typedef struct Fid      Fid;
20 typedef struct User     User;
21
22 enum {
23         Qroot,
24         Quser,
25         Qkey,
26         Qaeskey,
27         Qpakhash,
28         Qsecret,
29         Qlog,
30         Qstatus,
31         Qexpire,
32         Qwarnings,
33         Qmax,
34
35         Nuser   = 512,
36         MAXBAD  = 10,   /* max # of bad attempts before disabling the account */
37         /* file must be randomly addressible, so names have fixed length */
38         Namelen = ANAMELEN,
39 };
40
41 enum {
42         Sok,
43         Sdisabled,
44         Smax,
45 };
46
47 struct Fid {
48         int     fid;
49         ulong   qtype;
50         User    *user;
51         int     busy;
52         Fid     *next;
53 };
54
55 struct User {
56         char    *name;
57         Authkey key;
58         char    secret[SECRETLEN];
59         ulong   expire;                 /* 0 == never */
60         uchar   status;
61         ulong   bad;            /* # of consecutive bad authentication attempts */
62         int     ref;
63         char    removed;
64         uchar   warnings;
65         ulong   purgatory;              /* time purgatory ends */
66         ulong   uniq;
67         User    *link;
68 };
69
70 char    *qinfo[Qmax] = {
71         [Qroot]         "keys",
72         [Quser]         ".",
73         [Qkey]          "key",
74         [Qaeskey]       "aeskey",
75         [Qpakhash]      "pakhash",
76         [Qsecret]       "secret",
77         [Qlog]          "log",
78         [Qexpire]       "expire",
79         [Qstatus]       "status",
80         [Qwarnings]     "warnings",
81 };
82
83 char    *status[Smax] = {
84         [Sok]           "ok",
85         [Sdisabled]     "disabled",
86 };
87
88 Fid     *fids;
89 User    *users[Nuser];
90 char    *userkeys;
91 int     nuser;
92 ulong   uniq = 1;
93 Fcall   rhdr, thdr;
94 int     usepass;
95 int     readonly;
96 char    *warnarg;
97 uchar   mdata[8192 + IOHDRSZ];
98 int     messagesize = sizeof mdata;
99
100 int     readusers(void);
101 ulong   hash(char*);
102 Fid     *findfid(int);
103 User    *finduser(char*);
104 User    *installuser(char*);
105 int     removeuser(User*);
106 void    insertuser(User*);
107 void    writeusers(void);
108 void    io(int, int);
109 void    *emalloc(ulong);
110 char    *estrdup(char*);
111 Qid     mkqid(User*, ulong);
112 int     dostat(User*, ulong, void*, int);
113 int     newkeys(void);
114 void    warning(void);
115 int     weirdfmt(Fmt *f);
116
117 char    *Auth(Fid*), *Attach(Fid*), *Version(Fid*),
118         *Flush(Fid*), *Walk(Fid*),
119         *Open(Fid*), *Create(Fid*),
120         *Read(Fid *), *Write(Fid*), *Clunk(Fid*),
121         *Remove(Fid *), *Stat(Fid*), *Wstat(Fid*);
122 char    *(*fcalls[])(Fid*) = {
123         [Tattach]       Attach,
124         [Tauth] Auth,
125         [Tclunk]        Clunk,
126         [Tcreate]       Create,
127         [Tflush]        Flush,
128         [Topen]         Open,
129         [Tread]         Read,
130         [Tremove]       Remove,
131         [Tstat]         Stat,
132         [Tversion]      Version,
133         [Twalk]         Walk,
134         [Twrite]        Write,
135         [Twstat]        Wstat,
136 };
137
138 static void
139 usage(void)
140 {
141         fprint(2, "usage: %s [-p] [-r] [-m mtpt] [-w warn] [keyfile]\n", argv0);
142         exits("usage");
143 }
144
145 static int
146 haveaeskey(void)
147 {
148         return memcmp(authkey.aes, zeros, 16) != 0;
149 }
150
151 void
152 main(int argc, char *argv[])
153 {
154         char *mntpt;
155         int p[2];
156
157         fmtinstall('W', weirdfmt);
158         mntpt = "/mnt/keys";
159         ARGBEGIN{
160         case 'm':
161                 mntpt = EARGF(usage());
162                 break;
163         case 'p':
164                 usepass = 1;
165                 break;
166         case 'w':
167                 warnarg = EARGF(usage());
168                 break;
169         case 'r':
170                 readonly = 1;
171                 break;
172         default:
173                 usage();
174                 break;
175         }ARGEND
176         argv0 = "keyfs";
177
178         userkeys = "/adm/keys";
179         if(argc > 1)
180                 usage();
181         if(argc == 1)
182                 userkeys = argv[0];
183
184         if(pipe(p) < 0)
185                 error("can't make pipe: %r");
186
187         private();
188         if(usepass)
189                 getpass(&authkey, nil, 0, 0);
190         else {
191                 if(!getauthkey(&authkey))
192                         fprint(2, "keyfs: warning: can't read NVRAM\n");
193         }
194
195         keydbaes = 0;
196         if(!newkeys() || !readusers()){
197                 if(!keydbaes)
198                         keydbaes = haveaeskey();
199                 else if(!haveaeskey()){
200                         fprint(2, "keyfs: no aes key in NVRAM\n");
201                         getpass(&authkey, nil, 0, 0);
202                         readusers();
203                 }
204         }
205
206         switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG)){
207         case 0:
208                 close(p[0]);
209                 io(p[1], p[1]);
210                 exits(0);
211         case -1:
212                 error("fork");
213         default:
214                 close(p[1]);
215                 if(mount(p[0], -1, mntpt, MREPL|MCREATE, "") < 0)
216                         error("can't mount: %r");
217                 exits(0);
218         }
219 }
220
221 char *
222 Flush(Fid *f)
223 {
224         USED(f);
225         return 0;
226 }
227
228 char *
229 Auth(Fid *)
230 {
231         return "keyfs: authentication not required";
232 }
233
234 char *
235 Attach(Fid *f)
236 {
237         if(f->busy)
238                 Clunk(f);
239         f->user = nil;
240         f->qtype = Qroot;
241         f->busy = 1;
242         thdr.qid = mkqid(f->user, f->qtype);
243         return 0;
244 }
245
246 char*
247 Version(Fid*)
248 {
249         Fid *f;
250
251         for(f = fids; f; f = f->next)
252                 if(f->busy)
253                         Clunk(f);
254         if(rhdr.msize < 256)
255                 return "message size too small";
256         if(rhdr.msize > sizeof mdata)
257                 thdr.msize = sizeof mdata;
258         else
259                 thdr.msize = rhdr.msize;
260         messagesize = thdr.msize;
261         if(strncmp(rhdr.version, "9P2000", 6) != 0)
262                 return "bad 9P version";
263         thdr.version = "9P2000";
264         return 0;
265 }
266
267 char *
268 Walk(Fid *f)
269 {
270         char *name, *err;
271         int i, j, max;
272         Fid *nf;
273         ulong qtype;
274         User *user;
275
276         if(!f->busy)
277                 return "walk of unused fid";
278         nf = nil;
279         qtype = f->qtype;
280         user = f->user;
281         if(rhdr.fid != rhdr.newfid){
282                 nf = findfid(rhdr.newfid);
283                 if(nf->busy)
284                         return "fid in use";
285                 f = nf; /* walk f */
286         }
287
288         err = nil;
289         i = 0;
290         if(rhdr.nwname > 0){
291                 for(; i<rhdr.nwname; i++){
292                         if(i >= MAXWELEM){
293                                 err = "too many path name elements";
294                                 break;
295                         }
296                         name = rhdr.wname[i];
297                         switch(qtype){
298                         case Qroot:
299                                 if(strcmp(name, "..") == 0)
300                                         goto Accept;
301                                 user = finduser(name);
302                                 if(user == nil)
303                                         goto Out;
304                                 qtype = Quser;
305
306                         Accept:
307                                 thdr.wqid[i] = mkqid(user, qtype);
308                                 break;
309
310                         case Quser:
311                                 if(strcmp(name, "..") == 0) {
312                                         qtype = Qroot;
313                                         user = nil;
314                                         goto Accept;
315                                 }
316                                 max = Qmax;
317                                 for(j = Quser + 1; j < Qmax; j++)
318                                         if(strcmp(name, qinfo[j]) == 0){
319                                                 qtype = j;
320                                                 break;
321                                         }
322                                 if(j < max)
323                                         goto Accept;
324                                 goto Out;
325
326                         default:
327                                 err = "file is not a directory";
328                                 goto Out;
329                         }
330                 }
331             Out:
332                 if(i < rhdr.nwname && err == nil)
333                         err = "file not found";
334         }
335
336         if(err != nil){
337                 return err;
338         }
339
340         /* if we cloned and then completed the walk, update new fid */
341         if(rhdr.fid != rhdr.newfid && i == rhdr.nwname){
342                 nf->busy = 1;
343                 nf->qtype = qtype;
344                 nf->user = user;
345                 if(user != nil)
346                         user->ref++;
347         }else if(nf == nil && rhdr.nwname > 0){ /* walk without clone (rare) */
348                 Clunk(f);
349                 f->busy = 1;
350                 f->qtype = qtype;
351                 f->user = user;
352                 if(user != nil)
353                         user->ref++;
354         }
355
356         thdr.nwqid = i;
357         return 0;
358 }
359
360 char *
361 Clunk(Fid *f)
362 {
363         f->busy = 0;
364         if(f->user != nil && --f->user->ref == 0 && f->user->removed) {
365                 free(f->user->name);
366                 free(f->user);
367         }
368         f->user = nil;
369         return nil;
370 }
371
372 char *
373 Open(Fid *f)
374 {
375         int mode;
376
377         if(!f->busy)
378                 return "open of unused fid";
379         mode = rhdr.mode;
380         if(f->qtype == Quser && (mode & (OWRITE|OTRUNC)))
381                 return "user already exists";
382         if((f->qtype == Qaeskey || f->qtype == Qpakhash) && !keydbaes)
383                 return "keyfile not in aes format";
384         thdr.qid = mkqid(f->user, f->qtype);
385         thdr.iounit = messagesize - IOHDRSZ;
386         return 0;
387 }
388
389 char *
390 Create(Fid *f)
391 {
392         char *name;
393         long perm;
394
395         if(!f->busy)
396                 return "create of unused fid";
397         if(readonly)
398                 return "mounted read-only";
399         name = rhdr.name;
400         if(f->user != nil){
401                 return "permission denied";
402         }else{
403                 perm = rhdr.perm;
404                 if(!(perm & DMDIR))
405                         return "permission denied";
406                 if(strcmp(name, "") == 0)
407                         return "empty file name";
408                 if(strlen(name) >= Namelen)
409                         return "file name too long";
410                 if(finduser(name) != nil)
411                         return "user already exists";
412                 f->user = installuser(name);
413                 f->user->ref++;
414                 f->qtype = Quser;
415         }
416         thdr.qid = mkqid(f->user, f->qtype);
417         thdr.iounit = messagesize - IOHDRSZ;
418         writeusers();
419         return 0;
420 }
421
422 char *
423 Read(Fid *f)
424 {
425         User *u;
426         char *data;
427         ulong off, n, m;
428         int i, j, max;
429
430         if(!f->busy)
431                 return "read of unused fid";
432         n = rhdr.count;
433         off = rhdr.offset;
434         thdr.count = 0;
435         data = thdr.data;
436         switch(f->qtype){
437         case Qroot:
438                 j = 0;
439                 for(i = 0; i < Nuser; i++)
440                         for(u = users[i]; u != nil; j += m, u = u->link){
441                                 m = dostat(u, Quser, data, n);
442                                 if(m <= BIT16SZ)
443                                         break;
444                                 if(j < off)
445                                         continue;
446                                 data += m;
447                                 n -= m;
448                         }
449                 thdr.count = data - thdr.data;
450                 return 0;
451         case Quser:
452                 max = Qmax;
453                 max -= Quser + 1;
454                 j = 0;
455                 for(i = 0; i < max; j += m, i++){
456                         m = dostat(f->user, i + Quser + 1, data, n);
457                         if(m <= BIT16SZ)
458                                 break;
459                         if(j < off)
460                                 continue;
461                         data += m;
462                         n -= m;
463                 }
464                 thdr.count = data - thdr.data;
465                 return 0;
466         case Qkey:
467         case Qaeskey:
468         case Qpakhash:
469         case Qsecret:
470                 if(f->user->status != Sok)
471                         return "user disabled";
472                 if(f->user->purgatory > (ulong)time(0))
473                         return "user in purgatory";
474                 if(f->user->expire != 0 && f->user->expire < (ulong)time(0))
475                         return "user expired";
476                 m = 0;
477                 switch(f->qtype){
478                 case Qkey:
479                         data = (char*)f->user->key.des;
480                         m = DESKEYLEN;
481                         break;
482                 case Qaeskey:
483                         data = (char*)f->user->key.aes;
484                         m = AESKEYLEN;
485                         break;
486                 case Qpakhash:
487                         data = (char*)f->user->key.pakhash;
488                         m = PAKHASHLEN;
489                         break;
490                 case Qsecret:
491                         data = f->user->secret;
492         Readstr:
493                         m = strlen(data);
494                         break;
495                 }
496                 if(off >= m)
497                         n = 0;
498                 else {
499                         data += off;
500                         m -= off;
501                         if(n > m)
502                                 n = m;
503                 }
504                 if(data != thdr.data)
505                         memmove(thdr.data, data, n);
506                 thdr.count = n;
507                 return 0;
508         case Qstatus:
509                 if(f->user->status == Sok && f->user->expire && f->user->expire < (ulong)time(0))
510                         sprint(data, "expired\n");
511                 else
512                         sprint(data, "%s\n", status[f->user->status]);
513                 goto Readstr;
514         case Qexpire:
515                 if(!f->user->expire)
516                         strcpy(data, "never\n");
517                 else
518                         sprint(data, "%lud\n", f->user->expire);
519                 goto Readstr;
520         case Qlog:
521                 sprint(data, "%lud\n", f->user->bad);
522                 goto Readstr;
523         case Qwarnings:
524                 sprint(data, "%ud\n", f->user->warnings);
525                 goto Readstr;
526         default:
527                 return "permission denied: unknown qid";
528         }
529 }
530
531 char *
532 Write(Fid *f)
533 {
534         char *data, *p;
535         ulong n, expire;
536         int i;
537
538         if(!f->busy)
539                 return "permission denied";
540         if(readonly)
541                 return "mounted read-only";
542         n = rhdr.count;
543         data = rhdr.data;
544         switch(f->qtype){
545         case Qkey:
546                 if(n != DESKEYLEN)
547                         return "garbled write data";
548                 memmove(f->user->key.des, data, n);
549                 thdr.count = n;
550                 break;
551         case Qaeskey:
552                 if(n != AESKEYLEN)
553                         return "garbled write data";
554                 memmove(f->user->key.aes, data, n);
555                 authpak_hash(&f->user->key, f->user->name);
556                 thdr.count = n;
557                 break;
558         case Qsecret:
559                 if(n >= SECRETLEN)
560                         return "garbled write data";
561                 memmove(f->user->secret, data, n);
562                 f->user->secret[n] = '\0';
563                 thdr.count = n;
564                 break;
565         case Qstatus:
566                 data[n] = '\0';
567                 if(p = strchr(data, '\n'))
568                         *p = '\0';
569                 for(i = 0; i < Smax; i++)
570                         if(strcmp(data, status[i]) == 0){
571                                 f->user->status = i;
572                                 break;
573                         }
574                 if(i == Smax)
575                         return "unknown status";
576                 f->user->bad = 0;
577                 thdr.count = n;
578                 break;
579         case Qexpire:
580                 data[n] = '\0';
581                 if(p = strchr(data, '\n'))
582                         *p = '\0';
583                 else
584                         p = &data[n];
585                 if(strcmp(data, "never") == 0)
586                         expire = 0;
587                 else{
588                         expire = strtoul(data, &data, 10);
589                         if(data != p)
590                                 return "bad expiration date";
591                 }
592                 f->user->expire = expire;
593                 f->user->warnings = 0;
594                 thdr.count = n;
595                 break;
596         case Qlog:
597                 data[n] = '\0';
598                 if(strcmp(data, "good") == 0)
599                         f->user->bad = 0;
600                 else
601                         f->user->bad++;
602                 if(f->user->bad && ((f->user->bad)%MAXBAD) == 0)
603                         f->user->purgatory = (ulong)time(0) + f->user->bad;
604                 return 0;
605         case Qwarnings:
606                 data[n] = '\0';
607                 f->user->warnings = strtoul(data, nil, 10);
608                 thdr.count = n;
609                 break;
610         case Qroot:
611         case Quser:
612         default:
613                 return "permission denied";
614         }
615         writeusers();
616         return 0;
617 }
618
619 char *
620 Remove(Fid *f)
621 {
622         if(!f->busy)
623                 return "permission denied";
624         if(readonly){
625                 Clunk(f);
626                 return "mounted read-only";
627         }
628         if(f->qtype == Qwarnings)
629                 f->user->warnings = 0;
630         else if(f->qtype == Quser)
631                 removeuser(f->user);
632         else {
633                 Clunk(f);
634                 return "permission denied";
635         }
636         Clunk(f);
637         writeusers();
638         return 0;
639 }
640
641 char *
642 Stat(Fid *f)
643 {
644         static uchar statbuf[1024];
645
646         if(!f->busy)
647                 return "stat on unattached fid";
648         thdr.nstat = dostat(f->user, f->qtype, statbuf, sizeof statbuf);
649         if(thdr.nstat <= BIT16SZ)
650                 return "stat buffer too small";
651         thdr.stat = statbuf;
652         return 0;
653 }
654
655 char *
656 Wstat(Fid *f)
657 {
658         Dir d;
659         int n;
660         char buf[1024];
661
662         if(!f->busy || f->qtype != Quser)
663                 return "permission denied";
664         if(readonly)
665                 return "mounted read-only";
666         if(rhdr.nstat > sizeof buf)
667                 return "wstat buffer too big";
668         if(convM2D(rhdr.stat, rhdr.nstat, &d, buf) == 0)
669                 return "bad stat buffer";
670         n = strlen(d.name);
671         if(n == 0 || n >= Namelen)
672                 return "bad user name";
673         if(finduser(d.name))
674                 return "user already exists";
675         if(!removeuser(f->user))
676                 return "user previously removed";
677         free(f->user->name);
678         f->user->name = estrdup(d.name);
679         insertuser(f->user);
680         writeusers();
681         return 0;
682 }
683
684 Qid
685 mkqid(User *u, ulong qtype)
686 {
687         Qid q;
688
689         q.vers = 0;
690         q.path = qtype;
691         if(u)
692                 q.path |= u->uniq * 0x100;
693         if(qtype == Quser || qtype == Qroot)
694                 q.type = QTDIR;
695         else
696                 q.type = QTFILE;
697         return q;
698 }
699
700 int
701 dostat(User *user, ulong qtype, void *p, int n)
702 {
703         Dir d;
704
705         if(qtype == Quser)
706                 d.name = user->name;
707         else
708                 d.name = qinfo[qtype];
709         d.uid = d.gid = d.muid = "auth";
710         d.qid = mkqid(user, qtype);
711         if(d.qid.type & QTDIR)
712                 d.mode = 0777|DMDIR;
713         else
714                 d.mode = 0666;
715         d.atime = d.mtime = time(0);
716         d.length = 0;
717         return convD2M(&d, p, n);
718 }
719
720 void
721 writeusers(void)
722 {
723         int keydblen, keydboff;
724         int fd, i, nu;
725         User *u;
726         uchar *p, *buf;
727         ulong expire;
728
729         if(readonly){
730                 fprint(2, "writeusers called while read-only; shouldn't happen\n");
731                 return;
732         }
733
734         /* what format to use */
735         keydblen = KEYDBLEN;
736         keydboff = KEYDBOFF;
737         if(keydbaes){
738                 keydblen += AESKEYLEN;
739                 keydboff = 8+16;        /* segnature[8] + iv[16] */
740         }
741
742         /* count users */
743         nu = 0;
744         for(i = 0; i < Nuser; i++)
745                 for(u = users[i]; u != nil; u = u->link)
746                         nu++;
747
748         /* pack into buffer */
749         buf = emalloc(keydboff + nu*keydblen);
750         p = buf;
751         genrandom(p, keydboff);
752         p += keydboff;
753         for(i = 0; i < Nuser; i++)
754                 for(u = users[i]; u != nil; u = u->link){
755                         strncpy((char*)p, u->name, Namelen);
756                         p += Namelen;
757                         memmove(p, u->key.des, DESKEYLEN);
758                         p += DESKEYLEN;
759                         *p++ = u->status;
760                         *p++ = u->warnings;
761                         expire = u->expire;
762                         *p++ = expire;
763                         *p++ = expire >> 8;
764                         *p++ = expire >> 16;
765                         *p++ = expire >> 24;
766                         memmove(p, u->secret, SECRETLEN);
767                         p += SECRETLEN;
768                         if(keydbaes){
769                                 memmove(p, u->key.aes, AESKEYLEN);
770                                 p += AESKEYLEN;
771                         }
772                 }
773
774         /* encrypt */
775         if(keydbaes){
776                 AESstate s;
777
778                 memmove(buf, "AES KEYS", 8);
779                 setupAESstate(&s, authkey.aes, AESKEYLEN, zeros);
780                 aesCBCencrypt(buf+8, (p - (buf+8)), &s);
781         } else {
782                 uchar key[8];
783                 DESstate s;
784
785                 des56to64((uchar*)authkey.des, key);
786                 setupDESstate(&s, key, zeros);
787                 desCBCencrypt(buf, p - buf, &s);
788         }
789
790         /* write file */
791         fd = create(userkeys, OWRITE, 0660);
792         if(fd < 0){
793                 fprint(2, "keyfs: can't write %s: %r\n", userkeys);
794                 free(buf);
795                 return;
796         }
797         if(write(fd, buf, p - buf) != (p - buf))
798                 fprint(2, "keyfs: can't write %s: %r\n", userkeys);
799         close(fd);
800         free(buf);
801
802         newkeys();
803 }
804
805 int
806 weirdfmt(Fmt *f)
807 {
808         char *s, buf[ANAMELEN*4 + 1];
809         int i, j, n;
810         Rune r;
811
812         s = va_arg(f->args, char*);
813         j = 0;
814         for(i = 0; i < ANAMELEN; i += n){
815                 n = chartorune(&r, s + i);
816                 if(r == Runeerror)
817                         j += sprint(buf+j, "[%.2x]", buf[i]);
818                 else if(isascii(r) && iscntrl(r))
819                         j += sprint(buf+j, "[%.2x]", r);
820                 else if(r == ' ' || r == '/')
821                         j += sprint(buf+j, "[%c]", r);
822                 else
823                         j += sprint(buf+j, "%C", r);
824         }
825         return fmtstrcpy(f, buf);
826 }
827
828 int
829 userok(char *user, int nu)
830 {
831         int i, n, rv;
832         Rune r;
833         char buf[ANAMELEN+1];
834
835         memset(buf, 0, sizeof buf);
836         memmove(buf, user, ANAMELEN);
837
838         if(buf[ANAMELEN-1] != 0){
839                 fprint(2, "keyfs: %d: no termination: %W\n", nu, buf);
840                 return -1;
841         }
842
843         rv = 0;
844         for(i = 0; buf[i]; i += n){
845                 n = chartorune(&r, buf+i);
846                 if(r == Runeerror){
847 //                      fprint(2, "keyfs: name %W bad rune byte %d\n", buf, i);
848                         rv = -1;
849                 } else if(isascii(r) && iscntrl(r) || r == ' ' || r == '/'){
850 //                      fprint(2, "keyfs: name %W bad char %C\n", buf, r);
851                         rv = -1;
852                 }
853         }
854
855         if(i == 0){
856                 fprint(2, "keyfs: %d: nil name\n", nu);
857                 return -1;
858         }
859         if(rv == -1)
860                 fprint(2, "keyfs: %d: bad syntax: %W\n", nu, buf);
861         return rv;
862 }
863
864 int
865 readusers(void)
866 {
867         int keydblen, keydboff;
868         int fd, i, n, nu;
869         uchar *p, *buf, *ep;
870         User *u;
871         Dir *d;
872
873         /* read file into an array */
874         fd = open(userkeys, OREAD);
875         if(fd < 0){
876                 fprint(2, "keyfs: can't read %s: %r\n", userkeys);
877                 return 0;
878         }
879         d = dirfstat(fd);
880         if(d == nil){
881                 close(fd);
882                 return 0;
883         }
884         buf = emalloc(d->length);
885         n = readn(fd, buf, d->length);
886         close(fd);
887         free(d);
888         if(n != d->length){
889                 free(buf);
890                 return 0;
891         }
892
893         keydblen = KEYDBLEN;
894         keydboff = KEYDBOFF;
895         keydbaes = n > 24 && memcmp(buf, "AES KEYS", 8) == 0;
896
897         /* decrypt */
898         if(keydbaes){
899                 AESstate s;
900
901                 /* make sure we have AES encryption key */
902                 if(!haveaeskey()){
903                         free(buf);
904                         return 0;
905                 }
906                 keydblen += AESKEYLEN;
907                 keydboff = 8+16;        /* signature[8] + iv[16] */
908                 setupAESstate(&s, authkey.aes, AESKEYLEN, zeros);
909                 aesCBCdecrypt(buf+8, n-8, &s);
910         } else {
911                 uchar key[8];
912                 DESstate s;
913
914                 des56to64((uchar*)authkey.des, key);
915                 setupDESstate(&s, key, zeros);
916                 desCBCdecrypt(buf, n, &s);
917         }
918
919         /* unpack */
920         nu = 0;
921         n = (n - keydboff) / keydblen;
922         ep = buf + keydboff;
923         for(i = 0; i < n; ep += keydblen, i++){
924                 if(userok((char*)ep, i) < 0)
925                         continue;
926                 u = finduser((char*)ep);
927                 if(u == nil)
928                         u = installuser((char*)ep);
929                 memmove(u->key.des, ep + Namelen, DESKEYLEN);
930                 p = ep + Namelen + DESKEYLEN;
931                 u->status = *p++;
932                 u->warnings = *p++;
933                 if(u->status >= Smax)
934                         fprint(2, "keyfs: warning: bad status in key file\n");
935                 u->expire = p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
936                 p += 4;
937                 memmove(u->secret, p, SECRETLEN);
938                 u->secret[SECRETLEN-1] = 0;
939                 p += SECRETLEN;
940                 if(keydbaes){
941                         memmove(u->key.aes, p, AESKEYLEN);
942                         authpak_hash(&u->key, u->name);
943                 }
944                 nu++;
945         }
946         free(buf);
947
948         print("%d keys read in %s format\n", nu, keydbaes ? "AES" : "DES");
949         return 1;
950 }
951
952 User *
953 installuser(char *name)
954 {
955         User *u;
956         int h;
957
958         h = hash(name);
959         u = emalloc(sizeof *u);
960         u->name = estrdup(name);
961         u->removed = 0;
962         u->ref = 0;
963         u->purgatory = 0;
964         u->expire = 0;
965         u->status = Sok;
966         u->bad = 0;
967         u->warnings = 0;
968         u->uniq = uniq++;
969         u->link = users[h];
970         users[h] = u;
971         return u;
972 }
973
974 User *
975 finduser(char *name)
976 {
977         User *u;
978
979         for(u = users[hash(name)]; u != nil; u = u->link)
980                 if(strcmp(name, u->name) == 0)
981                         return u;
982         return nil;
983 }
984
985 int
986 removeuser(User *user)
987 {
988         User *u, **last;
989         char *name;
990
991         user->removed = 1;
992         name = user->name;
993         last = &users[hash(name)];
994         for(u = *last; u != nil; u = *last){
995                 if(strcmp(name, u->name) == 0){
996                         *last = u->link;
997                         return 1;
998                 }
999                 last = &u->link;
1000         }
1001         return 0;
1002 }
1003
1004 void
1005 insertuser(User *user)
1006 {
1007         int h;
1008
1009         user->removed = 0;
1010         h = hash(user->name);
1011         user->link = users[h];
1012         users[h] = user;
1013 }
1014
1015 ulong
1016 hash(char *s)
1017 {
1018         ulong h;
1019
1020         h = 0;
1021         while(*s)
1022                 h = (h << 1) ^ *s++;
1023         return h % Nuser;
1024 }
1025
1026 Fid *
1027 findfid(int fid)
1028 {
1029         Fid *f, *ff;
1030
1031         ff = nil;
1032         for(f = fids; f != nil; f = f->next)
1033                 if(f->fid == fid)
1034                         return f;
1035                 else if(!ff && !f->busy)
1036                         ff = f;
1037         if(ff != nil){
1038                 ff->fid = fid;
1039                 return ff;
1040         }
1041         f = emalloc(sizeof *f);
1042         f->fid = fid;
1043         f->busy = 0;
1044         f->user = nil;
1045         f->next = fids;
1046         fids = f;
1047         return f;
1048 }
1049
1050 void
1051 io(int in, int out)
1052 {
1053         char *err;
1054         int n;
1055         ulong now, lastwarning;
1056
1057         /* after restart, let the system settle for 5 mins before warning */
1058         lastwarning = (ulong)time(0) - 24*60*60 + 5*60;
1059
1060         while((n = read9pmsg(in, mdata, messagesize)) != 0){
1061                 if(n < 0)
1062                         error("mount read: %r");
1063                 if(convM2S(mdata, n, &rhdr) != n)
1064                         error("convM2S format error: %r");
1065
1066                 if(newkeys())
1067                         readusers();
1068
1069                 thdr.data = (char*)mdata + IOHDRSZ;
1070                 thdr.fid = rhdr.fid;
1071                 if(!fcalls[rhdr.type])
1072                         err = "fcall request";
1073                 else
1074                         err = (*fcalls[rhdr.type])(findfid(rhdr.fid));
1075                 thdr.tag = rhdr.tag;
1076                 thdr.type = rhdr.type+1;
1077                 if(err){
1078                         thdr.type = Rerror;
1079                         thdr.ename = err;
1080                 }
1081                 n = convS2M(&thdr, mdata, messagesize);
1082                 if(write(out, mdata, n) != n)
1083                         error("mount write");
1084
1085                 now = time(0);
1086                 if(warnarg && (long)(now - lastwarning) > 24*60*60){
1087                         syslog(0, "auth", "keyfs starting warnings: %lux %lux",
1088                                 now, lastwarning);
1089                         warning();
1090                         lastwarning = now;
1091                 }
1092         }
1093 }
1094
1095 int
1096 newkeys(void)
1097 {
1098         Dir *d;
1099         static ulong ftime;
1100
1101         d = dirstat(userkeys);
1102         if(d == nil)
1103                 return 0;
1104         if(d->mtime > ftime){
1105                 ftime = d->mtime;
1106                 free(d);
1107                 return 1;
1108         }
1109         free(d);
1110         return 0;
1111 }
1112
1113 void *
1114 emalloc(ulong n)
1115 {
1116         void *p;
1117
1118         if((p = malloc(n)) != nil){
1119                 memset(p, 0, n);
1120                 return p;
1121         }
1122         error("out of memory");
1123         return nil;             /* not reached */
1124 }
1125
1126 char *
1127 estrdup(char *s)
1128 {
1129         char *d;
1130         int n;
1131
1132         n = strlen(s)+1;
1133         d = emalloc(n);
1134         memmove(d, s, n);
1135         return d;
1136 }
1137
1138 void
1139 warning(void)
1140 {
1141         int i;
1142         char buf[64];
1143
1144         snprint(buf, sizeof buf, "-%s", warnarg);
1145         switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG)){
1146         case 0:
1147                 i = open("/sys/log/auth", OWRITE);
1148                 if(i >= 0){
1149                         dup(i, 2);
1150                         seek(2, 0, 2);
1151                         close(i);
1152                 }
1153                 execl("/bin/auth/warning", "warning", warnarg, nil);
1154                 error("can't exec warning");
1155         }
1156 }