/*
* keyfs
*/
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <bio.h>
#include <libsec.h>
#include <authsrv.h>
#include "authcmdlib.h"

#pragma varargck        type    "W"     char*

Authkey authkey;
int     keydbaes;
uchar   zeros[16];

typedef struct Fid      Fid;
typedef struct User     User;

enum {
       Qroot,
       Quser,
       Qkey,
       Qaeskey,
       Qpakhash,
       Qsecret,
       Qlog,
       Qstatus,
       Qexpire,
       Qwarnings,
       Qmax,

       Nuser   = 512,
       MAXBAD  = 10,   /* max # of bad attempts before disabling the account */
       /* file must be randomly addressible, so names have fixed length */
       Namelen = ANAMELEN,
};

enum {
       Sok,
       Sdisabled,
       Smax,
};

struct Fid {
       int     fid;
       ulong   qtype;
       User    *user;
       int     busy;
       Fid     *next;
};

struct User {
       char    *name;
       Authkey key;
       char    secret[SECRETLEN];
       ulong   expire;                 /* 0 == never */
       uchar   status;
       ulong   bad;            /* # of consecutive bad authentication attempts */
       int     ref;
       char    removed;
       uchar   warnings;
       ulong   purgatory;              /* time purgatory ends */
       ulong   uniq;
       User    *link;
};

char    *qinfo[Qmax] = {
       [Qroot]         "keys",
       [Quser]         ".",
       [Qkey]          "key",
       [Qaeskey]       "aeskey",
       [Qpakhash]      "pakhash",
       [Qsecret]       "secret",
       [Qlog]          "log",
       [Qexpire]       "expire",
       [Qstatus]       "status",
       [Qwarnings]     "warnings",
};

char    *status[Smax] = {
       [Sok]           "ok",
       [Sdisabled]     "disabled",
};

Fid     *fids;
User    *users[Nuser];
char    *userkeys;
int     nuser;
ulong   uniq = 1;
Fcall   rhdr, thdr;
int     usepass;
int     readonly;
char    *warnarg;
uchar   mdata[8192 + IOHDRSZ];
int     messagesize = sizeof mdata;

int     readusers(void);
ulong   hash(char*);
Fid     *findfid(int);
User    *finduser(char*);
User    *installuser(char*);
int     removeuser(User*);
void    insertuser(User*);
void    writeusers(void);
void    io(int, int);
void    *emalloc(ulong);
char    *estrdup(char*);
Qid     mkqid(User*, ulong);
int     dostat(User*, ulong, void*, int);
int     newkeys(void);
void    warning(void);
int     weirdfmt(Fmt *f);

char    *Auth(Fid*), *Attach(Fid*), *Version(Fid*),
       *Flush(Fid*), *Walk(Fid*),
       *Open(Fid*), *Create(Fid*),
       *Read(Fid *), *Write(Fid*), *Clunk(Fid*),
       *Remove(Fid *), *Stat(Fid*), *Wstat(Fid*);
char    *(*fcalls[])(Fid*) = {
       [Tattach]       Attach,
       [Tauth] Auth,
       [Tclunk]        Clunk,
       [Tcreate]       Create,
       [Tflush]        Flush,
       [Topen]         Open,
       [Tread]         Read,
       [Tremove]       Remove,
       [Tstat]         Stat,
       [Tversion]      Version,
       [Twalk]         Walk,
       [Twrite]        Write,
       [Twstat]        Wstat,
};

static void
usage(void)
{
       fprint(2, "usage: %s [-p] [-r] [-m mtpt] [-w warn] [keyfile]\n", argv0);
       exits("usage");
}

static int
haveaeskey(void)
{
       return memcmp(authkey.aes, zeros, 16) != 0;
}

