shithub: riscv

ref: dfb4b522830edab8f3856289d326d6cf5e930644
dir: /sys/src/cmd/auth/authsrv.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ndb.h>
#include <regexp.h>
#include <libsec.h>
#include <authsrv.h>
#include "authcmdlib.h"

Ndb *db;
char raddr[128];
uchar zeros[32];

typedef struct Keyslot Keyslot;
struct Keyslot
{
	Authkey;
	char	id[ANAMELEN];
};
Keyslot hkey, akey, ukey;
uchar keyseed[SHA2_256dlen];

char ticketform;

/* Microsoft auth constants */
enum {
	MShashlen = 16,
	MSchallen = 8,
	MSresplen = 24,
	MSchallenv2 = 16,
};

void	pak(Ticketreq*);
void	ticketrequest(Ticketreq*);
void	challengebox(Ticketreq*);
void	changepasswd(Ticketreq*);
void	apop(Ticketreq*, int);
void	chap(Ticketreq*);
void	ntlm(Ticketreq*);
void	mschap(Ticketreq*, int);
void	vnc(Ticketreq*);
int	speaksfor(char*, char*);
void	replyerror(char*, ...);
void	getraddr(char*);
void	initkeyseed(void);
void	mkkey(char*, Authkey*);
void	mkticket(Ticketreq*, Ticket*);
void	nthash(uchar hash[MShashlen], char *passwd);
void	lmhash(uchar hash[MShashlen], char *passwd);
void	ntv2hash(uchar hash[MShashlen], char *passwd, char *user, char *dom);
void	mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen]);
void	desencrypt(uchar data[8], uchar key[7]);
void	tickauthreply(Ticketreq*, Authkey*);
void	tickauthreply2(Ticketreq*, Authkey*, uchar *, int, uchar *, int);
void	safecpy(char*, char*, int);
void	catch(void*, char*);

void
main(int argc, char *argv[])
{
	char buf[TICKREQLEN];
	Ticketreq tr;
	int n;

	ARGBEGIN{
	case 'N':
		ticketform = 1;
		break;
	}ARGEND

	strcpy(raddr, "unknown");
	if(argc >= 1)
		getraddr(argv[argc-1]);

	alarm(10*60*1000);	/* kill a connection after 10 minutes */

	private();
	initkeyseed();

	db = ndbopen("/lib/ndb/auth");
	if(db == 0)
		syslog(0, AUTHLOG, "no /lib/ndb/auth");

	for(;;){
		n = readn(0, buf, sizeof(buf));
		if(n <= 0 || convM2TR(buf, n, &tr) <= 0)
			exits(0);
		switch(tr.type){
		case AuthTreq:
			ticketrequest(&tr);
			break;
		case AuthChal:
			challengebox(&tr);
			break;
		case AuthPass:
			changepasswd(&tr);
			break;
		case AuthApop:
			apop(&tr, AuthApop);
			break;
		case AuthChap:
			chap(&tr);
			break;
		case AuthMSchap:
			mschap(&tr, MSchallen);
			break;
		case AuthMSchapv2:
			mschap(&tr, MSchallenv2);
			break;
		case AuthNTLM:
			ntlm(&tr);
			break;
		case AuthCram:
			apop(&tr, AuthCram);
			break;
		case AuthVNC:
			vnc(&tr);
			break;
		case AuthPAK:
			pak(&tr);
			continue;
		default:
			syslog(0, AUTHLOG, "unknown ticket request type: %d", tr.type);
			exits(0);
		}
		/* invalidate pak keys */
		akey.id[0] = 0;
		hkey.id[0] = 0;
		ukey.id[0] = 0;
	}
	/* not reached */
}

void
pak1(char *u, Keyslot *k)
{
	uchar y[PAKYLEN];
	PAKpriv p;

	safecpy(k->id, u, sizeof(k->id));
	if(!findkey(KEYDB, k->id, k) || tsmemcmp(k->aes, zeros, AESKEYLEN) == 0) {
		syslog(0, AUTHLOG, "pak-fail no AES key for id %s", k->id);
		/* make one up so caller doesn't know it was wrong */
		mkkey(k->id, k);
		authpak_hash(k, k->id);
	}
	authpak_new(&p, k, y, 0);
	if(write(1, y, PAKYLEN) != PAKYLEN)
		exits(0);
	if(readn(0, y, PAKYLEN) != PAKYLEN)
		exits(0);
	if(authpak_finish(&p, k, y))
		exits(0);
}

