#include "dat.h"

static char secstore[100];   /* server name */

/* bind in the default network and cs */
static void
bindnetcs(void)
{
	int srvfd;

	if(access("/net/cs", AEXIST) < 0){
		if((srvfd = open("#s/cs", ORDWR)) >= 0){
			mount(srvfd, -1, "/net", MBEFORE, "");
			close(srvfd);
		}
	}
	if(access("/net/il", AEXIST) < 0)
		bind("#I",  "/net", MBEFORE);
}

int
_authdial(char *net, char *authdom)
{
	int fd;
	int vanilla;

	vanilla = net == nil || strcmp(net, "/net") == 0;

	if(vanilla)
		bindnetcs();
	
	/* let cs figure things out */
	fd = authdial(net, authdom);
	if(fd >= 0)
		return fd;

	/* if this is the default network */
	if(vanilla){
		/* ... and cs isn't running */
		if(access("/net/cs", AEXIST) >= 0)
			return -1;
		/* ... use the auth sever passed to us as an arg */
		if(authaddr == nil)
			return -1;
		fd = dial(netmkaddr(authaddr, "il", "566"), 0, 0, 0);
		if(fd >= 0)
			return fd;
		fd = dial(netmkaddr(authaddr, "tcp", "567"), 0, 0, 0);
	}
	return fd;
}

/*
 *  prompt user for a key.  don't care about memory leaks, runs standalone
 */
static Attr*
promptforkey(char *params)
{
	char *v;
	int fd;
	Attr *a, *attr;
	char *def;

	fd = open("/dev/cons", ORDWR);
	if(fd < 0)
		sysfatal("opening /dev/cons: %r");

	attr = parseattr(params);
	fprint(fd, "\n!Adding key:");
	for(a=attr; a; a=a->next)
		if(a->type != AttrQuery && s_to_c(a->name)[0] != '!')
			fprint(fd, " %q=%q", s_to_c(a->name), s_to_c(a->val));
	fprint(fd, "\n");

	for(a=attr; a; a=a->next){
		v = s_to_c(a->name);
		if(a->type != AttrQuery || v[0]=='!')
			continue;
		def = nil;
		if(strcmp(v, "user") == 0)
			def = getuser();
		a->val = readcons(v, def, 0);
		if(a->val == nil)
			sysfatal("user terminated key input");
		a->type = AttrNameval;
	}
	for(a=attr; a; a=a->next){
		v = s_to_c(a->name);
		if(a->type != AttrQuery || v[0]!='!')
			continue;
		def = nil;
		if(strcmp(v+1, "user") == 0)
			def = getuser();
		a->val = readcons(v+1, def, 1);
		if(a->val == nil)
			sysfatal("user terminated key input");
		a->type = AttrNameval;
	}
	fprint(fd, "!\n");
	close(fd);
	return attr;
}

/*
 *  send a key to the mounted factotum
 */
static int
sendkey(Attr *attr)
{
	int fd, rv;
	char buf[1024];

	fd = open("/mnt/factotum/ctl", ORDWR);
	if(fd < 0)
		sysfatal("opening /mnt/factotum/ctl: %r");
	rv = fprint(fd, "key %A\n", attr);
	read(fd, buf, sizeof buf);
	close(fd);
	return rv;
}

/* askuser */
void
askuser(char *params)
{
	Attr *attr;

	attr = promptforkey(params);
	if(attr == nil)
		sysfatal("no key supplied");
	if(sendkey(attr) < 0)
		sysfatal("sending key to factotum: %r");
}

ulong conftaggen;
int
canusekey(Fsstate *fss, Key *k)
{
	int i;

	if(strfindattr(k->attr, "confirm")){
		for(i=0; i<fss->nconf; i++)
			if(fss->conf[i].key == k)
				return fss->conf[i].canuse;
		if(fss->nconf%16 == 0)
			fss->conf = erealloc(fss->conf, (fss->nconf+16)*(sizeof(fss->conf[0])));
		fss->conf[fss->nconf].key = k;
		k->ref++;
		fss->conf[fss->nconf].canuse = -1;
		fss->conf[fss->nconf].tag = conftaggen++;
		fss->nconf++;
		return -1;
	}
	return 1;
}