void
main(int argc, char *argv[])
{
       char *mntpt;
       int p[2];

       fmtinstall('W', weirdfmt);
       mntpt = "/mnt/keys";
       ARGBEGIN{
       case 'm':
               mntpt = EARGF(usage());
               break;
       case 'p':
               usepass = 1;
               break;
       case 'w':
               warnarg = EARGF(usage());
               break;
       case 'r':
               readonly = 1;
               break;
       default:
               usage();
               break;
       }ARGEND
       argv0 = "keyfs";

       userkeys = "/adm/keys";
       if(argc > 1)
               usage();
       if(argc == 1)
               userkeys = argv[0];

       if(pipe(p) < 0)
               error("can't make pipe: %r");

       private();
       if(usepass)
               getpass(&authkey, nil, 0, 0);
       else {
               if(!getauthkey(&authkey))
                       fprint(2, "keyfs: warning: can't read NVRAM\n");
       }

       keydbaes = 0;
       if(!newkeys() || !readusers()){
               if(!keydbaes)
                       keydbaes = haveaeskey();
               else if(!haveaeskey()){
                       fprint(2, "keyfs: no aes key in NVRAM\n");
                       getpass(&authkey, nil, 0, 0);
                       readusers();
               }
       }

       switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG)){
       case 0:
               close(p[0]);
               io(p[1], p[1]);
               exits(0);
       case -1:
               error("fork");
       default:
               close(p[1]);
               if(mount(p[0], -1, mntpt, MREPL|MCREATE, "") == -1)
                       error("can't mount: %r");
               exits(0);
       }
}

char *
Flush(Fid *f)
{
       USED(f);
       return 0;
}

char *
Auth(Fid *)
{
       return "keyfs: authentication not required";
}

char *
Attach(Fid *f)
{
       if(f->busy)
               Clunk(f);
       f->user = nil;
       f->qtype = Qroot;
       f->busy = 1;
       thdr.qid = mkqid(f->user, f->qtype);
       return 0;
}

char*
Version(Fid*)
{
       Fid *f;

       for(f = fids; f; f = f->next)
               if(f->busy)
                       Clunk(f);
       if(rhdr.msize < 256)
               return "message size too small";
       if(rhdr.msize > sizeof mdata)
               thdr.msize = sizeof mdata;
       else
               thdr.msize = rhdr.msize;
       messagesize = thdr.msize;
       if(strncmp(rhdr.version, "9P2000", 6) != 0)
               return "bad 9P version";
       thdr.version = "9P2000";
       return 0;
}

char *
Walk(Fid *f)
{
       char *name, *err;
       int i, j, max;
       Fid *nf;
       ulong qtype;
       User *user;

       if(!f->busy)
               return "walk of unused fid";
       nf = nil;
       qtype = f->qtype;
       user = f->user;
       if(rhdr.fid != rhdr.newfid){
               nf = findfid(rhdr.newfid);
               if(nf->busy)
                       return "fid in use";
               f = nf; /* walk f */
       }

       err = nil;
       i = 0;
       if(rhdr.nwname > 0){
               for(; i<rhdr.nwname; i++){
                       if(i >= MAXWELEM){
                               err = "too many path name elements";
                               break;
                       }
                       name = rhdr.wname[i];
                       switch(qtype){
                       case Qroot:
                               if(strcmp(name, "..") == 0)
                                       goto Accept;
                               user = finduser(name);
                               if(user == nil)
                                       goto Out;
                               qtype = Quser;

                       Accept:
                               thdr.wqid[i] = mkqid(user, qtype);
                               break;

                       case Quser:
                               if(strcmp(name, "..") == 0) {
                                       qtype = Qroot;
                                       user = nil;
                                       goto Accept;
                               }
                               max = Qmax;
                               for(j = Quser + 1; j < Qmax; j++)
                                       if(strcmp(name, qinfo[j]) == 0){
                                               qtype = j;
                                               break;
                                       }
                               if(j < max)
                                       goto Accept;
                               goto Out;

                       default:
                               err = "file is not a directory";
                               goto Out;
                       }
               }
           Out:
               if(i < rhdr.nwname && err == nil)
                       err = "file not found";
       }

       if(err != nil){
               return err;
       }

       /* if we cloned and then completed the walk, update new fid */
       if(rhdr.fid != rhdr.newfid && i == rhdr.nwname){
               nf->busy = 1;
               nf->qtype = qtype;
               nf->user = user;
               if(user != nil)
                       user->ref++;
       }else if(nf == nil && rhdr.nwname > 0){ /* walk without clone (rare) */
               Clunk(f);
               f->busy = 1;
               f->qtype = qtype;
               f->user = user;
               if(user != nil)
                       user->ref++;
       }

       thdr.nwqid = i;
       return 0;
}