void
pak(Ticketreq *tr)
{
	static uchar ok[1] = {AuthOK};

	if(write(1, ok, 1) != 1)
		exits(0);

	/* invalidate pak keys */
	akey.id[0] = 0;
	hkey.id[0] = 0;
	ukey.id[0] = 0;

	if(tr->hostid[0]) {
		if(tr->authid[0])
			pak1(tr->authid, &akey);
		pak1(tr->hostid, &hkey);
	} else if(tr->uid[0]) {
		pak1(tr->uid, &ukey);
	}

	ticketform = 1;
}

int
getkey(char *u, Keyslot *k)
{
	/* empty user id is an error */
	if(*u == 0)
		exits(0);

	if(k == &hkey && strcmp(u, k->id) == 0)
		return 1;
	if(k == &akey && strcmp(u, k->id) == 0)
		return 1;
	if(k == &ukey && strcmp(u, k->id) == 0)
		return 1;

	if(ticketform != 0){
		syslog(0, AUTHLOG, "need DES key for %s, but DES is disabled", u);
		replyerror("DES is disabled");
		exits(0);
	}

	return findkey(KEYDB, u, k);
}

void
ticketrequest(Ticketreq *tr)
{
	char tbuf[2*MAXTICKETLEN+1];
	Ticket t;
	int n;

	if(tr->uid[0] == 0)
		exits(0);
	if(!getkey(tr->authid, &akey)){
		/* make one up so caller doesn't know it was wrong */
		mkkey(tr->authid, &akey);
		syslog(0, AUTHLOG, "tr-fail authid %s", tr->authid);
	}
	if(!getkey(tr->hostid, &hkey)){
		/* make one up so caller doesn't know it was wrong */
		mkkey(tr->hostid, &hkey);
		syslog(0, AUTHLOG, "tr-fail hostid %s(%s)", tr->hostid, raddr);
	}
	mkticket(tr, &t);
	if(!speaksfor(tr->hostid, tr->uid)){
		mkkey(tr->authid, &akey);
		mkkey(tr->hostid, &hkey);
		syslog(0, AUTHLOG, "tr-fail %s@%s(%s) -> %s@%s no speaks for",
			tr->uid, tr->hostid, raddr, tr->uid, tr->authid);
	}
	n = 0;
	tbuf[n++] = AuthOK;
	t.num = AuthTc;
	n += convT2M(&t, tbuf+n, sizeof(tbuf)-n, &hkey);
	t.num = AuthTs;
	n += convT2M(&t, tbuf+n, sizeof(tbuf)-n, &akey);
	if(write(1, tbuf, n) != n)
		exits(0);

	syslog(0, AUTHLOG, "tr-ok %s@%s(%s) -> %s@%s", tr->uid, tr->hostid, raddr, tr->uid, tr->authid);
}

void
challengebox(Ticketreq *tr)
{
	char kbuf[DESKEYLEN], nkbuf[DESKEYLEN], buf[NETCHLEN+1];
	char *key, *netkey, *err;
	long chal;

	if(tr->uid[0] == 0)
		exits(0);
	key = finddeskey(KEYDB, tr->uid, kbuf);
	netkey = finddeskey(NETKEYDB, tr->uid, nkbuf);
	if(key == nil && netkey == nil){
		/* make one up so caller doesn't know it was wrong */
		genrandom((uchar*)nkbuf, DESKEYLEN);
		netkey = nkbuf;
		syslog(0, AUTHLOG, "cr-fail uid %s@%s", tr->uid, raddr);
	}

	if(!getkey(tr->hostid, &hkey)){
		/* make one up so caller doesn't know it was wrong */
		mkkey(tr->hostid, &hkey);
		syslog(0, AUTHLOG, "cr-fail hostid %s %s@%s", tr->hostid, tr->uid, raddr);
	}

	/*
	 * challenge-response
	 */
	memset(buf, 0, sizeof(buf));
	buf[0] = AuthOK;
	chal = nfastrand(MAXNETCHAL);
	sprint(buf+1, "%lud", chal);
	if(write(1, buf, NETCHLEN+1) != NETCHLEN+1)
		exits(0);
	if(readn(0, buf, NETCHLEN) != NETCHLEN)
		exits(0);
	if(!(key != nil && netcheck(key, chal, buf))
	&& !(netkey != nil && netcheck(netkey, chal, buf))
	&& (err = secureidcheck(tr->uid, buf)) != nil){
		replyerror("cr-fail %s %s %s", err, tr->uid, raddr);
		logfail(tr->uid);
		return;
	}
	succeed(tr->uid);

	/*
	 *  reply with ticket & authenticator
	 */
	tickauthreply(tr, &hkey);

	syslog(0, AUTHLOG, "cr-ok %s@%s(%s)", tr->uid, tr->hostid, raddr);
}

