Plan 9 from Bell Labs’s /usr/web/sources/patch/applied/auth-stderr-tags/keyfs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <authsrv.h>
#include <fcall.h>
#include <bio.h>
#include <mp.h>
#include <libsec.h>
#include "authcmdlib.h"

char authkey[8];

typedef struct Fid	Fid;
typedef struct User	User;

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

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

enum{
	Sok,
	Sdisabled,
	Smax,
};

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

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

char	*qinfo[Qmax] = {
	[Qroot]		"keys",
	[Quser]		".",
	[Qkey]		"key",
	[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;
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);
Qid	mkqid(User*, ulong);
int	dostat(User*, ulong, void*, int);
int	newkeys(void);
void	warning(void);

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,
};

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

	mntpt = "/mnt/keys";
	ARGBEGIN{
	case 'm':
		mntpt = ARGF();
		break;
	case 'w':
		warnarg = ARGF();
		break;
	case 'p':
		usepass = 1;
		break;
	}ARGEND
	argv0 = "keyfs";

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

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

	if(usepass) {
		getpass(authkey, nil, 0, 0);
	} else {
		if(!getauthkey(authkey))
			print("keyfs: warning: can't read /dev/key\n");
	}

	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 = 0;
	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 > 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)
					goto Out;
				qtype = Quser;

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

			case Quser:
				if(strcmp(name, "..") == 0) {
					qtype = Qroot;
					user = 0;
					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;
		if(nf->user = user)
			nf->user->ref++;
	}else if(nf == nil && rhdr.nwname > 0){	/* walk without clone (rare) */
		Clunk(f);
		f->busy = 1;
		f->qtype = qtype;
		if(f->user = user)
			f->user->ref++;
	}

	thdr.nwqid = i;
	return 0;
}

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

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";
	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";
	name = rhdr.name;
	if(f->user){
		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))
			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; 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:
		if(f->user->status != Sok)
			return "user disabled";
		if(f->user->purgatory > time(0))
			return "user in purgatory";
		if(f->user->expire != 0 && f->user->expire < time(0))
			return "user expired";
		if(off != 0)
			return 0;
		if(n > DESKEYLEN)
			n = DESKEYLEN;
		memmove(thdr.data, f->user->key, n);
		thdr.count = n;
		return 0;
	case Qsecret:
		if(f->user->status != Sok)
			return "user disabled";
		if(f->user->purgatory > time(0))
			return "user in purgatory";
		if(f->user->expire != 0 && f->user->expire < time(0))
			return "user expired";
		if(off != 0)
			return 0;
		if(n > strlen(f->user->secret))
			n = strlen(f->user->secret);
		memmove(thdr.data, f->user->secret, n);
		thdr.count = n;
		return 0;
	case Qstatus:
		if(off != 0){
			thdr.count = 0;
			return 0;
		}
		if(f->user->status == Sok && f->user->expire && f->user->expire < time(0))
			sprint(thdr.data, "expired\n");
		else
			sprint(thdr.data, "%s\n", status[f->user->status]);
		thdr.count = strlen(thdr.data);
		return 0;
	case Qexpire:
		if(off != 0){
			thdr.count = 0;
			return 0;
		}
		if(!f->user->expire)
			strcpy(data, "never\n");
		else
			sprint(data, "%lud\n", f->user->expire);
		if(n > strlen(data))
			n = strlen(data);
		thdr.count = n;
		return 0;
	case Qlog:
		if(off != 0){
			thdr.count = 0;
			return 0;
		}
		sprint(data, "%lud\n", f->user->bad);
		if(n > strlen(data))
			n = strlen(data);
		thdr.count = n;
		return 0;
	case Qwarnings:
		if(off != 0){
			thdr.count = 0;
			return 0;
		}
		sprint(data, "%ud\n", f->user->warnings);
		if(n > strlen(data))
			n = strlen(data);
		thdr.count = n;
		return 0;
	default:
		return "permission denied: unknown qid";
	}
}

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

	if(!f->busy)
		return "permission denied";
	n = rhdr.count;
	data = rhdr.data;
	switch(f->qtype){
	case Qkey:
		if(n != DESKEYLEN)
			return "garbled write data";
		memmove(f->user->key, data, DESKEYLEN);
		thdr.count = DESKEYLEN;
		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 = time(0) + f->user->bad;
		return 0;
	case Qwarnings:
		data[n] = '\0';
		f->user->warnings = strtoul(data, 0, 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(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(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 = strdup(d.name);
	if(f->user->name == nil)
		error("wstat: malloc failed: %r");
	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);
}

int
passline(Biobuf *b, void *vbuf)
{
	char *buf = vbuf;

	if(Bread(b, buf, KEYDBLEN) != KEYDBLEN)
		return 0;
	decrypt(authkey, buf, KEYDBLEN);
	buf[Namelen-1] = '\0';
	return 1;
}

void
randombytes(uchar *p, int len)
{
	int i, fd;

	fd = open("/dev/random", OREAD);
	if(fd < 0){
		fprint(2, "keyfs: can't open /dev/random, using rand()\n");
		srand(time(0));
		for(i = 0; i < len; i++)
			p[i] = rand();
		return;
	}
	read(fd, p, len);
	close(fd);
}

void
oldCBCencrypt(char *key7, uchar *p, int len)
{
	uchar ivec[8];
	uchar key[8];
	DESstate s;

	memset(ivec, 0, 8);
	des56to64((uchar*)key7, key);
	setupDESstate(&s, key, ivec);
	desCBCencrypt((uchar*)p, len, &s);
}

void
oldCBCdecrypt(char *key7, uchar *p, int len)
{
	uchar ivec[8];
	uchar key[8];
	DESstate s;

	memset(ivec, 0, 8);
	des56to64((uchar*)key7, key);
	setupDESstate(&s, key, ivec);
	desCBCdecrypt((uchar*)p, len, &s);

}

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

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

	/* pack into buffer */
	buf = malloc(KEYDBOFF + nu*KEYDBLEN);
	if(buf == 0){
		fprint(2, "keyfs: can't write keys file, out of memory\n");
		return;
	}
	p = buf;
	randombytes(p, KEYDBOFF);
	p += KEYDBOFF;
	for(i = 0; i < Nuser; i++)
		for(u = users[i]; u; u = u->link){
			strncpy((char*)p, u->name, Namelen);
			p += Namelen;
			memmove(p, u->key, 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;
		}

	/* encrypt */
	oldCBCencrypt(authkey, buf, p - buf);

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

	free(buf);
	close(fd);
}

int
readusers(void)
{
	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)
		return 0;
	d = dirfstat(fd);
	if(d == nil){
		close(fd);
		return 0;
	}
	buf = malloc(d->length);
	if(buf == 0){
		close(fd);
		free(d);
		return 0;
	}
	n = readn(fd, buf, d->length);
	close(fd);
	free(d);
	if(n != d->length){
		free(buf);
		return 0;
	}

	/* decrypt */
	n -= n % KEYDBLEN;
	oldCBCdecrypt(authkey, buf, n);

	/* unpack */
	nu = 0;
	for(i = KEYDBOFF; i < n; i += KEYDBLEN){
		ep = buf + i;
		u = finduser((char*)ep);
		if(u == 0)
			u = installuser((char*)ep);
		memmove(u->key, 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;
		nu++;
	}
	free(buf);

	print("%d keys read\n", nu);
	return 1;
}

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

	h = hash(name);
	u = emalloc(sizeof *u);
	u->name = strdup(name);
	if(u->name == nil)
		error("malloc failed: %r");
	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; u = u->link)
		if(strcmp(name, u->name) == 0)
			return u;
	return 0;
}

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

	user->removed = 1;
	name = user->name;
	last = &users[hash(name)];
	for(u = *last; u; 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 = 0;
	for(f = fids; f; f = f->next)
		if(f->fid == fid)
			return f;
		else if(!ff && !f->busy)
			ff = f;
	if(ff){
		ff->fid = fid;
		return ff;
	}
	f = emalloc(sizeof *f);
	f->fid = fid;
	f->busy = 0;
	f->user = 0;
	f->next = fids;
	fids = f;
	return f;
}

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

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

	for(;;){
		n = read9pmsg(in, mdata, messagesize);
		if(n == 0)
			continue;
		if(n < 0)
			error("mount read %d", n);
		if(convM2S(mdata, n, &rhdr) == 0)
			continue;

		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 && (now - lastwarning > 24*60*60)){
			syslog(0, "auth", "keyfs starting warnings: %lux %lux", now, lastwarning);
			warning();
			lastwarning = now;
		}
	}
}

int
newkeys(void)
{
	Dir *d;
	static long 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))
		return p;
	error("out of memory");
	return 0;		/* not reached */
}

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");
	}
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.