char *
Clunk(Fid *f)
{
       f->busy = 0;
       if(f->user != nil && --f->user->ref == 0 && f->user->removed) {
               free(f->user->name);
               free(f->user);
       }
       f->user = nil;
       return nil;
}

char *
Open(Fid *f)
{
       int mode;

       if(!f->busy)
               return "open of unused fid";
       mode = rhdr.mode;
       if(f->qtype == Quser && (mode & (OWRITE|OTRUNC)))
               return "user already exists";
       if((f->qtype == Qaeskey || f->qtype == Qpakhash) && !keydbaes)
               return "keyfile not in aes format";
       thdr.qid = mkqid(f->user, f->qtype);
       thdr.iounit = messagesize - IOHDRSZ;
       return 0;
}

char *
Create(Fid *f)
{
       char *name;
       long perm;

       if(!f->busy)
               return "create of unused fid";
       if(readonly)
               return "mounted read-only";
       name = rhdr.name;
       if(f->user != nil){
               return "permission denied";
       }else{
               perm = rhdr.perm;
               if(!(perm & DMDIR))
                       return "permission denied";
               if(strcmp(name, "") == 0)
                       return "empty file name";
               if(strlen(name) >= Namelen)
                       return "file name too long";
               if(finduser(name) != nil)
                       return "user already exists";
               f->user = installuser(name);
               f->user->ref++;
               f->qtype = Quser;
       }
       thdr.qid = mkqid(f->user, f->qtype);
       thdr.iounit = messagesize - IOHDRSZ;
       writeusers();
       return 0;
}

char *
Read(Fid *f)
{
       User *u;
       char *data;
       ulong off, n, m;
       int i, j, max;

       if(!f->busy)
               return "read of unused fid";
       n = rhdr.count;
       off = rhdr.offset;
       thdr.count = 0;
       data = thdr.data;
       switch(f->qtype){
       case Qroot:
               j = 0;
               for(i = 0; i < Nuser; i++)
                       for(u = users[i]; u != nil; j += m, u = u->link){
                               m = dostat(u, Quser, data, n);
                               if(m <= BIT16SZ)
                                       break;
                               if(j < off)
                                       continue;
                               data += m;
                               n -= m;
                       }
               thdr.count = data - thdr.data;
               return 0;
       case Quser:
               max = Qmax;
               max -= Quser + 1;
               j = 0;
               for(i = 0; i < max; j += m, i++){
                       m = dostat(f->user, i + Quser + 1, data, n);
                       if(m <= BIT16SZ)
                               break;
                       if(j < off)
                               continue;
                       data += m;
                       n -= m;
               }
               thdr.count = data - thdr.data;
               return 0;
       case Qkey:
       case Qaeskey:
       case Qpakhash:
       case Qsecret:
               if(f->user->status != Sok)
                       return "user disabled";
               if(f->user->purgatory > (ulong)time(0))
                       return "user in purgatory";
               if(f->user->expire != 0 && f->user->expire < (ulong)time(0))
                       return "user expired";
               m = 0;
               switch(f->qtype){
               case Qkey:
                       data = (char*)f->user->key.des;
                       m = DESKEYLEN;
                       break;
               case Qaeskey:
                       data = (char*)f->user->key.aes;
                       m = AESKEYLEN;
                       break;
               case Qpakhash:
                       data = (char*)f->user->key.pakhash;
                       m = PAKHASHLEN;
                       break;
               case Qsecret:
                       data = f->user->secret;
       Readstr:
                       m = strlen(data);
                       break;
               }
               if(off >= m)
                       n = 0;
               else {
                       data += off;
                       m -= off;
                       if(n > m)
                               n = m;
               }
               if(data != thdr.data)
                       memmove(thdr.data, data, n);
               thdr.count = n;
               return 0;
       case Qstatus:
               if(f->user->status == Sok && f->user->expire && f->user->expire < (ulong)time(0))
                       sprint(data, "expired\n");
               else
                       sprint(data, "%s\n", status[f->user->status]);
               goto Readstr;
       case Qexpire:
               if(!f->user->expire)
                       strcpy(data, "never\n");
               else
                       sprint(data, "%lud\n", f->user->expire);
               goto Readstr;
       case Qlog:
               sprint(data, "%lud\n", f->user->bad);
               goto Readstr;
       case Qwarnings:
               sprint(data, "%ud\n", f->user->warnings);
               goto Readstr;
       default:
               return "permission denied: unknown qid";
       }
}