/* closekey */
void
closekey(Key *k)
{
	if(k == nil)
		return;
	if(--k->ref != 0)
		return;
	freeattr(k->attr);
	freeattr(k->privattr);
	k->attr = (void*)~1;
	k->privattr = (void*)~1;
	k->proto = nil;
	free(k);
}

static uchar*
pstring(uchar *p, uchar *e, char *s)
{
	uint n;

	if(p == nil)
		return nil;
	if(s == nil)
		s = "";
	n = strlen(s);
	if(p+n+BIT16SZ >= e)
		return nil;
	PBIT16(p, n);
	p += BIT16SZ;
	memmove(p, s, n);
	p += n;
	return p;
}

static uchar*
pcarray(uchar *p, uchar *e, uchar *s, uint n)
{
	if(p == nil)
		return nil;
	if(s == nil){
		if(n > 0)
			sysfatal("pcarray");
		s = (uchar*)"";
	}
	if(p+n+BIT16SZ >= e)
		return nil;
	PBIT16(p, n);
	p += BIT16SZ;
	memmove(p, s, n);
	p += n;
	return p;
}

uchar*
convAI2M(AuthInfo *ai, uchar *p, int n)
{
	uchar *e = p+n;

	p = pstring(p, e, ai->cuid);
	p = pstring(p, e, ai->suid);
	p = pstring(p, e, ai->cap);
	p = pcarray(p, e, ai->secret, ai->nsecret);
	return p;
}

int
failure(Fsstate *s, char *fmt, ...)
{
	char e[ERRMAX];
	va_list arg;

	if(fmt == nil)
		rerrstr(s->err, sizeof(s->err));
	else {
		va_start(arg, fmt);
		snprint(e, sizeof e, fmt, arg);
		va_end(arg);
		strecpy(s->err, s->err+sizeof(s->err), e);
		errstr(e, sizeof e);
	}
	flog("%d: failure %s", s->seqnum, s->err);
	return RpcFailure;
}

static int
hasqueries(Attr *a)
{
	for(; a; a=a->next)
		if(a->type == AttrQuery)
			return 1;
	return 0;
}

char *ignored[] = {
	"role",
};

static int
ignoreattr(char *s)
{
	int i;

	for(i=0; i<nelem(ignored); i++)
		if(strcmp(ignored[i], s)==0)
			return 1;
	return 0;
}

int
findkey(Key **ret, Fsstate *fss, int who, int skip, Attr *attr0, char *fmt, ...)
{
	int i, s, nmatch;
	char buf[1024], *p;
	va_list arg;
	Attr *a, *attr1, **l;
	Key *k;

	*ret = nil;
	switch(who&Kwho){
	default:
		werrstr("bad who %d", who);
		failure(fss, nil);
		return failure(fss, nil);
	case Kowner:
		break;
	case Kuser:
		if(strcmp(fss->sysuser, owner) != 0){
			werrstr("%q can't use %q's keys", fss->sysuser, owner);
			return failure(fss, nil);
		}
		break;
	}

	if(fmt){
		va_start(arg, fmt);
		doprint(buf, buf+sizeof buf, fmt, arg);
		va_end(arg);
		attr1 = parseattr(buf);
	}else
		attr1 = nil;

	p = strfindattr(attr0, "proto");
	if(p == nil)
		p = strfindattr(attr1, "proto");
	if(p && findproto(p) == nil){
		werrstr("unknown protocol %s", p);
		freeattr(attr1);
		return failure(fss, nil);
	}

	nmatch = 0; 
	for(i=0; i<ring->nkey; i++){
		k = ring->key[i];
		if(matchattr(attr0, k->attr, k->privattr) && matchattr(attr1, k->attr, k->privattr)){
			if(nmatch++ < skip)
				continue;
			if(!(who&Knoconf)){
				switch(canusekey(fss, k)){
				case -1:
					freeattr(attr1);
					return RpcConfirm;
				case 0:
					continue;
				case 1:
					break;
				}
			}
			freeattr(attr1);
			k->ref++;
			*ret = k;
			return RpcOk;
		}
	}
	flog("%d: no key matches %A %A", fss->seqnum, attr0, attr1);
	werrstr("no key matches %A %A", attr0, attr1);
	s = RpcFailure;
	if(askforkeys && (hasqueries(attr0) || hasqueries(attr1))){
		if(nmatch == 0){
			attr0 = copyattr(attr0);
			for(l=&attr0; *l; l=&(*l)->next)
				;
			*l = attr1;
			for(l=&attr0; *l; ){
				if(ignoreattr(s_to_c((*l)->name))){
					a = *l;
					*l = (*l)->next;
					a->next = nil;
					freeattr(a);
				}else
					l = &(*l)->next;
			}
			attr0 = sortattr(attr0);
			snprint(fss->keyinfo, sizeof fss->keyinfo, "%A", attr0);
			freeattr(attr0);
			attr1 = nil;	/* attr1 was linked to attr0 */
		}else
			fss->keyinfo[0] = '\0';
		s = RpcNeedkey;
	}
	freeattr(attr1);
	if(s == RpcFailure)
		return failure(fss, nil);	/* loads error string */
	return s;
}

