1157 lines
20 KiB
C
1157 lines
20 KiB
C
/*
|
|
* 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, "") < 0)
|
|
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");
|
|
}
|
|
}
|