char *
Write(Fid *f)
{
       char *data, *p;
       ulong n, expire;
       int i;

       if(!f->busy)
               return "permission denied";
       if(readonly)
               return "mounted read-only";
       n = rhdr.count;
       data = rhdr.data;
       switch(f->qtype){
       case Qkey:
               if(n != DESKEYLEN)
                       return "garbled write data";
               memmove(f->user->key.des, data, n);
               thdr.count = n;
               break;
       case Qaeskey:
               if(n != AESKEYLEN)
                       return "garbled write data";
               memmove(f->user->key.aes, data, n);
               authpak_hash(&f->user->key, f->user->name);
               thdr.count = n;
               break;
       case Qsecret:
               if(n >= SECRETLEN)
                       return "garbled write data";
               memmove(f->user->secret, data, n);
               f->user->secret[n] = '\0';
               thdr.count = n;
               break;
       case Qstatus:
               data[n] = '\0';
               if(p = strchr(data, '\n'))
                       *p = '\0';
               for(i = 0; i < Smax; i++)
                       if(strcmp(data, status[i]) == 0){
                               f->user->status = i;
                               break;
                       }
               if(i == Smax)
                       return "unknown status";
               f->user->bad = 0;
               thdr.count = n;
               break;
       case Qexpire:
               data[n] = '\0';
               if(p = strchr(data, '\n'))
                       *p = '\0';
               else
                       p = &data[n];
               if(strcmp(data, "never") == 0)
                       expire = 0;
               else{
                       expire = strtoul(data, &data, 10);
                       if(data != p)
                               return "bad expiration date";
               }
               f->user->expire = expire;
               f->user->warnings = 0;
               thdr.count = n;
               break;
       case Qlog:
               data[n] = '\0';
               if(strcmp(data, "good") == 0)
                       f->user->bad = 0;
               else
                       f->user->bad++;
               if(f->user->bad && ((f->user->bad)%MAXBAD) == 0)
                       f->user->purgatory = (ulong)time(0) + f->user->bad;
               return 0;
       case Qwarnings:
               data[n] = '\0';
               f->user->warnings = strtoul(data, nil, 10);
               thdr.count = n;
               break;
       case Qroot:
       case Quser:
       default:
               return "permission denied";
       }
       writeusers();
       return 0;
}

char *
Remove(Fid *f)
{
       if(!f->busy)
               return "permission denied";
       if(readonly){
               Clunk(f);
               return "mounted read-only";
       }
       if(f->qtype == Qwarnings)
               f->user->warnings = 0;
       else if(f->qtype == Quser)
               removeuser(f->user);
       else {
               Clunk(f);
               return "permission denied";
       }
       Clunk(f);
       writeusers();
       return 0;
}

char *
Stat(Fid *f)
{
       static uchar statbuf[1024];

       if(!f->busy)
               return "stat on unattached fid";
       thdr.nstat = dostat(f->user, f->qtype, statbuf, sizeof statbuf);
       if(thdr.nstat <= BIT16SZ)
               return "stat buffer too small";
       thdr.stat = statbuf;
       return 0;
}