int
findp9authkey(Key **k, Fsstate *fss)
{
	char *dom;

	/*
	 * We don't use fss->attr here because we don't
	 * care about what the user name is set to, for instance.
	 */
	if(dom = strfindattr(fss->attr, "dom"))
		return findkey(k, fss, Kowner, 0, nil, "proto=p9sk1 dom=%q role=server user?", dom);
	else
		return findkey(k, fss, Kowner, 0, nil, "proto=p9sk1 role=server dom? user?");
}

Proto*
findproto(char *name)
{
	int i;

	for(i=0; prototab[i]; i++)
		if(strcmp(name, prototab[i]->name) == 0)
			return prototab[i];
	return nil;
}

char*
getnvramkey(int flag)
{
	char *s;
	Nvrsafe safe;

	memset(&safe, 0, sizeof safe);
	/*
	 * readnvram can return -1 meaning nvram wasn't written,
	 * but safe still holds good data.
	 */
	if(readnvram(&safe, flag)<0 && safe.authid[0]=='0') 
		return nil;

	s = emalloc(512);
	fmtinstall('H', encodeconv);
	sprint(s, "key proto=p9sk1 user=%q dom=%q !hex=%.*H !password=______", 
		safe.authid, safe.authdom, DESKEYLEN, safe.machkey);
	writehostowner(safe.authid);

	return s;
}

int
isclient(char *role)
{
	if(role == nil){
		werrstr("role not specified");
		return -1;
	}
	if(strcmp(role, "server") == 0)
		return 0;
	if(strcmp(role, "client") == 0)
		return 1;
	werrstr("unknown role %q", role);
	return -1;
}

static int
hasname(Attr *a0, Attr *a1, char *name)
{
	return findattr(a0, name) || findattr(a1, name);
}

static int
hasnameval(Attr *a0, Attr *a1, char *name, char *val)
{
	Attr *a;

	for(a=findattr(a0, name); a; a=findattr(a->next, name))
		if(strcmp(s_to_c(a->val), val) == 0)
			return 1;
	for(a=findattr(a1, name); a; a=findattr(a->next, name))
		if(strcmp(s_to_c(a->val), val) == 0)
			return 1;
	return 0;
}

int
matchattr(Attr *pat, Attr *a0, Attr *a1)
{
	int type;

	for(; pat; pat=pat->next){
		type = pat->type;
		if(ignoreattr(s_to_c(pat->name)))
			type = AttrDefault;
		switch(type){
		case AttrQuery:		/* name=something be present */
			if(!hasname(a0, a1, s_to_c(pat->name)))
				return 0;
			break;
		case AttrNameval:	/* name=val must be present */
			if(!hasnameval(a0, a1, s_to_c(pat->name), s_to_c(pat->val)))
				return 0;
			break;
		case AttrDefault:	/* name=val must be present if name=anything is present */
			if(hasname(a0, a1, s_to_c(pat->name)) && !hasnameval(a0, a1, s_to_c(pat->name), s_to_c(pat->val)))
				return 0;
			break;
		}
	}
	return 1;		
}