void
changepasswd(Ticketreq *tr)
{
	char tbuf[MAXTICKETLEN+1], prbuf[MAXPASSREQLEN], *err;
	Passwordreq pr;
	Authkey nkey;
	Ticket t;
	int n, m;

	if(!getkey(tr->uid, &ukey)){
		/* make one up so caller doesn't know it was wrong */
		mkkey(tr->uid, &ukey);
		syslog(0, AUTHLOG, "cp-fail uid %s@%s", tr->uid, raddr);
	}

	/* send back a ticket with a new key */
	mkticket(tr, &t);
	t.num = AuthTp;
	n = 0;
	tbuf[n++] = AuthOK;
	n += convT2M(&t, tbuf+n, sizeof(tbuf)-n, &ukey);
	if(write(1, tbuf, n) != n)
		exits(0);

	/* loop trying passwords out */
	for(;;){
		for(n=0; (m = convM2PR(prbuf, n, &pr, &t)) <= 0; n += m){
			m = -m;
			if(m <= n || m > sizeof(prbuf))
				exits(0);
			m -= n;
			if(readn(0, prbuf+n, m) != m)
				exits(0);
		}
		if(pr.num != AuthPass){
			replyerror("protocol botch1: %s", raddr);
			exits(0);
		}
		passtokey(&nkey, pr.old);
		if(tsmemcmp(ukey.des, nkey.des, DESKEYLEN) != 0){
			replyerror("protocol botch2: %s", raddr);
			continue;
		}
		if(tsmemcmp(ukey.aes, zeros, AESKEYLEN) != 0 && tsmemcmp(ukey.aes, nkey.aes, AESKEYLEN) != 0){
			replyerror("protocol botch3: %s", raddr);
			continue;
		}
		if(*pr.new){
			err = okpasswd(pr.new);
			if(err){
				replyerror("%s %s", err, raddr);
				continue;
			}
			passtokey(&nkey, pr.new);
		}
		if(pr.changesecret && setsecret(KEYDB, tr->uid, pr.secret) == 0){
			replyerror("can't write secret %s", raddr);
			continue;
		}
		if(*pr.new && setkey(KEYDB, tr->uid, &nkey) == 0){
			replyerror("can't write key %s", raddr);
			continue;
		}
		memmove(ukey.des, nkey.des, DESKEYLEN);
		memmove(ukey.aes, nkey.aes, AESKEYLEN);
		break;
	}
	succeed(tr->uid);

	prbuf[0] = AuthOK;
	if(write(1, prbuf, 1) != 1)
		exits(0);
}

static char*
domainname(void)
{
	static char sysname[Maxpath];
	static char *domain;
	int n;

	if(domain != nil)
		return domain;
	if(*sysname)
		return sysname;

	domain = csgetvalue(0, "sys", sysname, "dom", nil);
	if(domain != nil)
		return domain;

	n = readfile("/dev/sysname", sysname, sizeof(sysname)-1);
	if(n < 0){
		strcpy(sysname, "kremvax");
		return sysname;
	}
	sysname[n] = 0;

	return sysname;
}