char *
Wstat(Fid *f)
{
       Dir d;
       int n;
       char buf[1024];

       if(!f->busy || f->qtype != Quser)
               return "permission denied";
       if(readonly)
               return "mounted read-only";
       if(rhdr.nstat > sizeof buf)
               return "wstat buffer too big";
       if(convM2D(rhdr.stat, rhdr.nstat, &d, buf) == 0)
               return "bad stat buffer";
       n = strlen(d.name);
       if(n == 0 || n >= Namelen)
               return "bad user name";
       if(finduser(d.name))
               return "user already exists";
       if(!removeuser(f->user))
               return "user previously removed";
       free(f->user->name);
       f->user->name = estrdup(d.name);
       insertuser(f->user);
       writeusers();
       return 0;
}

Qid
mkqid(User *u, ulong qtype)
{
       Qid q;

       q.vers = 0;
       q.path = qtype;
       if(u)
               q.path |= u->uniq * 0x100;
       if(qtype == Quser || qtype == Qroot)
               q.type = QTDIR;
       else
               q.type = QTFILE;
       return q;
}

int
dostat(User *user, ulong qtype, void *p, int n)
{
       Dir d;

       if(qtype == Quser)
               d.name = user->name;
       else
               d.name = qinfo[qtype];
       d.uid = d.gid = d.muid = "auth";
       d.qid = mkqid(user, qtype);
       if(d.qid.type & QTDIR)
               d.mode = 0777|DMDIR;
       else
               d.mode = 0666;
       d.atime = d.mtime = time(0);
       d.length = 0;
       return convD2M(&d, p, n);
}

void
writeusers(void)
{
       int keydblen, keydboff;
       int fd, i, nu;
       User *u;
       uchar *p, *buf;
       ulong expire;

       if(readonly){
               fprint(2, "writeusers called while read-only; shouldn't happen\n");
               return;
       }

       /* what format to use */
       keydblen = KEYDBLEN;
       keydboff = KEYDBOFF;
       if(keydbaes){
               keydblen += AESKEYLEN;
               keydboff = 8+16;        /* segnature[8] + iv[16] */
       }

       /* count users */
       nu = 0;
       for(i = 0; i < Nuser; i++)
               for(u = users[i]; u != nil; u = u->link)
                       nu++;

       /* pack into buffer */
       buf = emalloc(keydboff + nu*keydblen);
       p = buf;
       genrandom(p, keydboff);
       p += keydboff;
       for(i = 0; i < Nuser; i++)
               for(u = users[i]; u != nil; u = u->link){
                       strncpy((char*)p, u->name, Namelen);
                       p += Namelen;
                       memmove(p, u->key.des, DESKEYLEN);
                       p += DESKEYLEN;
                       *p++ = u->status;
                       *p++ = u->warnings;
                       expire = u->expire;
                       *p++ = expire;
                       *p++ = expire >> 8;
                       *p++ = expire >> 16;
                       *p++ = expire >> 24;
                       memmove(p, u->secret, SECRETLEN);
                       p += SECRETLEN;
                       if(keydbaes){
                               memmove(p, u->key.aes, AESKEYLEN);
                               p += AESKEYLEN;
                       }
               }

       /* encrypt */
       if(keydbaes){
               AESstate s;

               memmove(buf, "AES KEYS", 8);
               setupAESstate(&s, authkey.aes, AESKEYLEN, zeros);
               aesCBCencrypt(buf+8, (p - (buf+8)), &s);
       } else {
               uchar key[8];
               DESstate s;

               des56to64((uchar*)authkey.des, key);
               setupDESstate(&s, key, zeros);
               desCBCencrypt(buf, p - buf, &s);
       }

       /* write file */
       fd = create(userkeys, OWRITE, 0660);
       if(fd < 0){
               fprint(2, "keyfs: can't write %s: %r\n", userkeys);
               free(buf);
               return;
       }
       if(write(fd, buf, p - buf) != (p - buf))
               fprint(2, "keyfs: can't write %s: %r\n", userkeys);
       close(fd);
       free(buf);

       newkeys();
}

