bug fixes; add secstore
diff --git a/src/cmd/factotum/fs.c b/src/cmd/factotum/fs.c
index 0e4f2fd..1d801b4 100644
--- a/src/cmd/factotum/fs.c
+++ b/src/cmd/factotum/fs.c
@@ -419,7 +419,7 @@
 static void
 fsdestroyfid(Fid *fid)
 {
-	if(fid->qid.path == Qrpc){
+	if(fid->qid.path == Qrpc && fid->aux){
 		convhangup(fid->aux);
 		convclose(fid->aux);
 	}
@@ -435,8 +435,6 @@
 
 	USED(v);
 
-	creq = chancreate(sizeof(Req*), 0);
-
 	while((r = recvp(creq)) != nil){
 		switch(r->ifcall.type){
 		default:
@@ -464,8 +462,6 @@
 	Fid *f;
 
 	USED(v);
-	cfid = chancreate(sizeof(Fid*), 0);
-	cfidr = chancreate(sizeof(Fid*), 0);
 
 	while((f = recvp(cfid)) != nil){
 		fsdestroyfid(f);
@@ -486,13 +482,6 @@
 static void
 fsattach(Req *r)
 {
-	static int first = 1;
-
-	if(first){
-		proccreate(fsproc, nil, STACK);
-		first = 0;
-	}
-
 	r->fid->qid = mkqid(QTDIR, Qroot);
 	r->ofcall.qid = r->fid->qid;
 	respond(r, nil);
@@ -511,6 +500,17 @@
 	recvp(cfidr);
 }
 
+void
+fsstart(Srv *s)
+{
+	USED(s);
+
+	creq = chancreate(sizeof(Req*), 0);
+	cfid = chancreate(sizeof(Fid*), 0);
+	cfidr = chancreate(sizeof(Fid*), 0);
+	proccreate(fsproc, nil, STACK);
+}
+
 Srv fs = {
 .attach=	fsattach,
 .walk1=	fswalk1,
@@ -520,5 +520,6 @@
 .stat=	fsstat,
 .flush=	fssend,
 .destroyfid=	fssendclunk,
+.start=	fsstart,
 };
 
diff --git a/src/cmd/factotum/main.c b/src/cmd/factotum/main.c
index 3e9f89f..a2647da 100644
--- a/src/cmd/factotum/main.c
+++ b/src/cmd/factotum/main.c
@@ -2,8 +2,9 @@
 #include "dat.h"
 
 int debug;
+int trysecstore = 1;
 char *factname = "factotum";
-char *service = nil;
+char *service = "factotum";
 char *owner;
 char *authaddr;
 void gflag(char*);
@@ -11,18 +12,20 @@
 void
 usage(void)
 {
-	fprint(2, "usage: factotum [-Dd] [-a authaddr] [-m mtpt]\n");
+	fprint(2, "usage: factotum [-Dd] [-a authaddr] [-m mtpt] [-s service]\n");
 	fprint(2, " or   factotum -g keypattern\n");
-	fprint(2, " or   factotum -g 'badkeyattr\nmsg\nkeypattern'");
-	exits("usage");
+	fprint(2, " or   factotum -g 'badkeyattr\\nmsg\\nkeypattern'\n");
+	threadexitsall("usage");
 }
 
 void
 threadmain(int argc, char *argv[])
 {
 	char *mtpt;
+	char err[ERRMAX];
 
-	mtpt = "/mnt";
+//	mtpt = "/mnt";
+	mtpt = nil;
 	owner = getuser();
 	quotefmtinstall();
 	fmtinstall('A', attrfmt);
@@ -31,7 +34,7 @@
 
 	if(argc == 3 && strcmp(argv[1], "-g") == 0){
 		gflag(argv[2]);
-		exits(nil);
+		threadexitsall(nil);
 	}
 
 	ARGBEGIN{
@@ -51,11 +54,24 @@
 	case 's':
 		service = EARGF(usage());
 		break;
+	case 'n':
+		trysecstore = 0;
+		break;
 	}ARGEND
 
 	if(argc != 0)
 		usage();
 
+	if(trysecstore && havesecstore()){
+		while(secstorefetch() < 0){
+			rerrstr(err, sizeof err);
+			if(strcmp(err, "cancel") == 0)
+				break;
+			fprint(2, "secstorefetch: %r\n");
+			fprint(2, "Enter an empty password to quit.\n");
+		}
+	}
+	
 	threadpostmountsrv(&fs, service, mtpt, MBEFORE);
 	threadexits(nil);
 }
@@ -150,14 +166,14 @@
 	if(nf == 1){	/* needkey or old badkey */
 		fprint(fd, "\n");
 		askuser(fd, s);
-		exits(nil);
+		threadexitsall(nil);
 	}
 	if(nf == 3){	/* new badkey */
 		fprint(fd, "\n");
 		fprint(fd, "!replace: %s\n", f[0]);
 		fprint(fd, "!because: %s\n", f[1]);
 		askuser(fd, f[2]);
-		exits(nil);
+		threadexitsall(nil);
 	}
 	usage();
 }
diff --git a/src/cmd/factotum/mkfile b/src/cmd/factotum/mkfile
index 3a30cd5..1d48e6a 100644
--- a/src/cmd/factotum/mkfile
+++ b/src/cmd/factotum/mkfile
@@ -22,6 +22,7 @@
 	rpc.$O\
 	util.$O\
 	xio.$O\
+	secstore.$O\
 
 HFILES=dat.h
 
diff --git a/src/cmd/factotum/p9any.c b/src/cmd/factotum/p9any.c
index 388ee38..59a660f 100644
--- a/src/cmd/factotum/p9any.c
+++ b/src/cmd/factotum/p9any.c
@@ -119,7 +119,7 @@
 static int
 p9anyclient(Conv *c)
 {
-	char *s, **f, *tok[20], ok[3], *q, *user, *dom;
+	char *s, **f, *tok[20], ok[3], *q, *user, *dom, *choice;
 	int i, n, ret, version;
 	Key *k;
 	Attr *attr;
@@ -222,12 +222,12 @@
 	c->state = "write choice";
 	
 	/* have a key: go for it */
-	s = estrappend(nil, "%q %q", f[i], q);
-	if(convwrite(c, s, strlen(s)+1) < 0){
-		free(s);
+	choice = estrappend(nil, "%q %q", f[i], q);
+	if(convwrite(c, choice, strlen(choice)+1) < 0){
+		free(choice);
 		goto out;
 	}
-	free(s);
+	free(choice);
 
 	if(version == 2){
 		c->state = "read ok";
diff --git a/src/cmd/factotum/plan9.c b/src/cmd/factotum/plan9.c
index 048c190..36082c3 100644
--- a/src/cmd/factotum/plan9.c
+++ b/src/cmd/factotum/plan9.c
@@ -15,7 +15,7 @@
 /*
  *  create a change uid capability 
  */
-static int caphashfd;
+static int caphashfd = -1;
 
 static char*
 mkcap(char *from, char *to)
@@ -84,17 +84,7 @@
 int
 _authdial(char *net, char *authdom)
 {
-	int vanilla;
-
-	vanilla = net==nil || strcmp(net, "/net")==0;
-
-	if(!vanilla || bindnetcs()>=0)
-		return authdial(net, authdom);
-
-	/* use the auth sever passed to us as an arg */
-	if(authaddr == nil)
-		return -1;
-	return dial(netmkaddr(authaddr, "tcp", "567"), 0, 0, 0);
+	return authdial(net, authdom);
 }
 
 Key*
@@ -116,74 +106,3 @@
 		werrstr("could not find plan 9 auth key dom %q", dom);
 	return k;
 }
-
-/*
- *  prompt for a string with a possible default response
- */
-char*
-readcons(char *prompt, char *def, int raw)
-{
-	int fdin, fdout, ctl, n;
-	char line[10];
-	char *s;
-
-	fdin = open("/dev/cons", OREAD);
-	if(fdin < 0)
-		fdin = 0;
-	fdout = open("/dev/cons", OWRITE);
-	if(fdout < 0)
-		fdout = 1;
-	if(def != nil)
-		fprint(fdout, "%s[%s]: ", prompt, def);
-	else
-		fprint(fdout, "%s: ", prompt);
-	if(raw){
-		ctl = open("/dev/consctl", OWRITE);
-		if(ctl >= 0)
-			write(ctl, "rawon", 5);
-	} else
-		ctl = -1;
-	s = estrdup("");
-	for(;;){
-		n = read(fdin, line, 1);
-		if(n == 0){
-		Error:
-			close(fdin);
-			close(fdout);
-			if(ctl >= 0)
-				close(ctl);
-			free(s);
-			return nil;
-		}
-		if(n < 0)
-			goto Error;
-		if(line[0] == 0x7f)
-			goto Error;
-		if(n == 0 || line[0] == '\n' || line[0] == '\r'){
-			if(raw){
-				write(ctl, "rawoff", 6);
-				write(fdout, "\n", 1);
-			}
-			close(ctl);
-			close(fdin);
-			close(fdout);
-			if(*s == 0 && def != nil)
-				s = estrappend(s, "%s", def);
-			return s;
-		}
-		if(line[0] == '\b'){
-			if(strlen(s) > 0)
-				s[strlen(s)-1] = 0;
-		} else if(line[0] == 0x15) {	/* ^U: line kill */
-			if(def != nil)
-				fprint(fdout, "\n%s[%s]: ", prompt, def);
-			else
-				fprint(fdout, "\n%s: ", prompt);
-			
-			s[0] = 0;
-		} else {
-			s = estrappend(s, "%c", line[0]);
-		}
-	}
-	return nil; /* not reached */
-}
diff --git a/src/cmd/factotum/secstore.c b/src/cmd/factotum/secstore.c
new file mode 100644
index 0000000..14c855b
--- /dev/null
+++ b/src/cmd/factotum/secstore.c
@@ -0,0 +1,648 @@
+/*
+ * Various files from /sys/src/cmd/auth/secstore, just enough
+ * to download a file at boot time.
+ */
+
+#include "std.h"
+#include "dat.h"
+#include <ip.h>
+
+enum{ CHK = 16};
+enum{ MAXFILESIZE = 10*1024*1024 };
+
+enum{// PW status bits
+	Enabled 	= (1<<0),
+	STA 		= (1<<1),	// extra SecurID step
+};
+
+static char testmess[] = "__secstore\tPAK\nC=%s\nm=0\n";
+char *secstore;
+
+int
+secdial(void)
+{
+	char *p;
+	int fd;
+
+	p = secstore;
+	if(p == nil)	  /* else use the authserver */
+		p = getenv("secstore");
+	if(p == nil)
+		p = getenv("auth");
+	if(p == nil)
+		p = "secstore";
+
+	fd = dial(netmkaddr(p, "net", "secstore"), 0, 0, 0);
+	if(fd < 0)
+		fprint(2, "secdial: %r\n");
+	return fd;
+}
+
+
+int
+havesecstore(void)
+{
+	int m, n, fd;
+	uchar buf[500];
+
+	n = snprint((char*)buf, sizeof buf, testmess, owner);
+	hnputs(buf, 0x8000+n-2);
+
+	fd = secdial();
+	if(fd < 0)
+		return 0;
+	if(write(fd, buf, n) != n || readn(fd, buf, 2) != 2){
+		close(fd);
+		return 0;
+	}
+	n = ((buf[0]&0x7f)<<8) + buf[1];
+	if(n+1 > sizeof buf){
+		werrstr("implausibly large count %d", n);
+		close(fd);
+		return 0;
+	}
+	m = readn(fd, buf, n);
+	close(fd);
+	if(m != n){
+		if(m >= 0)
+			werrstr("short read from secstore");
+		return 0;
+	}
+	buf[n] = 0;
+	if(strcmp((char*)buf, "!account expired") == 0){
+		werrstr("account expired");
+		return 0;
+	}
+	return strcmp((char*)buf, "!account exists") == 0;
+}
+
+// delimited, authenticated, encrypted connection
+enum{ Maxmsg=4096 };	// messages > Maxmsg bytes are truncated
+typedef struct SConn SConn;
+
+extern SConn* newSConn(int);	// arg is open file descriptor
+struct SConn{
+	void *chan;
+	int secretlen;
+	int (*secret)(SConn*, uchar*, int);// 
+	int (*read)(SConn*, uchar*, int); // <0 if error;  errmess in buffer
+	int (*write)(SConn*, uchar*, int);
+	void (*free)(SConn*);		// also closes file descriptor
+};
+// secret(s,b,dir) sets secret for digest, encrypt, using the secretlen
+//		bytes in b to form keys 	for the two directions;
+//	  set dir=0 in client, dir=1 in server
+
+// error convention: write !message in-band
+#define readstr secstore_readstr
+static void writerr(SConn*, char*);
+static int readstr(SConn*, char*);  // call with buf of size Maxmsg+1
+	// returns -1 upon error, with error message in buf
+
+typedef struct ConnState {
+	uchar secret[SHA1dlen];
+	ulong seqno;
+	RC4state rc4;
+} ConnState;
+
+typedef struct SS{
+	int fd;		// file descriptor for read/write of encrypted data
+	int alg;	// if nonzero, "alg sha rc4_128"
+	ConnState in, out;
+} SS;
+
+static int
+SC_secret(SConn *conn, uchar *sigma, int direction)
+{
+	SS *ss = (SS*)(conn->chan);
+	int nsigma = conn->secretlen;
+
+	if(direction != 0){
+		hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->out.secret, nil);
+		hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->in.secret, nil);
+	}else{
+		hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->out.secret, nil);
+		hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->in.secret, nil);
+	}
+	setupRC4state(&ss->in.rc4, ss->in.secret, 16); // restrict to 128 bits
+	setupRC4state(&ss->out.rc4, ss->out.secret, 16);
+	ss->alg = 1;
+	return 0;
+}
+
+static void
+hash(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen])
+{
+	DigestState sha;
+	uchar seq[4];
+
+	seq[0] = seqno>>24;
+	seq[1] = seqno>>16;
+	seq[2] = seqno>>8;
+	seq[3] = seqno;
+	memset(&sha, 0, sizeof sha);
+	sha1(secret, SHA1dlen, nil, &sha);
+	sha1(data, len, nil, &sha);
+	sha1(seq, 4, d, &sha);
+}
+
+static int
+verify(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen])
+{
+	DigestState sha;
+	uchar seq[4];
+	uchar digest[SHA1dlen];
+
+	seq[0] = seqno>>24;
+	seq[1] = seqno>>16;
+	seq[2] = seqno>>8;
+	seq[3] = seqno;
+	memset(&sha, 0, sizeof sha);
+	sha1(secret, SHA1dlen, nil, &sha);
+	sha1(data, len, nil, &sha);
+	sha1(seq, 4, digest, &sha);
+	return memcmp(d, digest, SHA1dlen);
+}
+
+static int
+SC_read(SConn *conn, uchar *buf, int n)
+{
+	SS *ss = (SS*)(conn->chan);
+	uchar count[2], digest[SHA1dlen];
+	int len, nr;
+
+	if(read(ss->fd, count, 2) != 2 || count[0]&0x80 == 0){
+		werrstr("!SC_read invalid count");
+		return -1;
+	}
+	len = (count[0]&0x7f)<<8 | count[1];	// SSL-style count; no pad
+	if(ss->alg){
+		len -= SHA1dlen;
+		if(len <= 0 || readn(ss->fd, digest, SHA1dlen) != SHA1dlen){
+			werrstr("!SC_read missing sha1");
+			return -1;
+		}
+		if(len > n || readn(ss->fd, buf, len) != len){
+			werrstr("!SC_read missing data");
+			return -1;
+		}
+		rc4(&ss->in.rc4, digest, SHA1dlen);
+		rc4(&ss->in.rc4, buf, len);
+		if(verify(ss->in.secret, buf, len, ss->in.seqno, digest) != 0){
+			werrstr("!SC_read integrity check failed");
+			return -1;
+		}
+	}else{
+		if(len <= 0 || len > n){
+			werrstr("!SC_read implausible record length");
+			return -1;
+		}
+		if( (nr = readn(ss->fd, buf, len)) != len){
+			werrstr("!SC_read expected %d bytes, but got %d", len, nr);
+			return -1;
+		}
+	}
+	ss->in.seqno++;
+	return len;
+}
+
+static int
+SC_write(SConn *conn, uchar *buf, int n)
+{
+	SS *ss = (SS*)(conn->chan);
+	uchar count[2], digest[SHA1dlen], enc[Maxmsg+1];
+	int len;
+
+	if(n <= 0 || n > Maxmsg+1){
+		werrstr("!SC_write invalid n %d", n);
+		return -1;
+	}
+	len = n;
+	if(ss->alg)
+		len += SHA1dlen;
+	count[0] = 0x80 | len>>8;
+	count[1] = len;
+	if(write(ss->fd, count, 2) != 2){
+		werrstr("!SC_write invalid count");
+		return -1;
+	}
+	if(ss->alg){
+		hash(ss->out.secret, buf, n, ss->out.seqno, digest);
+		rc4(&ss->out.rc4, digest, SHA1dlen);
+		memcpy(enc, buf, n);
+		rc4(&ss->out.rc4, enc, n);
+		if(write(ss->fd, digest, SHA1dlen) != SHA1dlen ||
+				write(ss->fd, enc, n) != n){
+			werrstr("!SC_write error on send");
+			return -1;
+		}
+	}else{
+		if(write(ss->fd, buf, n) != n){
+			werrstr("!SC_write error on send");
+			return -1;
+		}
+	}
+	ss->out.seqno++;
+	return n;
+}
+
+static void
+SC_free(SConn *conn)
+{
+	SS *ss = (SS*)(conn->chan);
+
+	close(ss->fd);
+	free(ss);
+	free(conn);
+}
+
+SConn*
+newSConn(int fd)
+{
+	SS *ss;
+	SConn *conn;
+
+	if(fd < 0)
+		return nil;
+	ss = (SS*)emalloc(sizeof(*ss));
+	conn = (SConn*)emalloc(sizeof(*conn));
+	ss->fd  = fd;
+	ss->alg = 0;
+	conn->chan = (void*)ss;
+	conn->secretlen = SHA1dlen;
+	conn->free = SC_free;
+	conn->secret = SC_secret;
+	conn->read = SC_read;
+	conn->write = SC_write;
+	return conn;
+}
+
+static void
+writerr(SConn *conn, char *s)
+{
+	char buf[Maxmsg];
+
+	snprint(buf, Maxmsg, "!%s", s);
+	conn->write(conn, (uchar*)buf, strlen(buf));
+}
+
+static int
+readstr(SConn *conn, char *s)
+{
+	int n;
+
+	n = conn->read(conn, (uchar*)s, Maxmsg);
+	if(n >= 0){
+		s[n] = 0;
+		if(s[0] == '!'){
+			memmove(s, s+1, n);
+			n = -1;
+		}
+	}else{
+		strcpy(s, "read error");
+	}
+	return n;
+}
+
+static int
+getfile(SConn *conn, uchar *key, int nkey)
+{
+	char *buf;
+	int nbuf, n, nr, len;
+	char s[Maxmsg+1], *gf, *p, *q;
+	uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw;
+	AESstate aes;
+	DigestState *sha;
+
+	gf = "factotum";
+	memset(&aes, 0, sizeof aes);
+
+	snprint(s, Maxmsg, "GET %s\n", gf);
+	conn->write(conn, (uchar*)s, strlen(s));
+
+	/* get file size */
+	s[0] = '\0';
+	if(readstr(conn, s) < 0){
+		werrstr("secstore: %r");
+		return -1;
+	}
+	if((len = atoi(s)) < 0){
+		werrstr("secstore: remote file %s does not exist", gf);
+		return -1;
+	}else if(len > MAXFILESIZE){//assert
+		werrstr("secstore: implausible file size %d for %s", len, gf);
+		return -1;
+	}
+
+	ibr = ibw = ib;
+	buf = nil;
+	nbuf = 0;
+	for(nr=0; nr < len;){
+		if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
+			werrstr("secstore: empty file chunk n=%d nr=%d len=%d: %r", n, nr, len);
+			return -1;
+		}
+		nr += n;
+		ibw += n;
+		if(!aes.setup){ /* first time, read 16 byte IV */
+			if(n < 16){
+				werrstr("secstore: no IV in file");
+				return -1;
+			}
+			sha = sha1((uchar*)"aescbc file", 11, nil, nil);
+			sha1(key, nkey, skey, sha);
+			setupAESstate(&aes, skey, AESbsize, ibr);
+			memset(skey, 0, sizeof skey);
+			ibr += AESbsize;
+			n -= AESbsize;
+		}
+		aesCBCdecrypt(ibw-n, n, &aes);
+		n = ibw-ibr-CHK;
+		if(n > 0){
+			buf = erealloc(buf, nbuf+n+1);
+			memmove(buf+nbuf, ibr, n);
+			nbuf += n;
+			ibr += n;
+		}
+		memmove(ib, ibr, ibw-ibr);
+		ibw = ib + (ibw-ibr);
+		ibr = ib;
+	}
+	n = ibw-ibr;
+	if((n != CHK) || (memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0)){
+		werrstr("secstore: decrypted file failed to authenticate!");
+		free(buf);
+		return -1;
+	}
+	if(nbuf == 0){
+		werrstr("secstore got empty file");
+		return -1;
+	}
+	buf[nbuf] = '\0';
+	p = buf;
+	n = 0;
+	while(p){
+		if(q = strchr(p, '\n'))
+			*q++ = '\0';
+		n++;
+		if(ctlwrite(p) < 0)
+			fprint(2, "secstore(%s) line %d: %r\n", gf, n);
+		p = q;
+	}
+	free(buf);
+	return 0;
+}
+
+static char VERSION[] = "secstore";
+
+typedef struct PAKparams{
+	mpint *q, *p, *r, *g;
+} PAKparams;
+
+static PAKparams *pak;
+
+// This group was generated by the seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E.
+static void
+initPAKparams(void)
+{
+	if(pak)
+		return;
+	pak = (PAKparams*)emalloc(sizeof(*pak));
+	pak->q = strtomp("E0F0EF284E10796C5A2A511E94748BA03C795C13", nil, 16, nil);
+	pak->p = strtomp("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBBD"
+		"B12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86"
+		"3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9"
+		"3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", nil, 16, nil);
+	pak->r = strtomp("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241CEF"
+		"2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E887"
+		"D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D21"
+		"C4656848614D888A4", nil, 16, nil);
+	pak->g = strtomp("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D2327173444"
+		"ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD41"
+		"0E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734E3E"
+		"2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", nil, 16, nil);
+}
+
+// H = (sha(ver,C,sha(passphrase)))^r mod p,
+// a hash function expensive to attack by brute force.
+static void
+longhash(char *ver, char *C, uchar *passwd, mpint *H)
+{
+	uchar *Cp;
+	int i, n, nver, nC;
+	uchar buf[140], key[1];
+
+	nver = strlen(ver);
+	nC = strlen(C);
+	n = nver + nC + SHA1dlen;
+	Cp = (uchar*)emalloc(n);
+	memmove(Cp, ver, nver);
+	memmove(Cp+nver, C, nC);
+	memmove(Cp+nver+nC, passwd, SHA1dlen);
+	for(i = 0; i < 7; i++){
+		key[0] = 'A'+i;
+		hmac_sha1(Cp, n, key, sizeof key, buf+i*SHA1dlen, nil);
+	}
+	memset(Cp, 0, n);
+	free(Cp);
+	betomp(buf, sizeof buf, H);
+	mpmod(H, pak->p, H);
+	mpexp(H, pak->r, pak->p, H);
+}
+
+// Hi = H^-1 mod p
+static char *
+PAK_Hi(char *C, char *passphrase, mpint *H, mpint *Hi)
+{
+	uchar passhash[SHA1dlen];
+
+	sha1((uchar *)passphrase, strlen(passphrase), passhash, nil);
+	initPAKparams();
+	longhash(VERSION, C, passhash, H);
+	mpinvert(H, pak->p, Hi);
+	return mptoa(Hi, 64, nil, 0);
+}
+
+// another, faster, hash function for each party to
+// confirm that the other has the right secrets.
+static void
+shorthash(char *mess, char *C, char *S, char *m, char *mu, char *sigma, char *Hi, uchar *digest)
+{
+	SHA1state *state;
+
+	state = sha1((uchar*)mess, strlen(mess), 0, 0);
+	state = sha1((uchar*)C, strlen(C), 0, state);
+	state = sha1((uchar*)S, strlen(S), 0, state);
+	state = sha1((uchar*)m, strlen(m), 0, state);
+	state = sha1((uchar*)mu, strlen(mu), 0, state);
+	state = sha1((uchar*)sigma, strlen(sigma), 0, state);
+	state = sha1((uchar*)Hi, strlen(Hi), 0, state);
+	state = sha1((uchar*)mess, strlen(mess), 0, state);
+	state = sha1((uchar*)C, strlen(C), 0, state);
+	state = sha1((uchar*)S, strlen(S), 0, state);
+	state = sha1((uchar*)m, strlen(m), 0, state);
+	state = sha1((uchar*)mu, strlen(mu), 0, state);
+	state = sha1((uchar*)sigma, strlen(sigma), 0, state);
+	sha1((uchar*)Hi, strlen(Hi), digest, state);
+}
+
+// On input, conn provides an open channel to the server;
+//	C is the name this client calls itself;
+//	pass is the user's passphrase
+// On output, session secret has been set in conn
+//	(unless return code is negative, which means failure).
+//    If pS is not nil, it is set to the (alloc'd) name the server calls itself.
+static int
+PAKclient(SConn *conn, char *C, char *pass, char **pS)
+{
+	char *mess, *mess2, *eol, *S, *hexmu, *ks, *hexm, *hexsigma = nil, *hexHi;
+	char kc[2*SHA1dlen+1];
+	uchar digest[SHA1dlen];
+	int rc = -1, n;
+	mpint *x, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0);
+	mpint *H = mpnew(0), *Hi = mpnew(0);
+
+	hexHi = PAK_Hi(C, pass, H, Hi);
+
+	// random 1<=x<=q-1; send C, m=g**x H
+	x = mprand(164, genrandom, nil);
+	mpmod(x, pak->q, x);
+	if(mpcmp(x, mpzero) == 0)
+		mpassign(mpone, x);
+	mpexp(pak->g, x, pak->p, m);
+	mpmul(m, H, m);
+	mpmod(m, pak->p, m);
+	hexm = mptoa(m, 64, nil, 0);
+	mess = (char*)emalloc(2*Maxmsg+2);
+	mess2 = mess+Maxmsg+1;
+	snprint(mess, Maxmsg, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm);
+	conn->write(conn, (uchar*)mess, strlen(mess));
+
+	// recv g**y, S, check hash1(g**xy)
+	if(readstr(conn, mess) < 0){
+		fprint(2, "error: %s\n", mess);
+		writerr(conn, "couldn't read g**y");
+		goto done;
+	}
+	eol = strchr(mess, '\n');
+	if(strncmp("mu=", mess, 3) != 0 || !eol || strncmp("\nk=", eol, 3) != 0){
+		writerr(conn, "verifier syntax error");
+		goto done;
+	}
+	hexmu = mess+3;
+	*eol = 0;
+	ks = eol+3;
+	eol = strchr(ks, '\n');
+	if(!eol || strncmp("\nS=", eol, 3) != 0){
+		writerr(conn, "verifier syntax error for secstore 1.0");
+		goto done;
+	}
+	*eol = 0;
+	S = eol+3;
+	eol = strchr(S, '\n');
+	if(!eol){
+		writerr(conn, "verifier syntax error for secstore 1.0");
+		goto done;
+	}
+	*eol = 0;
+	if(pS)
+		*pS = estrdup(S);
+	strtomp(hexmu, nil, 64, mu);
+	mpexp(mu, x, pak->p, sigma);
+	hexsigma = mptoa(sigma, 64, nil, 0);
+	shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+	enc64(kc, sizeof kc, digest, SHA1dlen);
+	if(strcmp(ks, kc) != 0){
+		writerr(conn, "verifier didn't match");
+		goto done;
+	}
+
+	// send hash2(g**xy)
+	shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+	enc64(kc, sizeof kc, digest, SHA1dlen);
+	snprint(mess2, Maxmsg, "k'=%s\n", kc);
+	conn->write(conn, (uchar*)mess2, strlen(mess2));
+
+	// set session key
+	shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+	memset(hexsigma, 0, strlen(hexsigma));
+	n = conn->secret(conn, digest, 0);
+	memset(digest, 0, SHA1dlen);
+	if(n < 0){//assert
+		writerr(conn, "can't set secret");
+		goto done;
+	}
+
+	rc = 0;
+done:
+	mpfree(x);
+	mpfree(sigma);
+	mpfree(mu);
+	mpfree(m);
+	mpfree(Hi);
+	mpfree(H);
+	free(hexsigma);
+	free(hexHi);
+	free(hexm);
+	free(mess);
+	return rc;
+}
+
+int
+secstorefetch(void)
+{
+	int rv = -1, fd;
+	char s[Maxmsg+1];
+	SConn *conn;
+	char *pass, *sta;
+
+	sta = nil;
+	conn = nil;
+	pass = readcons("secstore password", nil, 1);
+	if(pass==nil || strlen(pass)==0){
+		werrstr("cancel");
+		goto Out;
+	}
+	if((fd = secdial()) < 0)
+		goto Out;
+	if((conn = newSConn(fd)) == nil)
+		goto Out;
+	if(PAKclient(conn, owner, pass, nil) < 0){
+		werrstr("password mistyped?");
+		goto Out;
+	}
+	if(readstr(conn, s) < 0)
+		goto Out;
+	if(strcmp(s, "STA") == 0){
+		sta = readcons("STA PIN+SecureID", nil, 1);
+		if(sta==nil || strlen(sta)==0){
+			werrstr("cancel");
+			goto Out;
+		}
+		if(strlen(sta) >= sizeof s - 3){
+			werrstr("STA response too long");
+			goto Out;
+		}
+		strcpy(s+3, sta);
+		conn->write(conn, (uchar*)s, strlen(s));
+		readstr(conn, s);
+	}
+	if(strcmp(s, "OK") !=0){
+		werrstr("%s", s);
+		goto Out;
+	}
+	if(getfile(conn, (uchar*)pass, strlen(pass)) < 0)
+		goto Out;
+	conn->write(conn, (uchar*)"BYE", 3);
+	rv = 0;
+
+Out:
+	if(conn)
+		conn->free(conn);
+	if(pass)
+		free(pass);
+	if(sta)
+		free(sta);
+	return rv;
+}
+
diff --git a/src/cmd/factotum/util.c b/src/cmd/factotum/util.c
index 54b3351..accdddd 100644
--- a/src/cmd/factotum/util.c
+++ b/src/cmd/factotum/util.c
@@ -36,6 +36,7 @@
 estrappend(char *s, char *fmt, ...)
 {
 	char *t;
+	int l;
 	va_list arg;
 
 	va_start(arg, fmt);
@@ -43,8 +44,9 @@
 	if(t == nil)
 		sysfatal("out of memory");
 	va_end(arg);
-	s = erealloc(s, strlen(s)+strlen(t)+1);
-	strcat(s, t);
+	l = s ? strlen(s) : 0;
+	s = erealloc(s, l+strlen(t)+1);
+	strcpy(s+l, t);
 	free(t);
 	return s;
 }
diff --git a/src/cmd/factotum/xio.c b/src/cmd/factotum/xio.c
index 5089084..2e6b141 100644
--- a/src/cmd/factotum/xio.c
+++ b/src/cmd/factotum/xio.c
@@ -87,7 +87,7 @@
 	dom = va_arg(*arg, char*);
 	fd = _authdial(net, dom);
 	if(fd < 0)
-		fprint(2, "authdial: %r");
+		fprint(2, "authdial: %r\n");
 	return fd;
 }