/*
 *  create a change uid capability 
 */
char*
mkcap(char *user)
{
	uchar rand[20];
	char *cap;
	char *key;
	int nuser;
	uchar hash[SHA1dlen];
	int fd;

	if(strchr(user, '@') != nil)
		return nil;

	/* create the capability */
	nuser = strlen(user);
	cap = emalloc(nuser+1+sizeof(rand)*3+1);
	strcpy(cap, user);
	cap[nuser] = '@';
	memrandom(rand, sizeof(rand));
	key = cap+nuser+1;
	enc64(key, sizeof(rand)*3, rand, sizeof(rand));

	/* hash the capability */
	hmac_sha1((uchar*)user, strlen(user), (uchar*)key, strlen(key), hash, nil);

	/* give the kernel the hash */
	fd = open("#¤/caphash", OWRITE);
	if(fd < 0){
		free(cap);
		return nil;
	}
	if(write(fd, hash, SHA1dlen) < 0){
		free(cap);
		close(fd);
		return nil;
	}
	close(fd);

	return cap;
}

int
phaseerror(Fsstate *s, char *op)
{
	char tmp[32];

	werrstr("protocol phase error: %s in state %s", op, phasename(s, s->phase, tmp));	
	return RpcPhase;
}

char*
phasename(Fsstate *fss, int phase, char *tmp)
{
	char *name;

	if(fss->phase == Broken)
		name = "Broken";
	else if(phase == Established)
		name = "Established";
	else if(phase == Notstarted)
		name = "Notstarted";
	else if(phase < 0 || phase >= fss->maxphase
	|| (name = fss->phasename[phase]) == nil){
		sprint(tmp, "%d", phase);
		name = tmp;
	}
	return name;
}

static int
outin(char *prompt, char *def, int len)
{
	String *s;

	s = readcons(prompt, def, 0);
	if(s == nil)
		return -1;
	strncpy(def, s_to_c(s), len);
	def[len-1] = 0;
	s_free(s);
	return strlen(def);
}

/*
 *  get host owner and set it
 */
void
promptforhostowner(void)
{
	char owner[64], *p;

	/* hack for bitsy; can't prompt during boot */
	if(p = getenv("user")){
		writehostowner(p);
		return;
	}

	strcpy(owner, "none");
	do{
		outin("user", owner, sizeof(owner));
	} while(*owner == 0);
	writehostowner(owner);
}

/*
 * Insert a key into the keyring.
 * If the public attributes are identical to some other key, replace that one.
 */
int
replacekey(Key *kn)
{
	int i;
	Key *k;

	for(i=0; i<ring->nkey; i++){
		k = ring->key[i];
		if(matchattr(kn->attr, k->attr, nil) && matchattr(k->attr, kn->attr, nil)){
			closekey(k);
			kn->ref++;
			ring->key[i] = kn;
			return 0;
		}
	}
	if(ring->nkey%16 == 0)
		ring->key = erealloc(ring->key, (ring->nkey+16)*sizeof(ring->key[0]));
	kn->ref++;
	ring->key[ring->nkey++] = kn;
	return 0;
}

char*
safecpy(char *to, char *from, int n)
{
	memset(to, 0, n);
	if(n == 1)
		return to;
	strncpy(to, from, n-1);
	return to;
}