int
weirdfmt(Fmt *f)
{
       char *s, buf[ANAMELEN*4 + 1];
       int i, j, n;
       Rune r;

       s = va_arg(f->args, char*);
       j = 0;
       for(i = 0; i < ANAMELEN; i += n){
               n = chartorune(&r, s + i);
               if(r == Runeerror)
                       j += sprint(buf+j, "[%.2x]", buf[i]);
               else if(isascii(r) && iscntrl(r))
                       j += sprint(buf+j, "[%.2x]", r);
               else if(r == ' ' || r == '/')
                       j += sprint(buf+j, "[%c]", r);
               else
                       j += sprint(buf+j, "%C", r);
       }
       return fmtstrcpy(f, buf);
}

int
userok(char *user, int nu)
{
       int i, n, rv;
       Rune r;
       char buf[ANAMELEN+1];

       memset(buf, 0, sizeof buf);
       memmove(buf, user, ANAMELEN);

       if(buf[ANAMELEN-1] != 0){
               fprint(2, "keyfs: %d: no termination: %W\n", nu, buf);
               return -1;
       }

       rv = 0;
       for(i = 0; buf[i]; i += n){
               n = chartorune(&r, buf+i);
               if(r == Runeerror){
//                      fprint(2, "keyfs: name %W bad rune byte %d\n", buf, i);
                       rv = -1;
               } else if(isascii(r) && iscntrl(r) || r == ' ' || r == '/'){
//                      fprint(2, "keyfs: name %W bad char %C\n", buf, r);
                       rv = -1;
               }
       }

       if(i == 0){
               fprint(2, "keyfs: %d: nil name\n", nu);
               return -1;
       }
       if(rv == -1)
               fprint(2, "keyfs: %d: bad syntax: %W\n", nu, buf);
       return rv;
}

int
readusers(void)
{
       int keydblen, keydboff;
       int fd, i, n, nu;
       uchar *p, *buf, *ep;
       User *u;
       Dir *d;

       /* read file into an array */
       fd = open(userkeys, OREAD);
       if(fd < 0){
               fprint(2, "keyfs: can't read %s: %r\n", userkeys);
               return 0;
       }
       d = dirfstat(fd);
       if(d == nil){
               close(fd);
               return 0;
       }
       buf = emalloc(d->length);
       n = readn(fd, buf, d->length);
       close(fd);
       free(d);
       if(n != d->length){
               free(buf);
               return 0;
       }

       keydblen = KEYDBLEN;
       keydboff = KEYDBOFF;
       keydbaes = n > 24 && memcmp(buf, "AES KEYS", 8) == 0;

       /* decrypt */
       if(keydbaes){
               AESstate s;

               /* make sure we have AES encryption key */
               if(!haveaeskey()){
                       free(buf);
                       return 0;
               }
               keydblen += AESKEYLEN;
               keydboff = 8+16;        /* signature[8] + iv[16] */
               setupAESstate(&s, authkey.aes, AESKEYLEN, zeros);
               aesCBCdecrypt(buf+8, n-8, &s);
       } else {
               uchar key[8];
               DESstate s;

               des56to64((uchar*)authkey.des, key);
               setupDESstate(&s, key, zeros);
               desCBCdecrypt(buf, n, &s);
       }

       /* unpack */
       nu = 0;
       n = (n - keydboff) / keydblen;
       ep = buf + keydboff;
       for(i = 0; i < n; ep += keydblen, i++){
               if(userok((char*)ep, i) < 0)
                       continue;
               u = finduser((char*)ep);
               if(u == nil)
                       u = installuser((char*)ep);
               memmove(u->key.des, ep + Namelen, DESKEYLEN);
               p = ep + Namelen + DESKEYLEN;
               u->status = *p++;
               u->warnings = *p++;
               if(u->status >= Smax)
                       fprint(2, "keyfs: warning: bad status in key file\n");
               u->expire = p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
               p += 4;
               memmove(u->secret, p, SECRETLEN);
               u->secret[SECRETLEN-1] = 0;
               p += SECRETLEN;
               if(keydbaes){
                       memmove(u->key.aes, p, AESKEYLEN);
                       authpak_hash(&u->key, u->name);
               }
               nu++;
       }
       free(buf);

       print("%d keys read in %s format\n", nu, keydbaes ? "AES" : "DES");
       return 1;
}