static int
h2b(char c)
{
	if(c >= '0' && c <= '9')
		return c - '0';
	if(c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	if(c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	return 0;
}

void
apop(Ticketreq *tr, int type)
{
	int challen, i, n, tries;
	char *secret, *p;
	Ticketreq treq;
	DigestState *s;
	char sbuf[SECRETLEN];
	char trbuf[TICKREQLEN];
	char buf[MD5dlen*2];
	uchar digest[MD5dlen], resp[MD5dlen];
	ulong rb[4];
	char chal[256];

	USED(tr);

	/*
	 *  Create a challenge and send it.
	 */
	genrandom((uchar*)rb, sizeof(rb));
	p = chal;
	p += snprint(p, sizeof(chal), "<%lux%lux.%lux%lux@%s>",
		rb[0], rb[1], rb[2], rb[3], domainname());
	challen = p - chal;
	print("%c%-5d%s", AuthOKvar, challen, chal);

	tries = 5;
Retry:
	if(--tries < 0)
		exits(0);

	/*
	 *  get ticket request
	 */
	n = readn(0, trbuf, sizeof(trbuf));
	if(n <= 0 || convM2TR(trbuf, n, &treq) <= 0)
		exits(0);
	tr = &treq;
	if(tr->type != type || tr->uid[0] == 0)
		exits(0);

	/*
	 * read response
	 */
	if(readn(0, buf, MD5dlen*2) != MD5dlen*2)
		exits(0);
	for(i = 0; i < MD5dlen; i++)
		resp[i] = (h2b(buf[2*i])<<4)|h2b(buf[2*i+1]);

	/*
	 * lookup
	 */
	secret = findsecret(KEYDB, tr->uid, sbuf);
	if(!getkey(tr->hostid, &hkey) || secret == nil){
		replyerror("apop-fail bad response %s", raddr);
		goto Retry;
	}

	/*
	 *  check for match
	 */
	if(type == AuthCram){
		hmac_md5((uchar*)chal, challen,
			(uchar*)secret, strlen(secret),
			digest, nil);
	} else {
		s = md5((uchar*)chal, challen, 0, 0);
		md5((uchar*)secret, strlen(secret), digest, s);
	}
	if(tsmemcmp(digest, resp, MD5dlen) != 0){
		replyerror("apop-fail bad response %s", raddr);
		logfail(tr->uid);
		goto Retry;
	}

	succeed(tr->uid);

	/*
	 *  reply with ticket & authenticator
	 */
	tickauthreply(tr, &hkey);

	if(type == AuthCram)
		syslog(0, AUTHLOG, "cram-ok %s %s", tr->uid, raddr);
	else
		syslog(0, AUTHLOG, "apop-ok %s %s", tr->uid, raddr);
}

enum {
	VNCchallen=	16,
};

/* VNC reverses the bits of each byte before using as a des key */
uchar swizzletab[256] = {
 0x0, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
 0x8, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
 0x4, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
 0xc, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
 0x2, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
 0xa, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
 0x6, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
 0xe, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
 0x1, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
 0x9, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
 0x5, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
 0xd, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
 0x3, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
 0xb, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
 0x7, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
 0xf, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
};

void
vnc(Ticketreq *tr)
{
	uchar chal[VNCchallen+6];
	uchar reply[VNCchallen];
	char sbuf[SECRETLEN];
	char *secret;
	DESstate s;
	int i;

	if(tr->uid[0] == 0)
		exits(0);

	/*
	 *  Create a challenge and send it.
	 */
	genrandom(chal+6, VNCchallen);
	chal[0] = AuthOKvar;
	sprint((char*)chal+1, "%-5d", VNCchallen);
	if(write(1, chal, sizeof(chal)) != sizeof(chal))
		exits(0);

	/*
	 *  get response
	 */
	if(readn(0, reply, sizeof(reply)) != sizeof(reply))
		exits(0);

	/*
	 *  lookup keys (and swizzle bits)
	 */
	memset(sbuf, 0, sizeof(sbuf));
	secret = findsecret(KEYDB, tr->uid, sbuf);
	if(!getkey(tr->hostid, &hkey) || secret == nil){
		mkkey(tr->hostid, &hkey);
		genrandom((uchar*)sbuf, sizeof(sbuf));
		secret = sbuf;
	}
	for(i = 0; i < 8; i++)
		secret[i] = swizzletab[(uchar)secret[i]];

	/*
	 *  decrypt response and compare
	 */
	setupDESstate(&s, (uchar*)secret, nil);
	desECBdecrypt(reply, sizeof(reply), &s);
	if(tsmemcmp(reply, chal+6, VNCchallen) != 0){
		replyerror("vnc-fail bad response %s", raddr);
		logfail(tr->uid);
		return;
	}
	succeed(tr->uid);

	/*
	 *  reply with ticket & authenticator
	 */
	tickauthreply(tr, &hkey);

	syslog(0, AUTHLOG, "vnc-ok %s %s", tr->uid, raddr);
}

void
chap(Ticketreq *tr)
{
	char *secret;
	DigestState *s;
	char sbuf[SECRETLEN];
	uchar digest[MD5dlen];
	char chal[CHALLEN];
	OChapreply reply;
	int tries;

	/*
	 *  Create a challenge and send it.
	 */
	genrandom((uchar*)chal, sizeof(chal));
	if(write(1, chal, sizeof(chal)) != sizeof(chal))
		exits(0);

	tries = 5;
Retry:
	if(--tries < 0)
		exits(0);	

	/*
	 *  get chap reply
	 */
	if(readn(0, &reply, OCHAPREPLYLEN) < 0)
		exits(0);

	safecpy(tr->uid, reply.uid, sizeof(tr->uid));
	if(tr->uid[0] == 0)
		exits(0);

	/*
	 * lookup
	 */
	secret = findsecret(KEYDB, tr->uid, sbuf);
	if(!getkey(tr->hostid, &hkey) || secret == nil){
		replyerror("chap-fail bad response %s", raddr);
		goto Retry;
	}

	/*
	 *  check for match
	 */
	s = md5(&reply.id, 1, 0, 0);
	md5((uchar*)secret, strlen(secret), 0, s);
	md5((uchar*)chal, sizeof(chal), digest, s);

	if(tsmemcmp(digest, reply.resp, MD5dlen) != 0){
		replyerror("chap-fail bad response %s", raddr);
		logfail(tr->uid);
		goto Retry;
	}

	succeed(tr->uid);

	/*
	 *  reply with ticket & authenticator
	 */
	tickauthreply(tr, &hkey);

	syslog(0, AUTHLOG, "chap-ok %s %s", tr->uid, raddr);

	/* no secret after ticket */
	exits(0);
}

static uchar ntblobsig[] = {0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

void
ntlm(Ticketreq *tr)
{
	char *secret;
	char sbuf[SECRETLEN], windom[DOMLEN];
	uchar chal[MSchallen], ntblob[1024];
	uchar hash[MShashlen];
	uchar resp[MSresplen];
	NTLMreply reply;
	int dupe, lmok, ntok, ntbloblen;
	DigestState *s;
	int tries;

	/*
	 *  Create a challenge and send it.
	 */
	genrandom(chal, sizeof(chal));
	if(write(1, chal, MSchallen) != MSchallen)
		exits(0);

	tries = 5;
Retry:
	if(--tries < 0)
		exits(0);

	/*
	 *  get NTLM reply
	 */
	if(readn(0, &reply, NTLMREPLYLEN) < 0)
		exits(0);

	ntbloblen = 0;
	if(memcmp(reply.NTresp+16, ntblobsig, sizeof(ntblobsig)) == 0){
		ntbloblen = reply.len[0] | reply.len[1]<<8;
		ntbloblen -= NTLMREPLYLEN;
		if(ntbloblen < 0 || ntbloblen > sizeof(ntblob)-8)
			exits(0);
		if(readn(0, ntblob+8, ntbloblen) < 0)
			exits(0);
		memmove(ntblob, reply.NTresp+16, 8);
		ntbloblen += 8;
	}

	safecpy(tr->uid, reply.uid, sizeof(tr->uid));
	if(tr->uid[0] == 0)
		exits(0);

	safecpy(windom, reply.dom, sizeof(windom));

	/*
	 * lookup
	 */
	secret = findsecret(KEYDB, tr->uid, sbuf);
	if(!getkey(tr->hostid, &hkey) || secret == nil){
		replyerror("ntlm-fail bad response %s@%s/%s(%s)", tr->uid, windom, tr->hostid, raddr);
		goto Retry;
	}

	if(ntbloblen > 0){
		/* NTLMv2 */
		ntv2hash(hash, secret, tr->uid, windom);

		/*
		 * LmResponse = Cat(HMAC_MD5(LmHash, Cat(SC, CC)), CC)
		 */
		s = hmac_md5(chal, MSchallen, hash, MShashlen, nil, nil);
		hmac_md5((uchar*)reply.LMresp+16, MSchallen, hash, MShashlen, resp, s);
		lmok = tsmemcmp(resp, reply.LMresp, 16) == 0;

		/*
		 * NtResponse = Cat(HMAC_MD5(NtHash, Cat(SC, NtBlob)), NtBlob)
		 */
		s = hmac_md5(chal, MSchallen, hash, MShashlen, nil, nil);
		hmac_md5(ntblob, ntbloblen, hash, MShashlen, resp, s);
		ntok = tsmemcmp(resp, reply.NTresp, 16) == 0;

		/*
		 * LM response can be all zeros or signature key,
		 * so make it valid when the NT respone matches.
		 */
		lmok |= ntok;
		dupe = 0;
	} else if(memcmp(reply.NTresp, zeros, MSresplen) == 0){
		/* LMv2 */
		ntv2hash(hash, secret, tr->uid, windom);

		/*
		 * LmResponse = Cat(HMAC_MD5(LmHash, Cat(SC, CC)), CC)
		 */
		s = hmac_md5(chal, MSchallen, hash, MShashlen, nil, nil);
		hmac_md5((uchar*)reply.LMresp+16, MSchallen, hash, MShashlen, resp, s);
		lmok = ntok = tsmemcmp(resp, reply.LMresp, 16) == 0;
		dupe = 0;
	} else {
		/* LM+NTLM */
		lmhash(hash, secret);
		mschalresp(resp, hash, chal);
		lmok = tsmemcmp(resp, reply.LMresp, MSresplen) == 0;

		nthash(hash, secret);
		mschalresp(resp, hash, chal);
		ntok = tsmemcmp(resp, reply.NTresp, MSresplen) == 0;
		dupe = tsmemcmp(reply.LMresp, reply.NTresp, MSresplen) == 0;
	}

	/*
	 * It is valid to send the same response in both the LM and NTLM 
	 * fields provided one of them is correct, if neither matches,
	 * or the two fields are different and either fails to match, 
	 * the whole sha-bang fails.
	 *
	 * This is an improvement in security as it allows clients who
	 * wish to do NTLM auth (which is insecure) not to send
	 * LM tokens (which is very insecure).
	 *
	 * Windows servers supports clients doing this also though
	 * windows clients don't seem to use the feature.
	 */
	if((!ntok && !lmok) || ((!ntok || !lmok) && !dupe)){
		replyerror("ntlm-fail bad response %s@%s/%s(%s)", tr->uid, windom, tr->hostid, raddr);
		logfail(tr->uid);
		goto Retry;
	}

	succeed(tr->uid);

	/*
	 *  reply with ticket & authenticator
	 */
	tickauthreply(tr, &hkey);

	syslog(0, AUTHLOG, "ntlm-ok %s@%s/%s(%s)", tr->uid, windom, tr->hostid, raddr);

	exits(0);
}

void
mschap(Ticketreq *tr, int nchal)
{
	char *secret;
	char sbuf[SECRETLEN];
	uchar chal[16];
	uchar hash[MShashlen];
	uchar resp[MSresplen];
	OMSchapreply reply;
	int dupe, lmok, ntok;
	uchar phash[SHA1dlen], chash[SHA1dlen], ahash[SHA1dlen];
	DigestState *s;
	int tries;

	/*
	 *  Create a challenge and send it.
	 */
	genrandom(chal, sizeof(chal));
	if(write(1, chal, nchal) != nchal)
		exits(0);

	tries = 5;
Retry:
	if(--tries < 0)
		exits(0);

	/*
	 *  get chap reply
	 */
	if(readn(0, &reply, OMSCHAPREPLYLEN) < 0)
		exits(0);

	safecpy(tr->uid, reply.uid, sizeof(tr->uid));
	if(tr->uid[0] == 0)
		exits(0);

	/*
	 * lookup
	 */
	secret = findsecret(KEYDB, tr->uid, sbuf);
	if(!getkey(tr->hostid, &hkey) || secret == nil){
		replyerror("mschap-fail bad response %s/%s(%s)", tr->uid, tr->hostid, raddr);
		goto Retry;
	}

	if(nchal == MSchallenv2){
		/* MSCHAPv2 */
		s = sha1((uchar*)reply.LMresp, nchal, nil, nil);
		s = sha1(chal, nchal, nil, s);
		sha1((uchar*)tr->uid, strlen(tr->uid), chash, s);

		nthash(hash, secret);
		mschalresp(resp, hash, chash);
		ntok = lmok = tsmemcmp(resp, reply.NTresp, MSresplen) == 0;
		dupe = 0;
	} else {
		/* MSCHAP (LM+NTLM) */
		lmhash(hash, secret);
		mschalresp(resp, hash, chal);
		lmok = tsmemcmp(resp, reply.LMresp, MSresplen) == 0;

		nthash(hash, secret);
		mschalresp(resp, hash, chal);
		ntok = tsmemcmp(resp, reply.NTresp, MSresplen) == 0;
		dupe = tsmemcmp(reply.LMresp, reply.NTresp, MSresplen) == 0;
	}

	/*
	 * It is valid to send the same response in both the LM and NTLM 
	 * fields provided one of them is correct, if neither matches,
	 * or the two fields are different and either fails to match, 
	 * the whole sha-bang fails.
	 *
	 * This is an improvement in security as it allows clients who
	 * wish to do NTLM auth (which is insecure) not to send
	 * LM tokens (which is very insecure).
	 *
	 * Windows servers supports clients doing this also though
	 * windows clients don't seem to use the feature.
	 */
	if((!ntok && !lmok) || ((!ntok || !lmok) && !dupe)){
		replyerror("mschap-fail bad response %s/%s(%s)", tr->uid, tr->hostid, raddr);
		logfail(tr->uid);
		goto Retry;
	}

	succeed(tr->uid);

	nthash(hash, secret);
	md4(hash, 16, hash, nil);

	/*
	 *  reply with ticket & authenticator
	 */
	if(nchal == MSchallenv2){
		s = sha1(hash, 16, nil, nil);
		sha1((uchar*)reply.NTresp, MSresplen, nil, s);
		sha1((uchar*)"This is the MPPE Master Key", 27, phash, s);

		s = sha1(hash, 16, nil, nil);
		sha1((uchar*)reply.NTresp, MSresplen, nil, s);
		sha1((uchar*)"Magic server to client signing constant", 39, ahash, s);

		s = sha1(ahash, 20, nil, nil);
		sha1(chash, 8, nil, s);
		sha1((uchar*)"Pad to make it do more than one iteration", 41, ahash, s);

		tickauthreply2(tr, &hkey, phash, 16, ahash, 20);
	} else {
		s = sha1(hash, 16, nil, nil);
		sha1(hash, 16, nil, s);
		sha1(chal, 8, phash, s);

		tickauthreply2(tr, &hkey, phash, 16, nil, 0);
	}

	syslog(0, AUTHLOG, "mschap-ok %s/%s(%s)", tr->uid, tr->hostid, raddr);

	exits(0);
}

void
nthash(uchar hash[MShashlen], char *passwd)
{
	DigestState *ds;
	uchar b[2];
	Rune r;

	ds = md4(nil, 0, nil, nil);
	while(*passwd){
		passwd += chartorune(&r, passwd);
		b[0] = r & 0xff;
		b[1] = r >> 8;
		md4(b, 2, nil, ds);
	}
	md4(nil, 0, hash, ds);
}

void
ntv2hash(uchar hash[MShashlen], char *passwd, char *user, char *dom)
{
	uchar v1hash[MShashlen];
	DigestState *ds;
	uchar b[2];
	Rune r;

	nthash(v1hash, passwd);

	/*
	 * Some documentation insists that the username must be forced to
	 * uppercase, but the domain name should not be. Other shows both
	 * being forced to uppercase. I am pretty sure this is irrevevant as the
	 * domain name passed from the remote server always seems to be in
	 * uppercase already.
	 */
        ds = hmac_md5(nil, 0, v1hash, sizeof(v1hash), nil, nil);
	while(*user){
		user += chartorune(&r, user);
		r = toupperrune(r);
		b[0] = r & 0xff;
		b[1] = r >> 8;
        	hmac_md5(b, 2, v1hash, sizeof(v1hash), nil, ds);
	}
	while(*dom){
		dom += chartorune(&r, dom);
		b[0] = r & 0xff;
		b[1] = r >> 8;
        	hmac_md5(b, 2, v1hash, sizeof(v1hash), nil, ds);
	}
        hmac_md5(nil, 0, v1hash, sizeof(v1hash), hash, ds);
}

void
lmhash(uchar hash[MShashlen], char *passwd)
{
	uchar buf[14];
	char *stdtext = "KGS!@#$%";
	int i;

	memset(buf, 0, sizeof(buf));
	strncpy((char*)buf, passwd, sizeof(buf));
	for(i=0; i<sizeof(buf); i++)
		if(buf[i] >= 'a' && buf[i] <= 'z')
			buf[i] += 'A' - 'a';

	memcpy(hash, stdtext, 8);
	memcpy(hash+8, stdtext, 8);

	desencrypt(hash, buf);
	desencrypt(hash+8, buf+7);
}

void
mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen])
{
	int i;
	uchar buf[21];

	memset(buf, 0, sizeof(buf));
	memcpy(buf, hash, MShashlen);

	for(i=0; i<3; i++) {
		memmove(resp+i*MSchallen, chal, MSchallen);
		desencrypt(resp+i*MSchallen, buf+i*7);
	}
}

void
desencrypt(uchar data[8], uchar key[7])
{
	ulong ekey[32];

	key_setup(key, ekey);
	block_cipher(ekey, data, 0);
}

/*
 *  return true of the speaker may speak for the user
 *
 *  a speaker may always speak for himself/herself
 */
int
speaksfor(char *speaker, char *user)
{
	Ndbtuple *tp, *ntp;
	Ndbs s;
	int ok;
	char notuser[Maxpath];

	if(strcmp(speaker, user) == 0)
		return 1;

	if(db == nil)
		return 0;

	tp = ndbsearch(db, &s, "hostid", speaker);
	if(tp == nil)
		return 0;

	ok = 0;
	snprint(notuser, sizeof notuser, "!%s", user);
	for(ntp = tp; ntp != nil; ntp = ntp->entry)
		if(strcmp(ntp->attr, "uid") == 0){
			if(strcmp(ntp->val, notuser) == 0){
				ok = 0;
				break;
			}
			if(*ntp->val == '*' || strcmp(ntp->val, user) == 0)
				ok = 1;
		}
	ndbfree(tp);
	return ok;
}

/*
 *  return an error reply
 */
void
replyerror(char *fmt, ...)
{
	char buf[AERRLEN+1];
	va_list arg;

	memset(buf, 0, sizeof(buf));
	va_start(arg, fmt);
	vseprint(buf + 1, buf + sizeof(buf), fmt, arg);
	va_end(arg);
	buf[AERRLEN] = 0;
	buf[0] = AuthErr;
	write(1, buf, AERRLEN+1);
	syslog(0, AUTHLOG, buf+1);
}

void
getraddr(char *dir)
{
	int n;
	char *cp;
	char file[Maxpath];

	raddr[0] = 0;
	snprint(file, sizeof(file), "%s/remote", dir);
	n = readfile(file, raddr, sizeof(raddr)-1);
	if(n < 0)
		return;
	raddr[n] = 0;

	cp = strchr(raddr, '\n');
	if(cp)
		*cp = 0;
	cp = strchr(raddr, '!');
	if(cp)
		*cp = 0;
}

void
initkeyseed(void)
{
	int fd;

	genrandom(keyseed, sizeof(keyseed));
	if((fd = open("/adm/keyseed", OREAD)) >= 0){
		werrstr("file truncated");
		if(read(fd, keyseed, sizeof(keyseed)) == sizeof(keyseed)){
			close(fd);
			return;
		}
		close(fd);
	}
	syslog(0, AUTHLOG, "initkeyseed: no keyseed: %r");
	if((fd = create("/adm/keyseed", OWRITE, 0600)) < 0){
		syslog(0, AUTHLOG, "initkeyseed: can't create: %r");
		return;
	}
	write(fd, keyseed, sizeof(keyseed));
	close(fd);
}

void
mkkey(char *id, Authkey *a)
{
	uchar h[SHA2_256dlen];

	genrandom((uchar*)a, sizeof(Authkey));

	/*
	 * the DES key has to be constant for a user in each response,
	 * so we make one up pseudo randomly from a keyseed and user name.
	 */
	hmac_sha2_256((uchar*)id, strlen(id), keyseed, sizeof(keyseed), h, nil);
	memmove(a->des, h, DESKEYLEN);
	memset(h, 0, sizeof(h));
}

void
mkticket(Ticketreq *tr, Ticket *t)
{
	memset(t, 0, sizeof(Ticket));
	memmove(t->chal, tr->chal, CHALLEN);
	safecpy(t->cuid, tr->uid, ANAMELEN);
	safecpy(t->suid, tr->uid, ANAMELEN);
	genrandom(t->key, NONCELEN);
	t->form = ticketform;
}

/*
 *  reply with ticket and authenticator
 */
/*
 *  reply with ticket and authenticator
 */
void
tickauthreply(Ticketreq *tr, Authkey *key)
{
	tickauthreply2(tr, key, nil, 0, nil, 0);
}

/*
 *  reply with ticket and authenticator with
 *  secret s[ns] and authenticator data a[na].
 */
void
tickauthreply2(Ticketreq *tr, Authkey *key, uchar *ps, int ns, uchar *pa, int na)
{
	Ticket t;
	Authenticator a;
	char buf[MAXTICKETLEN+MAXAUTHENTLEN+1];
	int n;

	mkticket(tr, &t);
	if(t.form != 0 && ns > 0){
		assert(ns <= NONCELEN);
		memmove(t.key, ps, ns);
	}
	t.num = AuthTs;
	n = 0;
	buf[n++] = AuthOK;
	n += convT2M(&t, buf+n, sizeof(buf)-n, key);
	memset(&a, 0, sizeof(a));
	memmove(a.chal, t.chal, CHALLEN);
	genrandom(a.rand, NONCELEN);
	if(t.form != 0 && na > 0){
		assert(na <= NONCELEN);
		memmove(a.rand, pa, na);
	}
	a.num = AuthAc;
	n += convA2M(&a, buf+n, sizeof(buf)-n, &t);
	if(write(1, buf, n) != n)
		exits(0);
}


void
safecpy(char *to, char *from, int len)
{
	strncpy(to, from, len);
	to[len-1] = 0;
}

void
catch(void*, char *msg)
{
	if(strstr(msg, "alarm") != nil)
		noted(NCONT);
	noted(NDFLT);
}