int
secdial(void)
{
	char *p, buf[80], *f[3];
	int fd, nf;

	bindnetcs();

	p = secstore; /* take it from writehostowner, if set there */
	if(*p == 0)	  /* else use the authserver */
		p = "$auth";
	fd = dial(netmkaddr(p, "tcp", "5356"), 0, 0, 0);
	if(fd >= 0)
		return fd;

	/* if cs isn't running, try the auth server */
	if(access("/net/cs", AEXIST) >= 0)
		return -1;
	if(authaddr == nil)
		return -1;

	/*
	 * authaddr is something like il!host!566 or tcp!host!567.
	 * extract host, accounting for a change of format to something
	 * like il!host or tcp!host or host.
	 */
	safecpy(buf, authaddr, sizeof buf);
	nf = getfields(buf, f, nelem(f), 0, "!");
	switch(nf){
	default:
		return -1;
	case 1:
		p = f[0];
		break;
	case 2:
	case 3:
		p = f[1];
		break;
	}
	fd = dial(netmkaddr(p, "tcp", "5356"), 0, 0, 0);
	if(fd >= 0)
		return fd;
	return -1;
}

Attr*
setattr(Attr *a, char *fmt, ...)
{
	char buf[1024];
	va_list arg;
	Attr *b;

	va_start(arg, fmt);
	doprint(buf, buf+sizeof buf, fmt, arg);
	va_end(arg);
	b = parseattr(buf);
	a = setattrs(a, b);
	setmalloctag(a, getcallerpc(&a));
	freeattr(b);
	return a;
}

/*
 *  add attributes in list b to list a.  If any attributes are in
 *  both lists, replace those in a by those in b.
 */
Attr*
setattrs(Attr *a, Attr *b)
{
	int found;
	Attr **l, *freea;

	for(; b; b=b->next){
		found = 0;
		for(l=&a; *l; ){
			if(strcmp(s_to_c(b->name), s_to_c((*l)->name)) == 0){
				switch(b->type){
				case AttrNameval:
					if(!found){
						found = 1;
						s_free((*l)->val);
						(*l)->val = s_incref(b->val);
						(*l)->type = AttrNameval;
						l = &(*l)->next;
					}else{
						freea = *l;
						*l = (*l)->next;
						freea->next = nil;
						freeattr(freea);
					}
					break;
				case AttrQuery:
					found++;
					break;
				}
			}else
				l = &(*l)->next;
		}
		if(found == 0){
			*l = mkattr(b->type, s_to_c(b->name), s_to_c(b->val), nil);
			setmalloctag(*l, getcallerpc(&a));
		}
	}
	return a;		
}

void
setmalloctaghere(void *v)
{
	setmalloctag(v, getcallerpc(&v));
}

Attr*
sortattr(Attr *a)
{
	int i;
	Attr *anext, *a0, *a1, **l;

	if(a == nil || a->next == nil)
		return a;

	/* cut list in halves */
	a0 = nil;
	a1 = nil;
	i = 0;
	for(; a; a=anext){
		anext = a->next;
		if(i++%2){
			a->next = a0;
			a0 = a;
		}else{
			a->next = a1;
			a1 = a;
		}
	}

	/* sort */
	a0 = sortattr(a0);
	a1 = sortattr(a1);

	/* merge */
	l = &a;
	while(a0 || a1){
		if(a1==nil){
			anext = a0;
			a0 = a0->next;
		}else if(a0==nil){
			anext = a1;
			a1 = a1->next;
		}else if(strcmp(s_to_c(a0->name), s_to_c(a1->name)) < 0){
			anext = a0;
			a0 = a0->next;
		}else{
			anext = a1;
			a1 = a1->next;
		}
		*l = anext;
		l = &(*l)->next;
	}
	*l = nil;
	return a;
}

char*
strfindattr(Attr *a, char *n)
{
	a = findattr(a, n);
	if(a == nil)
		return nil;
	return s_to_c(a->val);
}

int
toosmall(Fsstate *fss, uint n)
{
	fss->rpc.nwant = n;
	return RpcToosmall;
}

void
writehostowner(char *owner)
{
	int fd;
	char *s;

	if((s = strchr(owner,'@')) != nil){
		*s++ = 0;
		strncpy(secstore, s, (sizeof secstore)-1);
	}
	fd = open("#c/hostowner", OWRITE);
	if(fd >= 0){
		if(fprint(fd, "%s", owner) < 0)
			fprint(2, "setting #c/hostowner to %q: %r\n", owner);
		close(fd);
	}
}