User *
installuser(char *name)
{
       User *u;
       int h;

       h = hash(name);
       u = emalloc(sizeof *u);
       u->name = estrdup(name);
       u->removed = 0;
       u->ref = 0;
       u->purgatory = 0;
       u->expire = 0;
       u->status = Sok;
       u->bad = 0;
       u->warnings = 0;
       u->uniq = uniq++;
       u->link = users[h];
       users[h] = u;
       return u;
}

User *
finduser(char *name)
{
       User *u;

       for(u = users[hash(name)]; u != nil; u = u->link)
               if(strcmp(name, u->name) == 0)
                       return u;
       return nil;
}

int
removeuser(User *user)
{
       User *u, **last;
       char *name;

       user->removed = 1;
       name = user->name;
       last = &users[hash(name)];
       for(u = *last; u != nil; u = *last){
               if(strcmp(name, u->name) == 0){
                       *last = u->link;
                       return 1;
               }
               last = &u->link;
       }
       return 0;
}

void
insertuser(User *user)
{
       int h;

       user->removed = 0;
       h = hash(user->name);
       user->link = users[h];
       users[h] = user;
}

ulong
hash(char *s)
{
       ulong h;

       h = 0;
       while(*s)
               h = (h << 1) ^ *s++;
       return h % Nuser;
}

Fid *
findfid(int fid)
{
       Fid *f, *ff;

       ff = nil;
       for(f = fids; f != nil; f = f->next)
               if(f->fid == fid)
                       return f;
               else if(!ff && !f->busy)
                       ff = f;
       if(ff != nil){
               ff->fid = fid;
               return ff;
       }
       f = emalloc(sizeof *f);
       f->fid = fid;
       f->busy = 0;
       f->user = nil;
       f->next = fids;
       fids = f;
       return f;
}

void
io(int in, int out)
{
       char *err;
       int n;
       ulong now, lastwarning;

       /* after restart, let the system settle for 5 mins before warning */
       lastwarning = (ulong)time(0) - 24*60*60 + 5*60;

       while((n = read9pmsg(in, mdata, messagesize)) != 0){
               if(n < 0)
                       error("mount read: %r");
               if(convM2S(mdata, n, &rhdr) != n)
                       error("convM2S format error: %r");

               if(newkeys())
                       readusers();

               thdr.data = (char*)mdata + IOHDRSZ;
               thdr.fid = rhdr.fid;
               if(!fcalls[rhdr.type])
                       err = "fcall request";
               else
                       err = (*fcalls[rhdr.type])(findfid(rhdr.fid));
               thdr.tag = rhdr.tag;
               thdr.type = rhdr.type+1;
               if(err){
                       thdr.type = Rerror;
                       thdr.ename = err;
               }
               n = convS2M(&thdr, mdata, messagesize);
               if(write(out, mdata, n) != n)
                       error("mount write");

               now = time(0);
               if(warnarg && (long)(now - lastwarning) > 24*60*60){
                       syslog(0, "auth", "keyfs starting warnings: %lux %lux",
                               now, lastwarning);
                       warning();
                       lastwarning = now;
               }
       }
}

int
newkeys(void)
{
       Dir *d;
       static ulong ftime;

       d = dirstat(userkeys);
       if(d == nil)
               return 0;
       if(d->mtime > ftime){
               ftime = d->mtime;
               free(d);
               return 1;
       }
       free(d);
       return 0;
}

void *
emalloc(ulong n)
{
       void *p;

       if((p = malloc(n)) != nil){
               memset(p, 0, n);
               return p;
       }
       error("out of memory");
       return nil;             /* not reached */
}

char *
estrdup(char *s)
{
       char *d;
       int n;

       n = strlen(s)+1;
       d = emalloc(n);
       memmove(d, s, n);
       return d;
}

void
warning(void)
{
       int i;
       char buf[64];

       snprint(buf, sizeof buf, "-%s", warnarg);
       switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG)){
       case 0:
               i = open("/sys/log/auth", OWRITE);
               if(i >= 0){
                       dup(i, 2);
                       seek(2, 0, 2);
                       close(i);
               }
               execl("/bin/auth/warning", "warning", warnarg, nil);
               error("can't exec warning");
       }
}