Commit Diff


commit - a770daa795754cb600ad3fab2fdd2961147006c4
commit + 2277c5d7bbe1f9595fad512d8f790708473a9bf1
blob - /dev/null
blob + 6703d607b5eba0827fc287ae394f0b16b56fa139 (mode 644)
--- /dev/null
+++ include/9p.h
@@ -0,0 +1,244 @@
+#ifndef __9P_H__
+#define __9P_H__ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+#pragma src "/sys/src/lib9p"
+#pragma lib "lib9p.a"
+*/
+
+/*
+ * Maps from ulongs to void*s.
+ */
+typedef struct Intmap	Intmap;
+
+Intmap*	allocmap(void (*inc)(void*));
+void		freemap(Intmap*, void (*destroy)(void*));
+void*	lookupkey(Intmap*, ulong);
+void*	insertkey(Intmap*, ulong, void*);
+int		caninsertkey(Intmap*, ulong, void*);
+void*	deletekey(Intmap*, ulong);
+
+/*
+ * Fid and Request structures.
+ */
+typedef struct Fid		Fid;
+typedef struct Req		Req;
+typedef struct Fidpool	Fidpool;
+typedef struct Reqpool	Reqpool;
+typedef struct File		File;
+typedef struct Filelist	Filelist;
+typedef struct Tree		Tree;
+typedef struct Readdir	Readdir;
+typedef struct Srv Srv;
+
+struct Fid
+{
+	ulong	fid;
+	char		omode;	/* -1 = not open */
+	File*		file;
+	char*	uid;
+	Qid		qid;
+	void*	aux;
+
+/* below is implementation-specific; don't use */
+	Readdir*	rdir;
+	Ref		ref;
+	Fidpool*	pool;
+	vlong	diroffset;
+	long		dirindex;
+};
+
+struct Req
+{
+	ulong	tag;
+	void*	aux;
+	Fcall		ifcall;
+	Fcall		ofcall;
+	Dir		d;
+	Req*		oldreq;
+	Fid*		fid;
+	Fid*		afid;
+	Fid*		newfid;
+	Srv*		srv;
+
+/* below is implementation-specific; don't use */
+	QLock	lk;
+	Ref		ref;
+	Reqpool*	pool;
+	uchar*	buf;
+	uchar	type;
+	uchar	responded;
+	char*	error;
+	void*	rbuf;
+	Req**	flush;
+	int		nflush;
+};
+
+/*
+ * Pools to maintain Fid <-> fid and Req <-> tag maps.
+ */
+
+struct Fidpool {
+	Intmap	*map;
+	void		(*destroy)(Fid*);
+	Srv		*srv;
+};
+
+struct Reqpool {
+	Intmap	*map;
+	void		(*destroy)(Req*);
+	Srv		*srv;
+};
+
+Fidpool*	allocfidpool(void (*destroy)(Fid*));
+void		freefidpool(Fidpool*);
+Fid*		allocfid(Fidpool*, ulong);
+Fid*		lookupfid(Fidpool*, ulong);
+void		closefid(Fid*);
+Fid*		removefid(Fidpool*, ulong);
+
+Reqpool*	allocreqpool(void (*destroy)(Req*));
+void		freereqpool(Reqpool*);
+Req*		allocreq(Reqpool*, ulong);
+Req*		lookupreq(Reqpool*, ulong);
+void		closereq(Req*);
+Req*		removereq(Reqpool*, ulong);
+
+typedef	int	Dirgen(int, Dir*, void*);
+void		dirread9p(Req*, Dirgen*, void*);
+
+/*
+ * File trees.
+ */
+struct File {
+	Ref ref;
+	Dir dir;
+	File *parent;
+	void *aux;
+
+/* below is implementation-specific; don't use */
+	RWLock rwlock;
+	Filelist *filelist;
+	Tree *tree;
+	int nchild;
+	int allocd;
+};
+
+struct Tree {
+	File *root;
+	void	(*destroy)(File *file);
+
+/* below is implementation-specific; don't use */
+	Lock genlock;
+	ulong qidgen;
+	ulong dirqidgen;
+};
+
+Tree*	alloctree(char*, char*, ulong, void(*destroy)(File*));
+void		freetree(Tree*);
+File*		createfile(File*, char*, char*, ulong, void*);
+int		removefile(File*);
+void		closefile(File*);
+File*		walkfile(File*, char*);
+Readdir*	opendirfile(File*);
+long		readdirfile(Readdir*, uchar*, long);
+void		closedirfile(Readdir*);
+
+/*
+ * Kernel-style command parser
+ */
+typedef struct Cmdbuf Cmdbuf;
+typedef struct Cmdtab Cmdtab;
+Cmdbuf*		parsecmd(char *a, int n);
+void		respondcmderror(Req*, Cmdbuf*, char*, ...);
+Cmdtab*	lookupcmd(Cmdbuf*, Cmdtab*, int);
+/*
+#pragma varargck argpos respondcmderr 3
+*/
+struct Cmdbuf
+{
+	char	*buf;
+	char	**f;
+	int	nf;
+};
+
+struct Cmdtab
+{
+	int	index;	/* used by client to switch on result */
+	char	*cmd;	/* command name */
+	int	narg;	/* expected #args; 0 ==> variadic */
+};
+
+/*
+ * File service loop.
+ */
+struct Srv {
+	Tree*	tree;
+	void		(*destroyfid)(Fid*);
+	void		(*destroyreq)(Req*);
+	void		(*end)(Srv*);
+	void*	aux;
+
+	void		(*attach)(Req*);
+	void		(*auth)(Req*);
+	void		(*open)(Req*);
+	void		(*create)(Req*);
+	void		(*read)(Req*);
+	void		(*write)(Req*);
+	void		(*remove)(Req*);
+	void		(*flush)(Req*);
+	void		(*stat)(Req*);
+	void		(*wstat)(Req*);
+	void		(*walk)(Req*);
+	char*	(*clone)(Fid*, Fid*);
+	char*	(*walk1)(Fid*, char*, Qid*);
+
+	int		infd;
+	int		outfd;
+	int		nopipe;
+	int		srvfd;
+	int		leavefdsopen;	/* magic for acme win */
+
+/* below is implementation-specific; don't use */
+	Fidpool*	fpool;
+	Reqpool*	rpool;
+	uint		msize;
+
+	uchar*	rbuf;
+	QLock	rlock;
+	uchar*	wbuf;
+	QLock	wlock;
+};
+
+void		srv(Srv*);
+void		postmountsrv(Srv*, char*, char*, int);
+int 		postfd(char*, int);
+int		chatty9p;
+void		respond(Req*, char*);
+void		threadpostmountsrv(Srv*, char*, char*, int);
+
+/*
+ * Helper.  Assumes user is same as group.
+ */
+int		hasperm(File*, char*, int);
+
+void*	emalloc9p(ulong);
+void*	erealloc9p(void*, ulong);
+char*	estrdup9p(char*);
+
+enum {
+	OMASK = 3
+};
+
+void readstr(Req*, char*);
+void readbuf(Req*, void*, long);
+void	walkandclone(Req*, char*(*walk1)(Fid*,char*,void*), char*(*clone)(Fid*,Fid*,void*), void*);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
blob - /dev/null
blob + f0ecdeaf57888a440dde4a39d082563c2948dadd (mode 644)
--- /dev/null
+++ include/auth.h
@@ -0,0 +1,159 @@
+#ifndef __AUTH_H__
+#define __AUTH_H__ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+#pragma	src	"/sys/src/libauth"
+#pragma	lib	"libauth.a"
+*/
+
+/*
+ * Interface for typical callers.
+ */
+
+typedef struct	AuthInfo	AuthInfo;
+typedef struct	Chalstate	Chalstate;
+typedef struct	Chapreply	Chapreply;
+typedef struct	MSchapreply	MSchapreply;
+typedef struct	UserPasswd	UserPasswd;
+typedef struct	AuthRpc		AuthRpc;
+
+enum
+{
+	MAXCHLEN=	256,		/* max challenge length	*/
+	MAXNAMELEN=	256,		/* maximum name length */
+	MD5LEN=		16,
+
+	ARok = 0,			/* rpc return values */
+	ARdone,
+	ARerror,
+	ARneedkey,
+	ARbadkey,
+	ARwritenext,
+	ARtoosmall,
+	ARtoobig,
+	ARrpcfailure,
+	ARphase,
+
+	AuthRpcMax = 4096,
+};
+
+struct AuthRpc
+{
+	int afd;
+	char ibuf[AuthRpcMax];
+	char obuf[AuthRpcMax];
+	char *arg;
+	uint narg;
+};
+
+struct AuthInfo
+{
+	char	*cuid;		/* caller id */
+	char	*suid;		/* server id */
+	char	*cap;		/* capability (only valid on server side) */
+	int	nsecret;	/* length of secret */
+	uchar	*secret;	/* secret */
+};
+
+struct Chalstate
+{
+	char	*user;
+	char	chal[MAXCHLEN];
+	int	nchal;
+	void	*resp;
+	int	nresp;
+
+/* for implementation only */
+	int	afd;			/* to factotum */
+	AuthRpc	*rpc;			/* to factotum */
+	char	userbuf[MAXNAMELEN];	/* temp space if needed */
+	int	userinchal;		/* user was sent to obtain challenge */
+};
+
+struct	Chapreply		/* for protocol "chap" */
+{
+	uchar	id;
+	char	resp[MD5LEN];
+};
+
+struct	MSchapreply	/* for protocol "mschap" */
+{
+	char	LMresp[24];		/* Lan Manager response */
+	char	NTresp[24];		/* NT response */
+};
+
+struct	UserPasswd
+{
+	char	*user;
+	char	*passwd;
+};
+
+extern	int	newns(char*, char*);
+extern	int	addns(char*, char*);
+
+extern	int	noworld(char*);
+extern	int	amount(int, char*, int, char*);
+
+/* these two may get generalized away -rsc */
+extern	int	login(char*, char*, char*);
+extern	int	httpauth(char*, char*);
+
+typedef struct Attr Attr;
+enum {
+	AttrNameval,		/* name=val -- when matching, must have name=val */
+	AttrQuery,		/* name? -- when matching, must be present */
+	AttrDefault,		/* name:=val -- when matching, if present must match INTERNAL */
+};
+struct Attr
+{
+	int type;
+	Attr *next;
+	char *name;
+	char *val;
+};
+
+typedef int AuthGetkey(char*);
+
+int	_attrfmt(Fmt*);
+Attr	*_copyattr(Attr*);
+Attr	*_delattr(Attr*, char*);
+Attr	*_findattr(Attr*, char*);
+void	_freeattr(Attr*);
+Attr	*_mkattr(int, char*, char*, Attr*);
+Attr	*_parseattr(char*);
+char	*_strfindattr(Attr*, char*);
+/*
+#pragma varargck type "A" Attr*
+*/
+
+extern AuthInfo*	fauth_proxy(int, AuthRpc *rpc, AuthGetkey *getkey, char *params);
+extern AuthInfo*	auth_proxy(int fd, AuthGetkey *getkey, char *fmt, ...);
+extern int		auth_getkey(char*);
+extern int		(*amount_getkey)(char*);
+extern void		auth_freeAI(AuthInfo *ai);
+extern int		auth_chuid(AuthInfo *ai, char *ns);
+extern Chalstate	*auth_challenge(char*, ...);
+extern AuthInfo*	auth_response(Chalstate*);
+extern int		auth_respond(void*, uint, char*, uint, void*, uint, AuthGetkey *getkey, char*, ...);
+extern void		auth_freechal(Chalstate*);
+extern AuthInfo*	auth_userpasswd(char *user, char *passwd);
+extern UserPasswd*	auth_getuserpasswd(AuthGetkey *getkey, char*, ...);
+extern AuthInfo*	auth_getinfo(AuthRpc *rpc);
+extern AuthRpc*		auth_allocrpc(int afd);
+extern Attr*		auth_attr(AuthRpc *rpc);
+extern void		auth_freerpc(AuthRpc *rpc);
+extern uint		auth_rpc(AuthRpc *rpc, char *verb, void *a, int n);
+extern int		auth_wep(char*, char*, ...);
+/*
+#pragma varargck argpos auth_proxy 3
+#pragma varargck argpos auth_challenge 1
+#pragma varargck argpos auth_respond 3
+#pragma varargck argpos auth_getuserpasswd 2
+*/
+#ifdef __cplusplus
+}
+#endif
+#endif
blob - /dev/null
blob + 1149a212082856737ca55ffb17d99ecba8b4e8d8 (mode 644)
--- /dev/null
+++ include/authsrv.h
@@ -0,0 +1,177 @@
+#ifndef __AUTHSRV_H__
+#define __AUTHSRV_H__ 1
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+#pragma	src	"/sys/src/libauthsrv"
+#pragma	lib	"libauthsrv.a"
+*/
+
+/*
+ * Interface for talking to authentication server.
+ */
+typedef struct	Ticket		Ticket;
+typedef struct	Ticketreq	Ticketreq;
+typedef struct	Authenticator	Authenticator;
+typedef struct	Nvrsafe		Nvrsafe;
+typedef struct	Passwordreq	Passwordreq;
+typedef struct	OChapreply	OChapreply;
+typedef struct	OMSchapreply	OMSchapreply;
+
+enum
+{
+	ANAMELEN=	28,		/* maximum size of name in previous proto */
+	AERRLEN=	64,		/* maximum size of errstr in previous proto */
+	DOMLEN=		48,		/* length of an authentication domain name */
+	DESKEYLEN=	7,		/* length of a des key for encrypt/decrypt */
+	CHALLEN=	8,		/* length of a plan9 sk1 challenge */
+	NETCHLEN=	16,		/* max network challenge length (used in AS protocol) */
+	CONFIGLEN=	14,
+	SECRETLEN=	32,		/* max length of a secret */
+
+	KEYDBOFF=	8,		/* length of random data at the start of key file */
+	OKEYDBLEN=	ANAMELEN+DESKEYLEN+4+2,	/* length of an entry in old key file */
+	KEYDBLEN=	OKEYDBLEN+SECRETLEN,	/* length of an entry in key file */
+	OMD5LEN=	16,
+};
+
+/* encryption numberings (anti-replay) */
+enum
+{
+	AuthTreq=1,	/* ticket request */
+	AuthChal=2,	/* challenge box request */
+	AuthPass=3,	/* change password */
+	AuthOK=4,	/* fixed length reply follows */
+	AuthErr=5,	/* error follows */
+	AuthMod=6,	/* modify user */
+	AuthApop=7,	/* apop authentication for pop3 */
+	AuthOKvar=9,	/* variable length reply follows */
+	AuthChap=10,	/* chap authentication for ppp */
+	AuthMSchap=11,	/* MS chap authentication for ppp */
+	AuthCram=12,	/* CRAM verification for IMAP (RFC2195 & rfc2104) */
+	AuthHttp=13,	/* http domain login */
+	AuthVNC=14,	/* VNC server login (deprecated) */
+
+
+	AuthTs=64,	/* ticket encrypted with server's key */
+	AuthTc,		/* ticket encrypted with client's key */
+	AuthAs,		/* server generated authenticator */
+	AuthAc,		/* client generated authenticator */
+	AuthTp,		/* ticket encrypted with client's key for password change */
+	AuthHr,		/* http reply */
+};
+
+struct Ticketreq
+{
+	char	type;
+	char	authid[ANAMELEN];	/* server's encryption id */
+	char	authdom[DOMLEN];	/* server's authentication domain */
+	char	chal[CHALLEN];		/* challenge from server */
+	char	hostid[ANAMELEN];	/* host's encryption id */
+	char	uid[ANAMELEN];		/* uid of requesting user on host */
+};
+#define	TICKREQLEN	(3*ANAMELEN+CHALLEN+DOMLEN+1)
+
+struct Ticket
+{
+	char	num;			/* replay protection */
+	char	chal[CHALLEN];		/* server challenge */
+	char	cuid[ANAMELEN];		/* uid on client */
+	char	suid[ANAMELEN];		/* uid on server */
+	char	key[DESKEYLEN];		/* nonce DES key */
+};
+#define	TICKETLEN	(CHALLEN+2*ANAMELEN+DESKEYLEN+1)
+
+struct Authenticator
+{
+	char	num;			/* replay protection */
+	char	chal[CHALLEN];
+	ulong	id;			/* authenticator id, ++'d with each auth */
+};
+#define	AUTHENTLEN	(CHALLEN+4+1)
+
+struct Passwordreq
+{
+	char	num;
+	char	old[ANAMELEN];
+	char	new[ANAMELEN];
+	char	changesecret;
+	char	secret[SECRETLEN];	/* new secret */
+};
+#define	PASSREQLEN	(2*ANAMELEN+1+1+SECRETLEN)
+
+struct	OChapreply
+{
+	uchar	id;
+	char	uid[ANAMELEN];
+	char	resp[OMD5LEN];
+};
+
+struct	OMSchapreply
+{
+	char	uid[ANAMELEN];
+	char	LMresp[24];		/* Lan Manager response */
+	char	NTresp[24];		/* NT response */
+};
+
+/*
+ *  convert to/from wire format
+ */
+extern	int	convT2M(Ticket*, char*, char*);
+extern	void	convM2T(char*, Ticket*, char*);
+extern	void	convM2Tnoenc(char*, Ticket*);
+extern	int	convA2M(Authenticator*, char*, char*);
+extern	void	convM2A(char*, Authenticator*, char*);
+extern	int	convTR2M(Ticketreq*, char*);
+extern	void	convM2TR(char*, Ticketreq*);
+extern	int	convPR2M(Passwordreq*, char*, char*);
+extern	void	convM2PR(char*, Passwordreq*, char*);
+
+/*
+ *  convert ascii password to DES key
+ */
+extern	int	opasstokey(char*, char*);
+extern	int	passtokey(char*, char*);
+
+/*
+ *  Nvram interface
+ */
+enum {
+	NVwrite = 1<<0,		/* always prompt and rewrite nvram */
+	NVwriteonerr = 1<<1,	/* prompt and rewrite nvram when corrupt */
+};
+
+struct Nvrsafe
+{
+	char	machkey[DESKEYLEN];
+	uchar	machsum;
+	char	authkey[DESKEYLEN];
+	uchar	authsum;
+	char	config[CONFIGLEN];
+	uchar	configsum;
+	char	authid[ANAMELEN];
+	uchar	authidsum;
+	char	authdom[DOMLEN];
+	uchar	authdomsum;
+};
+
+extern	uchar	nvcsum(void*, int);
+extern int	readnvram(Nvrsafe*, int);
+
+/*
+ *  call up auth server
+ */
+extern	int	authdial(char *netroot, char *authdom);
+
+/*
+ *  exchange messages with auth server
+ */
+extern	int	_asgetticket(int, char*, char*);
+extern	int	_asrdresp(int, char*, int);
+extern	int	sslnegotiate(int, Ticket*, char**, char**);
+extern	int	srvsslnegotiate(int, Ticket*, char**, char**);
+#ifdef __cplusplus
+}
+#endif
+#endif
blob - /dev/null
blob + 876c0c7370f2ca57579346a9c9333cea5df810b9 (mode 644)
--- /dev/null
+++ include/mp.h
@@ -0,0 +1,153 @@
+#ifndef __MP_H__
+#define __MP_H__ 1
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+#pragma	src	"/sys/src/libmp"
+#pragma	lib	"libmp.a"
+*/
+
+#define _MPINT 1
+
+typedef long mpdigit;
+
+// the code assumes mpdigit to be at least an int
+// mpdigit must be an atomic type.  mpdigit is defined
+// in the architecture specific u.h
+
+typedef struct mpint mpint;
+
+struct mpint
+{
+	int	sign;	// +1 or -1
+	int	size;	// allocated digits
+	int	top;	// significant digits
+	mpdigit	*p;
+	char	flags;
+};
+
+enum
+{
+	MPstatic=	0x01,
+	Dbytes=		sizeof(mpdigit),	// bytes per digit
+	Dbits=		Dbytes*8		// bits per digit
+};
+
+// allocation
+void	mpsetminbits(int n);	// newly created mpint's get at least n bits
+mpint*	mpnew(int n);		// create a new mpint with at least n bits
+void	mpfree(mpint *b);
+void	mpbits(mpint *b, int n);	// ensure that b has at least n bits
+void	mpnorm(mpint *b);		// dump leading zeros
+mpint*	mpcopy(mpint *b);
+void	mpassign(mpint *old, mpint *new);
+
+// random bits
+mpint*	mprand(int bits, void (*gen)(uchar*, int), mpint *b);
+
+// conversion
+mpint*	strtomp(char*, char**, int, mpint*);	// ascii
+int	mpfmt(Fmt*);
+char*	mptoa(mpint*, int, char*, int);
+mpint*	letomp(uchar*, uint, mpint*);	// byte array, little-endian
+int	mptole(mpint*, uchar*, uint, uchar**);
+mpint*	betomp(uchar*, uint, mpint*);	// byte array, little-endian
+int	mptobe(mpint*, uchar*, uint, uchar**);
+uint	mptoui(mpint*);			// unsigned int
+mpint*	uitomp(uint, mpint*);
+int	mptoi(mpint*);			// int
+mpint*	itomp(int, mpint*);
+uvlong	mptouv(mpint*);			// unsigned vlong
+mpint*	uvtomp(uvlong, mpint*);
+vlong	mptov(mpint*);			// vlong
+mpint*	vtomp(vlong, mpint*);
+
+// divide 2 digits by one
+void	mpdigdiv(mpdigit *dividend, mpdigit divisor, mpdigit *quotient);
+
+// in the following, the result mpint may be
+// the same as one of the inputs.
+void	mpadd(mpint *b1, mpint *b2, mpint *sum);	// sum = b1+b2
+void	mpsub(mpint *b1, mpint *b2, mpint *diff);	// diff = b1-b2
+void	mpleft(mpint *b, int shift, mpint *res);	// res = b<<shift
+void	mpright(mpint *b, int shift, mpint *res);	// res = b>>shift
+void	mpmul(mpint *b1, mpint *b2, mpint *prod);	// prod = b1*b2
+void	mpexp(mpint *b, mpint *e, mpint *m, mpint *res);	// res = b**e mod m
+void	mpmod(mpint *b, mpint *m, mpint *remainder);	// remainder = b mod m
+
+// quotient = dividend/divisor, remainder = dividend % divisor
+void	mpdiv(mpint *dividend, mpint *divisor,  mpint *quotient, mpint *remainder);
+
+// return neg, 0, pos as b1-b2 is neg, 0, pos
+int	mpcmp(mpint *b1, mpint *b2);
+
+// extended gcd return d, x, and y, s.t. d = gcd(a,b) and ax+by = d
+void	mpextendedgcd(mpint *a, mpint *b, mpint *d, mpint *x, mpint *y);
+
+// res = b**-1 mod m
+void	mpinvert(mpint *b, mpint *m, mpint *res);
+
+// bit counting
+int	mpsignif(mpint*);	// number of sigificant bits in mantissa
+int	mplowbits0(mpint*);	// k, where n = 2**k * q for odd q
+
+// well known constants
+extern mpint	*mpzero, *mpone, *mptwo;
+
+// sum[0:alen] = a[0:alen-1] + b[0:blen-1]
+// prereq: alen >= blen, sum has room for alen+1 digits
+void	mpvecadd(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *sum);
+
+// diff[0:alen-1] = a[0:alen-1] - b[0:blen-1]
+// prereq: alen >= blen, diff has room for alen digits
+void	mpvecsub(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *diff);
+
+// p[0:n] += m * b[0:n-1]
+// prereq: p has room for n+1 digits
+void	mpvecdigmuladd(mpdigit *b, int n, mpdigit m, mpdigit *p);
+
+// p[0:n] -= m * b[0:n-1]
+// prereq: p has room for n+1 digits
+int	mpvecdigmulsub(mpdigit *b, int n, mpdigit m, mpdigit *p);
+
+// p[0:alen*blen-1] = a[0:alen-1] * b[0:blen-1]
+// prereq: alen >= blen, p has room for m*n digits
+void	mpvecmul(mpdigit *a, int alen, mpdigit *b, int blen, mpdigit *p);
+
+// sign of a - b or zero if the same
+int	mpveccmp(mpdigit *a, int alen, mpdigit *b, int blen);
+
+// divide the 2 digit dividend by the one digit divisor and stick in quotient
+// we assume that the result is one digit - overflow is all 1's
+void	mpdigdiv(mpdigit *dividend, mpdigit divisor, mpdigit *quotient);
+
+// playing with magnitudes
+int	mpmagcmp(mpint *b1, mpint *b2);
+void	mpmagadd(mpint *b1, mpint *b2, mpint *sum);	// sum = b1+b2
+void	mpmagsub(mpint *b1, mpint *b2, mpint *sum);	// sum = b1+b2
+
+// chinese remainder theorem
+typedef struct CRTpre	CRTpre;		// precomputed values for converting
+					//  twixt residues and mpint
+typedef struct CRTres	CRTres;		// residue form of an mpint
+
+struct CRTres
+{
+	int	n;		// number of residues
+	mpint	*r[1];		// residues
+};
+
+CRTpre*	crtpre(int, mpint**);			// precompute conversion values
+CRTres*	crtin(CRTpre*, mpint*);			// convert mpint to residues
+void	crtout(CRTpre*, CRTres*, mpint*);	// convert residues to mpint
+void	crtprefree(CRTpre*);
+void	crtresfree(CRTres*);
+
+
+/* #pragma	varargck	type	"B"	mpint* */
+#ifdef __cplusplus
+}
+#endif
+#endif
blob - /dev/null
blob + 0da51b518542503e23e5ecd7fe11b908982ad981 (mode 644)
--- /dev/null
+++ include/nfs3.h
@@ -0,0 +1,801 @@
+/* 
+ * NFS mounter V3;  see RFC 1813
+ */
+/*
+#pragma lib "libsunrpc.a"
+#pragma src "/sys/src/libsunrpc"
+*/
+#define _NFS3H_ /* sorry */
+
+enum {
+	NfsMount1HandleSize = 32,
+	NfsMount3MaxPathSize = 1024,
+	NfsMount3MaxNameSize = 255,
+	NfsMount3MaxHandleSize = 64,
+	NfsMount3Program = 100005,
+	NfsMount3Version = 3,
+	NfsMount1Program = 100005,
+	NfsMount1Version = 1
+};
+typedef struct NfsMount3TNull NfsMount3TNull;
+typedef struct NfsMount3RNull NfsMount3RNull;
+typedef struct NfsMount3TMnt NfsMount3TMnt;
+typedef struct NfsMount3RMnt NfsMount3RMnt;
+typedef struct NfsMount3TDump NfsMount3TDump;
+typedef struct NfsMount3Entry NfsMount3Entry;
+typedef struct NfsMount3RDump NfsMount3RDump;
+typedef struct NfsMount3TUmnt NfsMount3TUmnt;
+typedef struct NfsMount3RUmnt NfsMount3RUmnt;
+typedef struct NfsMount3Export NfsMount3Export;
+typedef struct NfsMount3TUmntall NfsMount3TUmntall;
+typedef struct NfsMount3RUmntall NfsMount3RUmntall;
+typedef struct NfsMount3TExport NfsMount3TExport;
+typedef struct NfsMount3RExport NfsMount3RExport;
+
+typedef enum
+{
+	NfsMount3CallTNull,
+	NfsMount3CallRNull,
+	NfsMount3CallTMnt,
+	NfsMount3CallRMnt,
+	NfsMount3CallTDump,
+	NfsMount3CallRDump,
+	NfsMount3CallTUmnt,
+	NfsMount3CallRUmnt,
+	NfsMount3CallTUmntall,
+	NfsMount3CallRUmntall,
+	NfsMount3CallTExport,
+	NfsMount3CallRExport
+} NfsMount3CallType;
+
+struct NfsMount3TNull {
+	SunCall call;
+};
+
+struct NfsMount3RNull {
+	SunCall call;
+};
+
+struct NfsMount3TMnt {
+	SunCall call;
+	char *path;
+};
+
+struct NfsMount3RMnt {
+	SunCall call;
+	uint status;
+	uchar *handle;
+	uint len;
+	u32int *auth;
+	u32int nauth;
+};
+
+struct NfsMount3TDump {
+	SunCall call;
+};
+
+struct NfsMount3Entry {
+	char *host;
+	char *path;
+};
+
+struct NfsMount3RDump {
+	SunCall call;
+	uchar *data;
+	u32int count;
+};
+
+struct NfsMount3TUmnt {
+	SunCall call;
+	char *path;
+};
+
+struct NfsMount3RUmnt {
+	SunCall call;
+};
+
+struct NfsMount3Export {
+	char *path;
+	char **g;
+	u32int ng;
+};
+
+struct NfsMount3TUmntall {
+	SunCall call;
+};
+
+struct NfsMount3RUmntall {
+	SunCall call;
+};
+
+struct NfsMount3TExport {
+	SunCall call;
+};
+
+struct NfsMount3RExport {
+	SunCall call;
+	uchar *data;
+	u32int count;
+};
+
+uint nfsmount3exportgroupsize(uchar*);
+uint nfsmount3exportsize(NfsMount3Export*);
+int nfsmount3exportpack(uchar*, uchar*, uchar**, NfsMount3Export*);
+int nfsmount3exportunpack(uchar*, uchar*, uchar**, char**, char***, NfsMount3Export*);
+int nfsmount3entrypack(uchar*, uchar*, uchar**, NfsMount3Entry*);
+int nfsmount3entryunpack(uchar*, uchar*, uchar**, NfsMount3Entry*);
+uint nfsmount3entrysize(NfsMount3Entry*);
+
+extern SunProg nfsmount3prog;
+
+/*
+ * NFS V3; see RFC 1813
+ */
+enum {
+	Nfs3MaxHandleSize = 64,
+	Nfs3CookieVerfSize = 8,
+	Nfs3CreateVerfSize = 8,
+	Nfs3WriteVerfSize = 8,
+	Nfs3AccessRead = 1,
+	Nfs3AccessLookup = 2,
+	Nfs3AccessModify = 4,
+	Nfs3AccessExtend = 8,
+	Nfs3AccessDelete = 16,
+	Nfs3AccessExecute = 32,
+	Nfs3FsHasLinks = 1,
+	Nfs3FsHasSymlinks = 2,
+	Nfs3FsHomogeneous = 8,
+	Nfs3FsCanSetTime = 16,
+
+	Nfs3Version = 3,	
+	Nfs3Program = 100003,
+};
+typedef enum
+{
+	Nfs3Ok = 0,
+	Nfs3ErrNotOwner = 1,
+	Nfs3ErrNoEnt = 2,
+	Nfs3ErrIo = 5,
+	Nfs3ErrNxio = 6,
+	Nfs3ErrNoMem = 12,
+	Nfs3ErrAcces = 13,
+	Nfs3ErrExist = 17,
+	Nfs3ErrXDev = 18,
+	Nfs3ErrNoDev = 19,
+	Nfs3ErrNotDir = 20,
+	Nfs3ErrIsDir = 21,
+	Nfs3ErrInval = 22,
+	Nfs3ErrFbig = 27,
+	Nfs3ErrNoSpc = 28,
+	Nfs3ErrRoFs = 30,
+	Nfs3ErrMLink = 31,
+	Nfs3ErrNameTooLong = 63,
+	Nfs3ErrNotEmpty = 66,
+	Nfs3ErrDQuot = 69,
+	Nfs3ErrStale = 70,
+	Nfs3ErrRemote = 71,
+	Nfs3ErrBadHandle = 10001,
+	Nfs3ErrNotSync = 10002,
+	Nfs3ErrBadCookie = 10003,
+	Nfs3ErrNotSupp = 10004,
+	Nfs3ErrTooSmall = 10005,
+	Nfs3ErrServerFault = 10006,
+	Nfs3ErrBadType = 10007,
+	Nfs3ErrJukebox = 10008,
+	Nfs3ErrFprintNotFound = 10009,
+	Nfs3ErrAborted = 10010,
+} Nfs3Status;
+
+void nfs3Errstr(Nfs3Status);
+
+typedef enum
+{
+	Nfs3FileReg = 1,
+	Nfs3FileDir = 2,
+	Nfs3FileBlock = 3,
+	Nfs3FileChar = 4,
+	Nfs3FileSymlink = 5,
+	Nfs3FileSocket = 6,
+	Nfs3FileFifo = 7,
+} Nfs3FileType;
+
+enum
+{
+	Nfs3ModeSetUid = 0x800,
+	Nfs3ModeSetGid = 0x400,
+	Nfs3ModeSticky = 0x200,
+};
+
+typedef enum
+{
+	Nfs3CallTNull,
+	Nfs3CallRNull,
+	Nfs3CallTGetattr,
+	Nfs3CallRGetattr,
+	Nfs3CallTSetattr,
+	Nfs3CallRSetattr,
+	Nfs3CallTLookup,
+	Nfs3CallRLookup,
+	Nfs3CallTAccess,
+	Nfs3CallRAccess,
+	Nfs3CallTReadlink,
+	Nfs3CallRReadlink,
+	Nfs3CallTRead,
+	Nfs3CallRRead,
+	Nfs3CallTWrite,
+	Nfs3CallRWrite,
+	Nfs3CallTCreate,
+	Nfs3CallRCreate,
+	Nfs3CallTMkdir,
+	Nfs3CallRMkdir,
+	Nfs3CallTSymlink,
+	Nfs3CallRSymlink,
+	Nfs3CallTMknod,
+	Nfs3CallRMknod,
+	Nfs3CallTRemove,
+	Nfs3CallRRemove,
+	Nfs3CallTRmdir,
+	Nfs3CallRRmdir,
+	Nfs3CallTRename,
+	Nfs3CallRRename,
+	Nfs3CallTLink,
+	Nfs3CallRLink,
+	Nfs3CallTReadDir,
+	Nfs3CallRReadDir,
+	Nfs3CallTReadDirPlus,
+	Nfs3CallRReadDirPlus,
+	Nfs3CallTFsStat,
+	Nfs3CallRFsStat,
+	Nfs3CallTFsInfo,
+	Nfs3CallRFsInfo,
+	Nfs3CallTPathconf,
+	Nfs3CallRPathconf,
+	Nfs3CallTCommit,
+	Nfs3CallRCommit,
+} Nfs3CallType;
+
+typedef struct Nfs3Handle Nfs3Handle;
+typedef struct Nfs3Time Nfs3Time;
+typedef struct Nfs3Attr Nfs3Attr;
+typedef struct Nfs3WccAttr Nfs3WccAttr;
+typedef struct Nfs3Wcc Nfs3Wcc;
+typedef enum
+{
+	Nfs3SetTimeDont = 0,
+	Nfs3SetTimeServer = 1,
+	Nfs3SetTimeClient = 2,
+} Nfs3SetTime;
+
+typedef struct Nfs3SetAttr Nfs3SetAttr;
+typedef struct Nfs3TNull Nfs3TNull;
+typedef struct Nfs3RNull Nfs3RNull;
+typedef struct Nfs3TGetattr Nfs3TGetattr;
+typedef struct Nfs3RGetattr Nfs3RGetattr;
+typedef struct Nfs3TSetattr Nfs3TSetattr;
+typedef struct Nfs3RSetattr Nfs3RSetattr;
+typedef struct Nfs3TLookup Nfs3TLookup;
+typedef struct Nfs3RLookup Nfs3RLookup;
+typedef struct Nfs3TAccess Nfs3TAccess;
+typedef struct Nfs3RAccess Nfs3RAccess;
+typedef struct Nfs3TReadlink Nfs3TReadlink;
+typedef struct Nfs3RReadlink Nfs3RReadlink;
+typedef struct Nfs3TRead Nfs3TRead;
+typedef struct Nfs3RRead Nfs3RRead;
+typedef enum
+{
+	Nfs3SyncNone = 0,
+	Nfs3SyncData = 1,
+	Nfs3SyncFile = 2,
+} Nfs3Sync;
+
+typedef struct Nfs3TWrite Nfs3TWrite;
+typedef struct Nfs3RWrite Nfs3RWrite;
+typedef enum
+{
+	Nfs3CreateUnchecked = 0,
+	Nfs3CreateGuarded = 1,
+	Nfs3CreateExclusive = 2,
+} Nfs3Create;
+
+typedef struct Nfs3TCreate Nfs3TCreate;
+typedef struct Nfs3RCreate Nfs3RCreate;
+typedef struct Nfs3TMkdir Nfs3TMkdir;
+typedef struct Nfs3RMkdir Nfs3RMkdir;
+typedef struct Nfs3TSymlink Nfs3TSymlink;
+typedef struct Nfs3RSymlink Nfs3RSymlink;
+typedef struct Nfs3TMknod Nfs3TMknod;
+typedef struct Nfs3RMknod Nfs3RMknod;
+typedef struct Nfs3TRemove Nfs3TRemove;
+typedef struct Nfs3RRemove Nfs3RRemove;
+typedef struct Nfs3TRmdir Nfs3TRmdir;
+typedef struct Nfs3RRmdir Nfs3RRmdir;
+typedef struct Nfs3TRename Nfs3TRename;
+typedef struct Nfs3RRename Nfs3RRename;
+typedef struct Nfs3TLink Nfs3TLink;
+typedef struct Nfs3RLink Nfs3RLink;
+typedef struct Nfs3TReadDir Nfs3TReadDir;
+typedef struct Nfs3Entry Nfs3Entry;
+typedef struct Nfs3RReadDir Nfs3RReadDir;
+typedef struct Nfs3TReadDirPlus Nfs3TReadDirPlus;
+typedef struct Nfs3EntryPlus Nfs3EntryPlus;
+typedef struct Nfs3RReadDirPlus Nfs3RReadDirPlus;
+typedef struct Nfs3TFsStat Nfs3TFsStat;
+typedef struct Nfs3RFsStat Nfs3RFsStat;
+typedef struct Nfs3TFsInfo Nfs3TFsInfo;
+typedef struct Nfs3RFsInfo Nfs3RFsInfo;
+typedef struct Nfs3TPathconf Nfs3TPathconf;
+typedef struct Nfs3RPathconf Nfs3RPathconf;
+typedef struct Nfs3TCommit Nfs3TCommit;
+typedef struct Nfs3RCommit Nfs3RCommit;
+
+struct Nfs3Handle {
+	uchar h[Nfs3MaxHandleSize];
+	u32int len;
+};
+
+struct Nfs3Time {
+	u32int sec;
+	u32int nsec;
+};
+
+struct Nfs3Attr {
+	Nfs3FileType type;
+	u32int mode;
+	u32int nlink;
+	u32int uid;
+	u32int gid;
+	u64int size;
+	u64int used;
+	u32int major;
+	u32int minor;
+	u64int fsid;
+	u64int fileid;
+	Nfs3Time atime;
+	Nfs3Time mtime;
+	Nfs3Time ctime;
+};
+
+struct Nfs3WccAttr {
+	u64int size;
+	Nfs3Time mtime;
+	Nfs3Time ctime;
+};
+
+struct Nfs3Wcc {
+	u1int haveWccAttr;
+	Nfs3WccAttr wccAttr;
+	u1int haveAttr;
+	Nfs3Attr attr;
+};
+
+struct Nfs3SetAttr {
+	u1int setMode;
+	u32int mode;
+	u1int setUid;
+	u32int uid;
+	u1int setGid;
+	u32int gid;
+	u1int setSize;
+	u64int size;
+	Nfs3SetTime setAtime;
+	Nfs3Time atime;
+	Nfs3SetTime setMtime;
+	Nfs3Time mtime;
+};
+
+struct Nfs3TNull {
+	SunCall call;
+};
+
+struct Nfs3RNull {
+	SunCall call;
+};
+
+struct Nfs3TGetattr {
+	SunCall call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RGetattr {
+	SunCall call;
+	Nfs3Status status;
+	Nfs3Attr attr;
+};
+
+struct Nfs3TSetattr {
+	SunCall call;
+	Nfs3Handle handle;
+	Nfs3SetAttr attr;
+	u1int checkCtime;
+	Nfs3Time ctime;
+};
+
+struct Nfs3RSetattr {
+	SunCall call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+};
+
+struct Nfs3TLookup {
+	SunCall call;
+	Nfs3Handle handle;
+	char *name;
+};
+
+struct Nfs3RLookup {
+	SunCall call;
+	Nfs3Status status;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u1int haveDirAttr;
+	Nfs3Attr dirAttr;
+};
+
+struct Nfs3TAccess {
+	SunCall call;
+	Nfs3Handle handle;
+	u32int access;
+};
+
+struct Nfs3RAccess {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u32int access;
+};
+
+struct Nfs3TReadlink {
+	SunCall call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RReadlink {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	char *data;
+};
+
+struct Nfs3TRead {
+	SunCall call;
+	Nfs3Handle handle;
+	u64int offset;
+	u32int count;
+};
+
+struct Nfs3RRead {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u32int count;
+	u1int eof;
+	uchar *data;
+	u32int ndata;
+};
+
+struct Nfs3TWrite {
+	SunCall call;
+	Nfs3Handle handle;
+	u64int offset;
+	u32int count;
+	Nfs3Sync stable;
+	uchar *data;
+	u32int ndata;
+};
+
+struct Nfs3RWrite {
+	SunCall call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+	u32int count;
+	Nfs3Sync committed;
+	uchar verf[Nfs3WriteVerfSize];
+};
+
+struct Nfs3TCreate {
+	SunCall call;
+	Nfs3Handle handle;
+	char *name;
+	Nfs3Create mode;
+	Nfs3SetAttr attr;
+	uchar verf[Nfs3CreateVerfSize];
+};
+
+struct Nfs3RCreate {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveHandle;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TMkdir {
+	SunCall call;
+	Nfs3Handle handle;
+	char *name;
+	Nfs3SetAttr attr;
+};
+
+struct Nfs3RMkdir {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveHandle;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TSymlink {
+	SunCall call;
+	Nfs3Handle handle;
+	char *name;
+	Nfs3SetAttr attr;
+	char *data;
+};
+
+struct Nfs3RSymlink {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveHandle;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TMknod {
+	SunCall call;
+	Nfs3Handle handle;
+	char *name;
+	Nfs3FileType type;
+	Nfs3SetAttr attr;
+	u32int major;
+	u32int minor;
+};
+
+struct Nfs3RMknod {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveHandle;
+	Nfs3Handle handle;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TRemove {
+	SunCall call;
+	Nfs3Handle handle;
+	char *name;
+};
+
+struct Nfs3RRemove {
+	SunCall call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+};
+
+struct Nfs3TRmdir {
+	SunCall call;
+	Nfs3Handle handle;
+	char *name;
+};
+
+struct Nfs3RRmdir {
+	SunCall call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+};
+
+struct Nfs3TRename {
+	SunCall call;
+	struct {
+		Nfs3Handle handle;
+		char *name;
+	} from;
+	struct {
+		Nfs3Handle handle;
+		char *name;
+	} to;
+};
+
+struct Nfs3RRename {
+	SunCall call;
+	Nfs3Status status;
+	Nfs3Wcc fromWcc;
+	Nfs3Wcc toWcc;
+};
+
+struct Nfs3TLink {
+	SunCall call;
+	Nfs3Handle handle;
+	struct {
+		Nfs3Handle handle;
+		char *name;
+	} link;
+};
+
+struct Nfs3RLink {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	Nfs3Wcc dirWcc;
+};
+
+struct Nfs3TReadDir {
+	SunCall call;
+	Nfs3Handle handle;
+	u64int cookie;
+	uchar verf[Nfs3CookieVerfSize];
+	u32int count;
+};
+
+struct Nfs3RReadDir {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	uchar verf[Nfs3CookieVerfSize];
+	uchar *data;
+	u32int count;
+	u1int eof;
+};
+
+struct Nfs3TReadDirPlus {
+	SunCall call;
+	Nfs3Handle handle;
+	u64int cookie;
+	uchar verf[Nfs3CookieVerfSize];
+	u32int dirCount;
+	u32int maxCount;
+};
+
+struct Nfs3Entry {
+	u64int fileid;
+	char *name;
+	u64int cookie;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u1int haveHandle;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RReadDirPlus {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	uchar verf[Nfs3CookieVerfSize];
+	uchar *data;
+	u32int count;
+	u1int eof;
+};
+
+struct Nfs3TFsStat {
+	SunCall call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RFsStat {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u64int totalBytes;
+	u64int freeBytes;
+	u64int availBytes;
+	u64int totalFiles;
+	u64int freeFiles;
+	u64int availFiles;
+	u32int invarSec;
+};
+
+struct Nfs3TFsInfo {
+	SunCall call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RFsInfo {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u32int readMax;
+	u32int readPref;
+	u32int readMult;
+	u32int writeMax;
+	u32int writePref;
+	u32int writeMult;
+	u32int readDirPref;
+	u64int maxFileSize;
+	Nfs3Time timePrec;
+	u32int flags;
+};
+
+struct Nfs3TPathconf {
+	SunCall call;
+	Nfs3Handle handle;
+};
+
+struct Nfs3RPathconf {
+	SunCall call;
+	Nfs3Status status;
+	u1int haveAttr;
+	Nfs3Attr attr;
+	u32int maxLink;
+	u32int maxName;
+	u1int noTrunc;
+	u1int chownRestricted;
+	u1int caseInsensitive;
+	u1int casePreserving;
+};
+
+struct Nfs3TCommit {
+	SunCall call;
+	Nfs3Handle handle;
+	u64int offset;
+	u32int count;
+};
+
+struct Nfs3RCommit {
+	SunCall call;
+	Nfs3Status status;
+	Nfs3Wcc wcc;
+	uchar verf[Nfs3WriteVerfSize];
+};
+
+char *nfs3statusstr(Nfs3Status);
+char *nfs3typestr(SunCallType);
+char *nfs3settimestr(Nfs3SetTime);
+char *nfs3syncstr(Nfs3Sync);
+
+void nfs3handleprint(Fmt*, Nfs3Handle*);
+u32int nfs3handlesize(Nfs3Handle*);
+int nfs3handlepack(uchar*, uchar*, uchar**, Nfs3Handle*);
+int nfs3handleunpack(uchar*, uchar*, uchar**, Nfs3Handle*);
+
+void nfs3timeprint(Fmt*, Nfs3Time*);
+u32int nfs3timesize(Nfs3Time*);
+int nfs3timepack(uchar*, uchar*, uchar**, Nfs3Time*);
+int nfs3timeunpack(uchar*, uchar*, uchar**, Nfs3Time*);
+
+void nfs3attrprint(Fmt*, Nfs3Attr*);
+u32int nfs3attrsize(Nfs3Attr*);
+int nfs3attrpack(uchar*, uchar*, uchar**, Nfs3Attr*);
+int nfs3attrunpack(uchar*, uchar*, uchar**, Nfs3Attr*);
+
+void nfs3wccattrprint(Fmt*, Nfs3WccAttr*);
+u32int nfs3wccattrsize(Nfs3WccAttr*);
+int nfs3wccattrpack(uchar*, uchar*, uchar**, Nfs3WccAttr*);
+int nfs3wccattrunpack(uchar*, uchar*, uchar**, Nfs3WccAttr*);
+
+void nfs3wccprint(Fmt*, Nfs3Wcc*);
+u32int nfs3wccsize(Nfs3Wcc*);
+int nfs3wccpack(uchar*, uchar*, uchar**, Nfs3Wcc*);
+int nfs3wccunpack(uchar*, uchar*, uchar**, Nfs3Wcc*);
+
+void nfs3setattrprint(Fmt*, Nfs3SetAttr*);
+u32int nfs3setattrsize(Nfs3SetAttr*);
+int nfs3setattrpack(uchar*, uchar*, uchar**, Nfs3SetAttr*);
+int nfs3setattrunpack(uchar*, uchar*, uchar**, Nfs3SetAttr*);
+
+extern SunProg nfs3prog;
+
+void nfs3entryprint(Fmt*, Nfs3Entry*);
+u32int nfs3entrysize(Nfs3Entry*);
+int nfs3entrypack(uchar*, uchar*, uchar**, Nfs3Entry*);
+int nfs3entryunpack(uchar*, uchar*, uchar**, Nfs3Entry*);
+
+void nfs3entryplusprint(Fmt*, Nfs3Entry*);
+u32int nfs3entryplussize(Nfs3Entry*);
+int nfs3entrypluspack(uchar*, uchar*, uchar**, Nfs3Entry*);
+int nfs3entryplusunpack(uchar*, uchar*, uchar**, Nfs3Entry*);
+
blob - /dev/null
blob + 4f108aa0bf3d2efd39f02f44c0145a06b42f4fcf (mode 644)
--- /dev/null
+++ include/sunrpc.h
@@ -0,0 +1,400 @@
+/*
+ * Sun RPC; see RFC 1057
+ */
+
+/*
+#pragma lib "libsunrpc.a"
+#pragma src "/sys/src/libsunrpc"
+*/
+
+typedef uchar u1int;
+
+typedef struct SunAuthInfo SunAuthInfo;
+typedef struct SunAuthUnix SunAuthUnix;
+typedef struct SunRpc SunRpc;
+typedef struct SunCall SunCall;
+
+enum
+{
+	/* Authinfo.flavor */
+	SunAuthNone = 0,
+	SunAuthSys,
+	SunAuthShort,
+	SunAuthDes,
+};
+
+typedef enum {
+	SunAcceptError = 0x10000,
+	SunRejectError = 0x20000,
+	SunAuthError = 0x40000,
+
+	/* Reply.status */
+	SunSuccess = 0,
+
+	SunProgUnavail = SunAcceptError | 1,
+	SunProgMismatch,
+	SunProcUnavail,
+	SunGarbageArgs,
+	SunSystemErr,
+
+	SunRpcMismatch = SunRejectError | 0,
+
+	SunAuthBadCred = SunAuthError | 1,
+	SunAuthRejectedCred,
+	SunAuthBadVerf,
+	SunAuthRejectedVerf,
+	SunAuthTooWeak,
+	SunAuthInvalidResp,
+	SunAuthFailed,
+} SunStatus;
+
+struct SunAuthInfo
+{
+	uint flavor;
+	uchar *data;
+	uint ndata;
+};
+
+struct SunAuthUnix
+{
+	u32int stamp;
+	char *sysname;
+	u32int uid;
+	u32int gid;
+	u32int g[16];
+	u32int ng;
+};
+
+struct SunRpc
+{
+	u32int xid;
+	uint iscall;
+
+	/*
+	 * only sent on wire in call
+	 * caller fills in for the reply unpackers.
+	 */
+	u32int proc;
+
+	/* call */
+	// uint proc;
+	u32int prog, vers;
+	SunAuthInfo cred;
+	SunAuthInfo verf;
+	uchar *data;
+	uint ndata;
+
+	/* reply */
+	u32int status;
+	// SunAuthInfo verf;
+	u32int low, high;
+	// uchar *data;
+	// uint ndata;
+};
+
+typedef enum
+{
+	SunCallTypeTNull,
+	SunCallTypeRNull,
+} SunCallType;
+
+struct SunCall
+{
+	SunRpc rpc;
+	SunCallType type;
+};
+
+void sunerrstr(SunStatus);
+
+void sunrpcprint(Fmt*, SunRpc*);
+uint sunrpcsize(SunRpc*);
+SunStatus sunrpcpack(uchar*, uchar*, uchar**, SunRpc*);
+SunStatus sunrpcunpack(uchar*, uchar*, uchar**, SunRpc*);
+
+void sunauthinfoprint(Fmt*, SunAuthInfo*);
+uint sunauthinfosize(SunAuthInfo*);
+int sunauthinfopack(uchar*, uchar*, uchar**, SunAuthInfo*);
+int sunauthinfounpack(uchar*, uchar*, uchar**, SunAuthInfo*);
+
+void sunauthunixprint(Fmt*, SunAuthUnix*);
+uint sunauthunixsize(SunAuthUnix*);
+int sunauthunixpack(uchar*, uchar*, uchar**, SunAuthUnix*);
+int sunauthunixunpack(uchar*, uchar*, uchar**, SunAuthUnix*);
+
+int sunenumpack(uchar*, uchar*, uchar**, int*);
+int sunenumunpack(uchar*, uchar*, uchar**, int*);
+int sunuint1pack(uchar*, uchar*, uchar**, u1int*);
+int sunuint1unpack(uchar*, uchar*, uchar**, u1int*);
+
+int sunstringpack(uchar*, uchar*, uchar**, char**, u32int);
+int sunstringunpack(uchar*, uchar*, uchar**, char**, u32int);
+uint sunstringsize(char*);
+
+int sunuint32pack(uchar*, uchar*, uchar**, u32int*);
+int sunuint32unpack(uchar*, uchar*, uchar**, u32int*);
+int sunuint64pack(uchar*, uchar*, uchar**, u64int*);
+int sunuint64unpack(uchar*, uchar*, uchar**, u64int*);
+
+int sunvaropaquepack(uchar*, uchar*, uchar**, uchar**, u32int*, u32int);
+int sunvaropaqueunpack(uchar*, uchar*, uchar**, uchar**, u32int*, u32int);
+uint sunvaropaquesize(u32int);
+
+int sunfixedopaquepack(uchar*, uchar*, uchar**, uchar*, u32int);
+int sunfixedopaqueunpack(uchar*, uchar*, uchar**, uchar*, u32int);
+uint sunfixedopaquesize(u32int);
+
+/*
+ * Sun RPC Program
+ */
+typedef struct SunProc SunProc;
+typedef struct SunProg SunProg;
+struct SunProg
+{
+	uint prog;
+	uint vers;
+	SunProc *proc;
+	int nproc;
+};
+
+struct SunProc
+{
+	int (*pack)(uchar*, uchar*, uchar**, SunCall*);
+	int (*unpack)(uchar*, uchar*, uchar**, SunCall*);
+	uint (*size)(SunCall*);
+	void (*fmt)(Fmt*, SunCall*);
+	uint sizeoftype;
+};
+
+SunStatus suncallpack(SunProg*, uchar*, uchar*, uchar**, SunCall*);
+SunStatus suncallunpack(SunProg*, uchar*, uchar*, uchar**, SunCall*);
+SunStatus suncallunpackalloc(SunProg*, SunCallType, uchar*, uchar*, uchar**, SunCall**);
+void suncallsetup(SunCall*, SunProg*, uint);
+uint suncallsize(SunProg*, SunCall*);
+
+/*
+ * Formatting
+#pragma varargck type "B" SunRpc*
+#pragma varargck type "C" SunCall*
+ */
+
+int	sunrpcfmt(Fmt*);
+int	suncallfmt(Fmt*);
+void	sunfmtinstall(SunProg*);
+
+
+/*
+ * Sun RPC Server
+ */
+typedef struct SunMsg SunMsg;
+typedef struct SunSrv SunSrv;
+
+enum
+{
+	SunStackSize = 32768,
+};
+
+struct SunMsg
+{
+	uchar *data;
+	int count;
+	SunSrv *srv;
+	SunRpc rpc;
+	SunProg *pg;
+	SunCall *call;
+	Channel *creply;	/* chan(SunMsg*) */
+};
+
+struct SunSrv
+{
+	int chatty;
+	int cachereplies;
+	int alwaysreject;
+	int localonly;
+	int localparanoia;
+	SunProg **map;
+	Channel *crequest;
+
+/* implementation use only */
+	Channel **cdispatch;
+	SunProg **prog;
+	int nprog;
+	void *cache;
+	Channel *creply;
+	Channel *cthread;
+};
+
+SunSrv *sunsrv(void);
+
+void	sunsrvprog(SunSrv *srv, SunProg *prog, Channel *c);
+int	sunsrvannounce(SunSrv *srv, char *address);
+int	sunsrvudp(SunSrv *srv, char *address);
+int	sunsrvnet(SunSrv *srv, char *address);
+int	sunsrvfd(SunSrv *srv, int fd);
+void	sunsrvthreadcreate(SunSrv *srv, void (*fn)(void*), void*);
+void	sunsrvclose(SunSrv*);
+
+int	sunmsgreply(SunMsg*, SunCall*);
+int	sunmsgdrop(SunMsg*);
+int	sunmsgreplyerror(SunMsg*, SunStatus);
+
+/*
+ * Sun RPC Client
+ */
+typedef struct SunClient SunClient;
+
+struct SunClient
+{
+	int		fd;
+	int		chatty;
+	int		needcount;
+	ulong	maxwait;
+	ulong	xidgen;
+	int		nsend;
+	int		nresend;
+	struct {
+		ulong min;
+		ulong max;
+		ulong avg;
+	} rtt;
+	Channel	*dying;
+	Channel	*rpcchan;
+	Channel	*timerchan;
+	Channel	*flushchan;
+	Channel	*readchan;
+	SunProg	**prog;
+	int		nprog;
+	int 		timertid;
+	int 		nettid;
+};
+
+SunClient	*sundial(char*);
+
+int	sunclientrpc(SunClient*, ulong, SunCall*, SunCall*, uchar**);
+void	sunclientclose(SunClient*);
+void	sunclientflushrpc(SunClient*, ulong);
+void	sunclientprog(SunClient*, SunProg*);
+
+
+/*
+ * Provided by callers.
+ * Should remove dependence on this, but hard.
+ */
+void	*emalloc(ulong);
+void *erealloc(void*, ulong);
+
+
+/*
+ * Sun RPC port mapper; see RFC 1057 Appendix A
+ */
+
+typedef struct PortMap PortMap;
+typedef struct PortTNull PortTNull;
+typedef struct PortRNull PortRNull;
+typedef struct PortTSet PortTSet;
+typedef struct PortRSet PortRSet;
+typedef struct PortTUnset PortTUnset;
+typedef struct PortRUnset PortRUnset;
+typedef struct PortTGetport PortTGetport;
+typedef struct PortRGetport PortRGetport;
+typedef struct PortTDump PortTDump;
+typedef struct PortRDump PortRDump;
+typedef struct PortTCallit PortTCallit;
+typedef struct PortRCallit PortRCallit;
+
+typedef enum
+{
+	PortCallTNull,
+	PortCallRNull,
+	PortCallTSet,
+	PortCallRSet,
+	PortCallTUnset,
+	PortCallRUnset,
+	PortCallTGetport,
+	PortCallRGetport,
+	PortCallTDump,
+	PortCallRDump,
+	PortCallTCallit,
+	PortCallRCallit,
+} PortCallType;
+
+enum
+{
+	PortProgram	= 100000,
+	PortVersion	= 2,
+
+	PortProtoTcp	= 6,	/* protocol number for TCP/IP */
+	PortProtoUdp	= 17	/* protocol number for UDP/IP */
+};
+
+struct PortMap {
+	u32int prog;
+	u32int vers;
+	u32int prot;
+	u32int port;
+};
+
+struct PortTNull {
+	SunCall call;
+};
+
+struct PortRNull {
+	SunCall call;
+};
+
+struct PortTSet {
+	SunCall call;
+	PortMap map;
+};
+
+struct PortRSet {
+	SunCall call;
+	u1int b;
+};
+
+struct PortTUnset {
+	SunCall call;
+	PortMap map;
+};
+
+struct PortRUnset {
+	SunCall call;
+	u1int b;
+};
+
+struct PortTGetport {
+	SunCall call;
+	PortMap map;
+};
+
+struct PortRGetport {
+	SunCall call;
+	u32int port;
+};
+
+struct PortTDump {
+	SunCall call;
+};
+
+struct PortRDump {
+	SunCall call;
+	PortMap *map;
+	int nmap;
+};
+
+struct PortTCallit {
+	SunCall call;
+	u32int prog;
+	u32int vers;
+	u32int proc;
+	uchar *data;
+	u32int count;
+};
+
+struct PortRCallit {
+	SunCall call;
+	u32int port;
+	uchar *data;
+	u32int count;
+};
+
+extern SunProg portprog;
blob - fbfb9f14fdc59adc724d81f01441547013723fba
blob + d4b4cad83e152a40020d96e612c506e031dab5bf
--- src/cmd/acme/acme.c
+++ src/cmd/acme/acme.c
@@ -27,8 +27,6 @@ char		wdir[512] = ".";
 Reffont	*reffonts[2];
 int		snarffd = -1;
 int		mainpid;
-int		plumbsendfd;
-int		plumbeditfd;
 
 enum{
 	NSnarf = 1000	/* less than 1024, I/O buffer size */
@@ -180,6 +178,8 @@ threadmain(int argc, char *argv[])
 		exits("keyboard");
 	}
 	mainpid = getpid();
+	startplumbing();
+/*
 	plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
 	if(plumbeditfd < 0)
 		fprint(2, "acme: can't initialize plumber: %r\n");
@@ -188,6 +188,7 @@ threadmain(int argc, char *argv[])
 		threadcreate(plumbproc, nil, STACK);
 	}
 	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+*/
 
 	fsysinit();
 
@@ -355,6 +356,7 @@ acmeerrorinit(void)
 	threadcreate(acmeerrorproc, nil, STACK);
 }
 
+/*
 void
 plumbproc(void *v)
 {
@@ -369,6 +371,7 @@ plumbproc(void *v)
 		sendp(cplumb, m);
 	}
 }
+*/
 
 void
 keyboardthread(void *v)
@@ -674,7 +677,7 @@ waitthread(void *v)
 					textsetselect(t, 0, 0);
 				}
 				if(w->msg[0])
-					warning(c->md, "%s: %s\n", c->name, w->msg);
+					warning(c->md, "%S: %s\n", c->name, w->msg);
 				flushimage(display, 1);
 			}
 			qunlock(&row.lk);
blob - 9101ca34fe8f40218f4a43fb9f64bcd18aa6ec1f
blob + 94cfa38314bd8e724ad7924f39c662e171407604
--- src/cmd/acme/dat.h
+++ src/cmd/acme/dat.h
@@ -524,8 +524,6 @@ char			*home;
 char			*fontnames[2];
 Image		*tagcols[NCOL];
 Image		*textcols[NCOL];
-int			plumbsendfd;
-int			plumbeditfd;
 extern char		wdir[]; /* must use extern because no dimension given */
 int			editing;
 int			erroutfd;
blob - e86af6ec1de57bb8821fbfc96c1be9c4d10d961b
blob + d7c9a9b077c0a0a1890ec6dbcaaf4cf04b0651b5
--- src/cmd/acme/elog.c
+++ src/cmd/acme/elog.c
@@ -170,7 +170,7 @@ eloginsert(File *f, int q0, Rune *r, int nr)
 		elogflush(f);
 	}
 	/* try to merge with previous */
-	if(f->elog.type==Insert && q0==f->elog.q0 && (q0+nr)-f->elog.q0<Maxstring){
+	if(f->elog.type==Insert && q0==f->elog.q0 && f->elog.nr+nr<Maxstring){
 		runemove(f->elog.r+f->elog.nr, r, nr);
 		f->elog.nr += nr;
 		return;
blob - d81153ecf67a4243f59468f3bd538022c8237807
blob + 74f9f47ced2be1ef35faca6300f8ec6837e8276f
--- src/cmd/acme/exec.c
+++ src/cmd/acme/exec.c
@@ -1472,6 +1472,7 @@ Hard:
 		}
 	}
 	threadexecl(cpid, sfd, "rc", "rc", "-c", t, nil);
+	warning(nil, "exec rc: %r\n");
 
    Fail:
 	/* threadexec hasn't happened, so send a zero */
blob - a73a7ec05ab83afc4535e81cfc087bb78441df54
blob + 9fba7d7ad79ec07cd4e8ee6de84a0b62b65b0626
--- src/cmd/acme/fns.h
+++ src/cmd/acme/fns.h
@@ -87,6 +87,7 @@ Rune*	skipbl(Rune*, int, int*);
 Rune*	findbl(Rune*, int, int*);
 char*	edittext(Window*, int, Rune*, int);
 void		flushwarnings(int);
+void		startplumbing(void);
 
 #define	runemalloc(a)		(Rune*)emalloc((a)*sizeof(Rune))
 #define	runerealloc(a, b)	(Rune*)erealloc((a), (b)*sizeof(Rune))
blob - 6b2592883429f687f6eaaca88721ddc53cb15da2
blob + f6c4d4ee5c8abff98f844c983e6dd1aae1463954
--- src/cmd/acme/look.c
+++ src/cmd/acme/look.c
@@ -8,15 +8,50 @@
 #include <frame.h>
 #include <fcall.h>
 #include <regexp.h>
+#define Fid FsFid
+#include <fs.h>
 #include <plumb.h>
+#undef Fid
 #include "dat.h"
 #include "fns.h"
 
+FsFid *plumbsendfid;
+FsFid *plumbeditfid;
+
 Window*	openfile(Text*, Expand*);
 
 int	nuntitled;
 
 void
+plumbproc(void *v)
+{
+	Plumbmsg *m;
+
+	USED(v);
+	threadsetname("plumbproc");
+	for(;;){
+		m = plumbrecvfid(plumbeditfid);
+		if(m == nil)
+			threadexits(nil);
+		sendp(cplumb, m);
+	}
+}
+
+void
+startplumbing(void)
+{
+	plumbeditfid = plumbopenfid("edit", OREAD|OCEXEC);
+	if(plumbeditfid == nil)
+		fprint(2, "acme: can't initialize plumber: %r\n");
+	else{
+		cplumb = chancreate(sizeof(Plumbmsg*), 0);
+		threadcreate(plumbproc, nil, STACK);
+	}
+	plumbsendfid = plumbopenfid("send", OWRITE|OCEXEC);
+}
+
+
+void
 look3(Text *t, uint q0, uint q1, int external)
 {
 	int n, c, f, expanded;
@@ -79,7 +114,7 @@ look3(Text *t, uint q0, uint q1, int external)
 		free(r);
 		goto Return;
 	}
-	if(plumbsendfd >= 0){
+	if(plumbsendfid != nil){
 		/* send whitespace-delimited word to plumber */
 		m = emalloc(sizeof(Plumbmsg));
 		m->src = estrdup("acme");
@@ -121,7 +156,7 @@ look3(Text *t, uint q0, uint q1, int external)
 		m->data = runetobyte(r, q1-q0);
 		m->ndata = strlen(m->data);
 		free(r);
-		if(m->ndata<messagesize-1024 && plumbsend(plumbsendfd, m) >= 0){
+		if(m->ndata<messagesize-1024 && plumbsendtofid(plumbsendfid, m) >= 0){
 			plumbfree(m);
 			goto Return;
 		}
blob - /dev/null
blob + 7aed917f52a27e4f384673158244d293cfba9f4d (mode 644)
--- /dev/null
+++ src/cmd/factotum/BUGS
@@ -0,0 +1 @@
+key, delkey, wipe should be in ctl not rpc.
blob - /dev/null
blob + f555c39484e63dff1bf88b6119d649d7544763b6 (mode 644)
--- /dev/null
+++ src/cmd/factotum/apop.c
@@ -0,0 +1,348 @@
+/*
+ * APOP, CRAM - MD5 challenge/response authentication
+ *
+ * The client does not authenticate the server, hence no CAI.
+ *
+ * Protocol:
+ *
+ *	S -> C:	random@domain
+ *	C -> S:	hex-response
+ *	S -> C:	ok
+ *
+ * Note that this is the protocol between factotum and the local
+ * program, not between the two factotums.  The information 
+ * exchanged here is wrapped in the APOP protocol by the local
+ * programs.
+ *
+ * If S sends "bad [msg]" instead of "ok", that is a hint that the key is bad.
+ * The protocol goes back to "C -> S: user".
+ */
+
+#include "std.h"
+#include "dat.h"
+
+static int
+apopcheck(Key *k)
+{
+	if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
+		werrstr("need user and !password attributes");
+		return -1;
+	}
+	return 0;
+}
+
+static int
+apopclient(Conv *c)
+{
+	char *chal, *pw, *res;
+	int astype, nchal, npw, ntry, ret;
+	uchar resp[MD5dlen];
+	Attr *attr;
+	DigestState *ds;
+	Key *k;
+	
+	chal = nil;
+	k = nil;
+	res = nil;
+	ret = -1;
+	attr = c->attr;
+
+	if(c->proto == &apop)
+		astype = AuthApop;
+	else if(c->proto == &cram)
+		astype = AuthCram;
+	else{
+		werrstr("bad proto");
+		goto out;
+	}
+
+	c->state = "find key";
+	k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
+	if(k == nil)
+		goto out;
+
+	c->state = "read challenge";
+	if((nchal = convreadm(c, &chal)) < 0)
+		goto out;
+
+  	for(ntry=1;; ntry++){
+		if(c->attr != attr)
+			freeattr(c->attr);
+		c->attr = addattrs(copyattr(attr), k->attr);
+		if((pw = strfindattr(k->privattr, "!password")) == nil){
+			werrstr("key has no password (cannot happen?)");
+			goto out;
+		}
+		npw = strlen(pw);
+
+		switch(astype){
+		case AuthApop:
+			ds = md5((uchar*)chal, nchal, nil, nil);
+			md5((uchar*)pw, npw, resp, ds);
+			break;
+		case AuthCram:
+			hmac_md5((uchar*)chal, nchal, (uchar*)pw, npw, resp, nil);
+			break;
+		}
+
+		/* C->S: APOP user hex-response\n */
+		if(ntry == 1)
+			c->state = "write user";
+		else{
+			sprint(c->statebuf, "write user (auth attempt #%d)", ntry);
+			c->state = c->statebuf;
+		}
+		if(convprint(c, "%s", strfindattr(k->attr, "user")) < 0)
+			goto out;
+
+		c->state = "write response";
+		if(convprint(c, "%.*H", sizeof resp, resp) < 0)
+			goto out;
+
+		c->state = "read result";
+		if(convreadm(c, &res) < 0)
+			goto out;
+
+		if(strcmp(res, "ok") == 0)
+			break;
+
+		if(strncmp(res, "bad ", 4) != 0){
+			werrstr("bad result: %s", res);
+			goto out;
+		}
+
+		c->state = "replace key";
+		if((k = keyreplace(c, k, "%s", res+4)) == nil){
+			c->state = "auth failed";
+			werrstr("%s", res+4);
+			goto out;
+		}
+		free(res);
+		res = nil;
+	}
+
+	werrstr("succeeded");
+	ret = 0;
+
+out:
+	keyclose(k);
+	free(chal);
+	if(c->attr != attr)
+		freeattr(attr);
+	return ret;
+}
+
+/* shared with auth dialing routines */
+typedef struct ServerState ServerState;
+struct ServerState
+{
+	int asfd;
+	Key *k;
+	Ticketreq tr;
+	Ticket t;
+	char *dom;
+	char *hostid;
+};
+
+enum
+{
+	APOPCHALLEN = 128,
+};
+
+static int apopchal(ServerState*, int, char[APOPCHALLEN]);
+static int apopresp(ServerState*, char*, char*);
+
+static int
+apopserver(Conv *c)
+{
+	char chal[APOPCHALLEN], *user, *resp;
+	ServerState s;
+	int astype, ret;
+	Attr *a;
+
+	ret = -1;
+	user = nil;
+	resp = nil;
+	memset(&s, 0, sizeof s);
+	s.asfd = -1;
+
+	if(c->proto == &apop)
+		astype = AuthApop;
+	else if(c->proto == &cram)
+		astype = AuthCram;
+	else{
+		werrstr("bad proto");
+		goto out;
+	}
+
+	c->state = "find key";
+	if((s.k = plan9authkey(c->attr)) == nil)
+		goto out;
+
+	a = copyattr(s.k->attr);
+	a = delattr(a, "proto");
+	c->attr = addattrs(c->attr, a);
+	freeattr(a);
+
+	c->state = "authdial";
+	s.hostid = strfindattr(s.k->attr, "user");
+	s.dom = strfindattr(s.k->attr, "dom");
+	if((s.asfd = xioauthdial(nil, s.dom)) < 0){
+		werrstr("authdial %s: %r", s.dom);
+		goto out;
+	}
+
+	c->state = "authchal";
+	if(apopchal(&s, astype, chal) < 0)
+		goto out;
+
+	c->state = "write challenge";
+	if(convprint(c, "%s", chal) < 0)
+		goto out;
+
+	for(;;){
+		c->state = "read user";
+		if(convreadm(c, &user) < 0)
+			goto out;
+
+		c->state = "read response";
+		if(convreadm(c, &resp) < 0)
+			goto out;
+
+		c->state = "authwrite";
+		switch(apopresp(&s, user, resp)){
+		case -1:
+			goto out;
+		case 0:
+			c->state = "write status";
+			if(convprint(c, "bad authentication failed") < 0)
+				goto out;
+			break;
+		case 1:
+			c->state = "write status";
+			if(convprint(c, "ok") < 0)
+				goto out;
+			goto ok;
+		}
+		free(user);
+		free(resp);
+		user = nil;
+		resp = nil;
+	}
+
+ok:
+	ret = 0;
+	c->attr = addcap(c->attr, c->sysuser, &s.t);
+
+out:
+	keyclose(s.k);
+	free(user);
+	free(resp);
+//	xioclose(s.asfd);
+	return ret;
+}
+
+static int
+apopchal(ServerState *s, int astype, char chal[APOPCHALLEN])
+{
+	char trbuf[TICKREQLEN];
+	Ticketreq tr;
+
+	memset(&tr, 0, sizeof tr);
+
+	tr.type = astype;
+
+	if(strlen(s->hostid) >= sizeof tr.hostid){
+		werrstr("hostid too long");
+		return -1;
+	}
+	strcpy(tr.hostid, s->hostid);
+
+	if(strlen(s->dom) >= sizeof tr.authdom){
+		werrstr("domain too long");
+		return -1;
+	}
+	strcpy(tr.authdom, s->dom);
+
+	convTR2M(&tr, trbuf);
+	if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+		return -1;
+
+	if(xioasrdresp(s->asfd, chal, APOPCHALLEN) <= 5)
+		return -1;
+
+	s->tr = tr;
+	return 0;
+}
+
+static int
+apopresp(ServerState *s, char *user, char *resp)
+{
+	char tabuf[TICKETLEN+AUTHENTLEN];
+	char trbuf[TICKREQLEN];
+	int len;
+	Authenticator a;
+	Ticket t;
+	Ticketreq tr;
+
+	tr = s->tr;
+	if(memrandom(tr.chal, CHALLEN) < 0)
+		return -1;
+
+	if(strlen(user) >= sizeof tr.uid){
+		werrstr("uid too long");
+		return -1;
+	}
+	strcpy(tr.uid, user);
+
+	convTR2M(&tr, trbuf);
+	if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+		return -1;
+
+	len = strlen(resp);
+	if(xiowrite(s->asfd, resp, len) != len)
+		return -1;
+
+	if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
+		return 0;
+
+	convM2T(tabuf, &t, s->k->priv);
+	if(t.num != AuthTs
+	|| memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
+		werrstr("key mismatch with auth server");
+		return -1;
+	}
+
+	convM2A(tabuf+TICKETLEN, &a, t.key);
+	if(a.num != AuthAc
+	|| memcmp(a.chal, tr.chal, sizeof a.chal) != 0
+	|| a.id != 0){
+		werrstr("key2 mismatch with auth server");
+		return -1;
+	}
+
+	s->t = t;
+	return 1;
+}
+
+static Role
+apoproles[] = 
+{
+	"client",	apopclient,
+	"server",	apopserver,
+	0
+};
+
+Proto apop = {
+.name=		"apop",
+.roles=		apoproles,
+.checkkey=	apopcheck,
+.keyprompt=	"user? !password?",
+};
+
+Proto cram = {
+.name=		"cram",
+.roles=		apoproles,
+.checkkey=	apopcheck,
+.keyprompt=	"user? !password?",
+};
blob - /dev/null
blob + 94483364776015eab07eb7763030f1803fd5ec65 (mode 644)
--- /dev/null
+++ src/cmd/factotum/attr.c
@@ -0,0 +1,228 @@
+#include "std.h"
+#include "dat.h"
+
+Attr*
+addattr(Attr *a, char *fmt, ...)
+{
+	char buf[1024];
+	va_list arg;
+	Attr *b;
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+sizeof buf, fmt, arg);
+	va_end(arg);
+	b = _parseattr(buf);
+	a = addattrs(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*
+addattrs(Attr *a, Attr *b)
+{
+	int found;
+	Attr **l, *aa;
+
+	for(; b; b=b->next){
+		switch(b->type){
+		case AttrNameval:
+			for(l=&a; *l; ){
+				if(strcmp((*l)->name, b->name) != 0){
+					l=&(*l)->next;
+					continue;
+				}
+				aa = *l;
+				*l = aa->next;
+				aa->next = nil;
+				freeattr(aa);
+			}
+			*l = mkattr(AttrNameval, b->name, b->val, nil);
+			break;
+		case AttrQuery:
+			found = 0;
+			for(l=&a; *l; l=&(*l)->next)
+				if((*l)->type==AttrNameval && strcmp((*l)->name, b->name) == 0)
+					found++;
+			if(!found)
+				*l = mkattr(AttrQuery, b->name, b->val, nil);
+			break;
+		}
+	}
+	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(a0->name, a1->name) < 0){
+			anext = a0;
+			a0 = a0->next;
+		}else{
+			anext = a1;
+			a1 = a1->next;
+		}
+		*l = anext;
+		l = &(*l)->next;
+	}
+	*l = nil;
+	return a;
+}
+
+int
+attrnamefmt(Fmt *fmt)
+{
+	char *b, buf[1024], *ebuf;
+	Attr *a;
+
+	ebuf = buf+sizeof buf;
+	b = buf;
+	strcpy(buf, " ");
+	for(a=va_arg(fmt->args, Attr*); a; a=a->next){
+		if(a->name == nil)
+			continue;
+		b = seprint(b, ebuf, " %q?", a->name);
+	}
+	return fmtstrcpy(fmt, buf+1);
+}
+
+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;
+}
+
+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(a->val, val) == 0)
+			return 1;
+	for(a=_findattr(a1, name); a; a=_findattr(a->next, name))
+		if(strcmp(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(pat->name))
+			type = AttrDefault;
+		switch(type){
+		case AttrQuery:		/* name=something be present */
+			if(!hasname(a0, a1, pat->name))
+				return 0;
+			break;
+		case AttrNameval:	/* name=val must be present */
+			if(!hasnameval(a0, a1, pat->name, pat->val))
+				return 0;
+			break;
+		case AttrDefault:	/* name=val must be present if name=anything is present */
+			if(hasname(a0, a1, pat->name) && !hasnameval(a0, a1, pat->name, pat->val))
+				return 0;
+			break;
+		}
+	}
+	return 1;		
+}
+
+Attr*
+parseattrfmtv(char *fmt, va_list arg)
+{
+	char *s;
+	Attr *a;
+
+	s = vsmprint(fmt, arg);
+	if(s == nil)
+		sysfatal("vsmprint: out of memory");
+	a = parseattr(s);
+	free(s);
+	return a;
+}
+
+Attr*
+parseattrfmt(char *fmt, ...)
+{
+	va_list arg;
+	Attr *a;
+
+	va_start(arg, fmt);
+	a = parseattrfmtv(fmt, arg);
+	va_end(arg);
+	return a;
+}
blob - /dev/null
blob + dcc411cea4af0a3860a830e9c2d10918413a155c (mode 644)
--- /dev/null
+++ src/cmd/factotum/chap.c
@@ -0,0 +1,424 @@
+/*
+ * CHAP, MSCHAP
+ * 
+ * The client does not authenticate the server, hence no CAI
+ *
+ * Protocol:
+ *
+ *	S -> C: random 8-byte challenge
+ *	C -> S: user in UTF-8
+ *	C -> S: Chapreply or MSchapreply structure
+ *	S -> C: ok or 'bad why'
+ *
+ * The chap protocol requires the client to give it id=%d, the id of
+ * the PPP message containing the challenge, which is used
+ * as part of the response.  Because the client protocol is message-id
+ * specific, there is no point in looping to try multiple keys.
+ *
+ * The MS chap protocol actually uses two different hashes, an
+ * older insecure one called the LM (Lan Manager) hash, and a newer
+ * more secure one called the NT hash.  By default we send back only
+ * the NT hash, because the LM hash can help an eavesdropper run
+ * a brute force attack.  If the key has an lm attribute, then we send only the
+ * LM hash.
+ */
+
+#include "std.h"
+#include "dat.h"
+
+enum {
+	ChapChallen = 8,
+
+	MShashlen = 16,
+	MSchallen = 8,
+	MSresplen = 24,
+};
+
+static int
+chapcheck(Key *k)
+{
+	if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
+		werrstr("need user and !password attributes");
+		return -1;
+	}
+	return 0;
+}
+
+static void
+nthash(uchar hash[MShashlen], char *passwd)
+{
+	uchar buf[512];
+	int i;
+	
+	for(i=0; *passwd && i<sizeof(buf); passwd++) {
+		buf[i++] = *passwd;
+		buf[i++] = 0;
+	}
+
+	memset(hash, 0, 16);
+
+	md4(buf, i, hash, 0);
+}
+
+static void
+desencrypt(uchar data[8], uchar key[7])
+{
+	ulong ekey[32];
+
+	key_setup(key, ekey);
+	block_cipher(ekey, data, 0);
+}
+
+static void
+lmhash(uchar hash[MShashlen], char *passwd)
+{
+	uchar buf[14];
+	char *stdtext = "KGS!@#$%";
+	int i;
+
+	strncpy((char*)buf, passwd, sizeof(buf));
+	for(i=0; i<sizeof(buf); i++)
+		if(buf[i] >= 'a' && buf[i] <= 'z')
+			buf[i] += 'A' - 'a';
+
+	memset(hash, 0, 16);
+	memcpy(hash, stdtext, 8);
+	memcpy(hash+8, stdtext, 8);
+
+	desencrypt(hash, buf);
+	desencrypt(hash+8, buf+7);
+}
+
+static 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);
+	}
+}
+
+static int
+chapclient(Conv *c)
+{
+	int id, astype, nchal, npw, ret;
+	uchar *chal;
+	char *s, *pw, *user, *res;
+	Attr *attr;
+	Key *k;
+	Chapreply cr;
+	MSchapreply mscr;
+	DigestState *ds;
+
+	ret = -1;
+	chal = nil;
+	k = nil;
+	attr = c->attr;
+
+	if(c->proto == &chap){
+		astype = AuthChap;
+		s = strfindattr(attr, "id");
+		if(s == nil || *s == 0){
+			werrstr("need id=n attr in start message");
+			goto out;
+		}
+		id = strtol(s, &s, 10);
+		if(*s != 0 || id < 0 || id >= 256){
+			werrstr("bad id=n attr in start message");
+			goto out;
+		}
+		cr.id = id;
+	}else if(c->proto == &mschap)
+		astype = AuthMSchap;
+	else{
+		werrstr("bad proto");
+		goto out;
+	}
+
+	c->state = "find key";
+	k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
+	if(k == nil)
+		goto out;
+
+	c->attr = addattrs(copyattr(attr), k->attr);
+
+	c->state = "read challenge";
+	if((nchal = convreadm(c, (char**)&chal)) < 0)
+		goto out;
+	if(astype == AuthMSchap && nchal != MSchallen)
+	c->state = "write user";
+	if((user = strfindattr(k->attr, "user")) == nil){
+		werrstr("key has no user (cannot happen?)");
+		goto out;
+	}
+	if(convprint(c, "%s", user) < 0)
+		goto out;
+
+	c->state = "write response";
+	if((pw = strfindattr(k->privattr, "!password")) == nil){
+		werrstr("key has no password (cannot happen?)");
+		goto out;
+	}
+	npw = strlen(pw);
+
+	if(astype == AuthChap){
+		ds = md5(&cr.id, 1, 0, 0);
+		md5((uchar*)pw, npw, 0, ds);
+		md5(chal, nchal, (uchar*)cr.resp, ds);
+		if(convwrite(c, &cr, sizeof cr) < 0)
+			goto out;
+	}else{
+		uchar hash[MShashlen];
+
+		memset(&mscr, 0, sizeof mscr);
+		if(strfindattr(k->attr, "lm")){
+			lmhash(hash, pw);
+			mschalresp((uchar*)mscr.LMresp, hash, chal);
+		}else{
+			nthash(hash, pw);
+			mschalresp((uchar*)mscr.NTresp, hash, chal);
+		}
+		if(convwrite(c, &mscr, sizeof mscr) < 0)
+			goto out;
+	}
+
+	c->state = "read result";
+	if(convreadm(c, &res) < 0)
+		goto out;
+	if(strcmp(res, "ok") == 0){
+		ret = 0;
+		werrstr("succeeded");
+		goto out;
+	}
+	if(strncmp(res, "bad ", 4) != 0){
+		werrstr("bad result: %s", res);
+		goto out;
+	}
+
+	c->state = "replace key";
+	keyevict(c, k, "%s", res+4);
+	werrstr("%s", res+4);
+
+out:
+	free(res);
+	keyclose(k);
+	free(chal);
+	if(c->attr != attr)
+		freeattr(attr);
+	return ret;
+}
+
+/* shared with auth dialing routines */
+typedef struct ServerState ServerState;
+struct ServerState
+{
+	int asfd;
+	Key *k;
+	Ticketreq tr;
+	Ticket t;
+	char *dom;
+	char *hostid;
+};
+
+static int chapchal(ServerState*, int, char[ChapChallen]);
+static int chapresp(ServerState*, char*, char*);
+
+static int
+chapserver(Conv *c)
+{
+	char chal[ChapChallen], *user, *resp;
+	ServerState s;
+	int astype, ret;
+	Attr *a;
+
+	ret = -1;
+	user = nil;
+	resp = nil;
+	memset(&s, 0, sizeof s);
+	s.asfd = -1;
+
+	if(c->proto == &chap)
+		astype = AuthChap;
+	else if(c->proto == &mschap)
+		astype = AuthMSchap;
+	else{
+		werrstr("bad proto");
+		goto out;
+	}
+
+	c->state = "find key";
+	if((s.k = plan9authkey(c->attr)) == nil)
+		goto out;
+
+	a = copyattr(s.k->attr);
+	a = delattr(a, "proto");
+	c->attr = addattrs(c->attr, a);
+	freeattr(a);
+
+	c->state = "authdial";
+	s.hostid = strfindattr(s.k->attr, "user");
+	s.dom = strfindattr(s.k->attr, "dom");
+	if((s.asfd = xioauthdial(nil, s.dom)) < 0){
+		werrstr("authdial %s: %r", s.dom);
+		goto out;
+	}
+
+	c->state = "authchal";
+	if(chapchal(&s, astype, chal) < 0)
+		goto out;
+
+	c->state = "write challenge";
+	if(convprint(c, "%s", chal) < 0)
+		goto out;
+
+	c->state = "read user";
+	if(convreadm(c, &user) < 0)
+		goto out;
+
+	c->state = "read response";
+	if(convreadm(c, &resp) < 0)
+		goto out;
+
+	c->state = "authwrite";
+	switch(chapresp(&s, user, resp)){
+	default:
+		fprint(2, "factotum: bad result from chapresp\n");
+		goto out;
+	case -1:
+		goto out;
+	case 0:
+		c->state = "write status";
+		if(convprint(c, "bad authentication failed") < 0)
+			goto out;
+		goto out;
+
+	case 1:
+		c->state = "write status";
+		if(convprint(c, "ok") < 0)
+			goto out;
+		goto ok;
+	}
+
+ok:
+	ret = 0;
+	c->attr = addcap(c->attr, c->sysuser, &s.t);
+
+out:
+	keyclose(s.k);
+	free(user);
+	free(resp);
+//	xioclose(s.asfd);
+	return ret;
+}
+
+static int
+chapchal(ServerState *s, int astype, char chal[ChapChallen])
+{
+	char trbuf[TICKREQLEN];
+	Ticketreq tr;
+
+	memset(&tr, 0, sizeof tr);
+
+	tr.type = astype;
+
+	if(strlen(s->hostid) >= sizeof tr.hostid){
+		werrstr("hostid too long");
+		return -1;
+	}
+	strcpy(tr.hostid, s->hostid);
+
+	if(strlen(s->dom) >= sizeof tr.authdom){
+		werrstr("domain too long");
+		return -1;
+	}
+	strcpy(tr.authdom, s->dom);
+
+	convTR2M(&tr, trbuf);
+	if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+		return -1;
+
+	if(xioasrdresp(s->asfd, chal, ChapChallen) <= 5)
+		return -1;
+
+	s->tr = tr;
+	return 0;
+}
+
+static int
+chapresp(ServerState *s, char *user, char *resp)
+{
+	char tabuf[TICKETLEN+AUTHENTLEN];
+	char trbuf[TICKREQLEN];
+	int len;
+	Authenticator a;
+	Ticket t;
+	Ticketreq tr;
+
+	tr = s->tr;
+	if(memrandom(tr.chal, CHALLEN) < 0)
+		return -1;
+
+	if(strlen(user) >= sizeof tr.uid){
+		werrstr("uid too long");
+		return -1;
+	}
+	strcpy(tr.uid, user);
+
+	convTR2M(&tr, trbuf);
+	if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+		return -1;
+
+	len = strlen(resp);
+	if(xiowrite(s->asfd, resp, len) != len)
+		return -1;
+
+	if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
+		return 0;
+
+	convM2T(tabuf, &t, s->k->priv);
+	if(t.num != AuthTs
+	|| memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
+		werrstr("key mismatch with auth server");
+		return -1;
+	}
+
+	convM2A(tabuf+TICKETLEN, &a, t.key);
+	if(a.num != AuthAc
+	|| memcmp(a.chal, tr.chal, sizeof a.chal) != 0
+	|| a.id != 0){
+		werrstr("key2 mismatch with auth server");
+		return -1;
+	}
+
+	s->t = t;
+	return 1;
+}
+
+static Role
+chaproles[] = 
+{
+	"client",	chapclient,
+	"server",	chapserver,
+	0
+};
+
+Proto chap = {
+.name=		"chap",
+.roles=		chaproles,
+.checkkey=	chapcheck,
+.keyprompt=	"user? !password?",
+};
+
+Proto mschap = {
+.name=		"mschap",
+.roles=		chaproles,
+.checkkey=	chapcheck,
+.keyprompt=	"user? !password?",
+};
+
blob - /dev/null
blob + 8f49245043b90a6fed49bd1f90336db5d1ac1b16 (mode 644)
--- /dev/null
+++ src/cmd/factotum/confirm.c
@@ -0,0 +1,139 @@
+#include "std.h"
+#include "dat.h"
+
+Logbuf confbuf;
+
+void
+confirmread(Req *r)
+{
+	lbread(&confbuf, r);
+}
+
+void
+confirmflush(Req *r)
+{
+	lbflush(&confbuf, r);
+}
+
+int
+confirmwrite(char *s)
+{
+	char *t, *ans;
+	int allow;
+	ulong tag;
+	Attr *a;
+	Conv *c;
+
+	a = _parseattr(s);
+	if(a == nil){
+		werrstr("bad attr");
+		return -1;
+	}
+	if((t = _strfindattr(a, "tag")) == nil){
+		werrstr("no tag");
+		return -1;
+	}
+	tag = strtoul(t, 0, 0);
+	if((ans = _strfindattr(a, "answer")) == nil){
+		werrstr("no answer");
+		return -1;
+	}
+	if(strcmp(ans, "yes") == 0)
+		allow = 1;
+	else if(strcmp(ans, "no") == 0)
+		allow = 0;
+	else{
+		werrstr("bad answer");
+		return -1;
+	}
+	for(c=conv; c; c=c->next){
+		if(tag == c->tag){
+			nbsendul(c->keywait, allow);
+			break;
+		}
+	}
+	if(c == nil){
+		werrstr("tag not found");
+		return -1;
+	}
+	return 0;
+}
+
+int
+confirmkey(Conv *c, Key *k)
+{
+	if(*confirminuse == 0)
+		return -1;
+
+	lbappend(&confbuf, "confirm tag=%lud %A %N", c->tag, k->attr, k->privattr);
+	c->state = "keyconfirm";
+	return recvul(c->keywait);
+}
+
+Logbuf needkeybuf;
+
+void
+needkeyread(Req *r)
+{
+	lbread(&needkeybuf, r);
+}
+
+void
+needkeyflush(Req *r)
+{
+	lbflush(&needkeybuf, r);
+}
+
+int
+needkeywrite(char *s)
+{
+	char *t;
+	ulong tag;
+	Attr *a;
+	Conv *c;
+
+	a = _parseattr(s);
+	if(a == nil){
+		werrstr("empty write");
+		return -1;
+	}
+	if((t = _strfindattr(a, "tag")) == nil){
+		werrstr("no tag");
+		freeattr(a);
+		return -1;
+	}
+	tag = strtoul(t, 0, 0);
+	for(c=conv; c; c=c->next)
+		if(c->tag == tag){
+			nbsendul(c->keywait, 0);
+			break;
+		}
+	if(c == nil){
+		werrstr("tag not found");
+		freeattr(a);
+		return -1;
+	}
+	freeattr(a);
+	return 0;
+}
+
+int
+needkey(Conv *c, Attr *a)
+{
+	if(c == nil || *needkeyinuse == 0)
+		return -1;
+
+	lbappend(&needkeybuf, "needkey tag=%lud %A", c->tag, a);
+	return nbrecvul(c->keywait);
+}
+
+int
+badkey(Conv *c, Key *k, char *msg, Attr *a)
+{
+	if(c == nil || *needkeyinuse == 0)
+		return -1;
+
+	lbappend(&needkeybuf, "badkey tag=%lud %A %N\n%s\n%A",
+		c->tag, k->attr, k->privattr, msg, a);
+	return nbrecvul(c->keywait);
+}
blob - /dev/null
blob + 862993f9a5d0ba879789de766123e5c1bda715e9 (mode 644)
--- /dev/null
+++ src/cmd/factotum/conv.c
@@ -0,0 +1,254 @@
+#include "std.h"
+#include "dat.h"
+
+Conv *conv;
+
+ulong taggen = 1;
+
+Conv*
+convalloc(char *sysuser)
+{
+	Conv *c;
+
+	c = mallocz(sizeof(Conv), 1);
+	if(c == nil)
+		return nil;
+	c->ref = 1;
+	c->tag = taggen++;
+	c->next = conv;
+	c->sysuser = estrdup(sysuser);
+	c->state = "nascent";
+	c->rpcwait = chancreate(sizeof(void*), 0);
+	c->keywait = chancreate(sizeof(void*), 0);
+	strcpy(c->err, "protocol has not started");
+	conv = c;
+	convreset(c);
+	return c;
+}
+
+void
+convreset(Conv *c)
+{
+	if(c->ref != 1){
+		c->hangup = 1;
+		nbsendp(c->rpcwait, 0);
+		while(c->ref > 1)
+			yield();
+		c->hangup = 0;
+	}
+	c->state = "nascent";
+	c->err[0] = '\0';
+	freeattr(c->attr);
+	c->attr = nil;
+	c->proto = nil;
+	c->rpc.op = 0;
+	c->active = 0;
+	c->done = 0;
+	c->hangup = 0;
+}
+
+void
+convhangup(Conv *c)
+{
+	c->hangup = 1;
+	c->rpc.op = 0;
+	(*c->kickreply)(c);
+	nbsendp(c->rpcwait, 0);
+}
+
+void
+convclose(Conv *c)
+{
+	Conv *p;
+
+	if(c == nil)
+		return;
+
+	if(--c->ref > 0)
+		return;
+
+	if(c == conv){
+		conv = c->next;
+		goto free;
+	}
+	for(p=conv; p && p->next!=c; p=p->next)
+		;
+	if(p == nil){
+		print("cannot find conv in list\n");
+		return;
+	}
+	p->next = c->next;
+
+free:
+	c->next = nil;
+	free(c);
+}
+
+static Rpc*
+convgetrpc(Conv *c, int want)
+{
+	for(;;){
+		if(c->hangup){
+			werrstr("hangup");
+			return nil;
+		}
+		if(c->rpc.op == RpcUnknown){
+			recvp(c->rpcwait);
+			if(c->hangup){
+				werrstr("hangup");
+				return nil;
+			}
+			if(c->rpc.op == RpcUnknown)
+				continue;
+		}
+		if(want < 0 || c->rpc.op == want)
+			return &c->rpc;
+		rpcrespond(c, "phase in state '%s' want '%s'", c->state, rpcname[want]);
+	}
+	return nil;	/* not reached */
+}
+
+/* read until the done function tells us that's enough */
+int
+convreadfn(Conv *c, int (*done)(void*, int), char **ps)
+{
+	int n;
+	Rpc *r;
+	char *s;
+
+	for(;;){
+		r = convgetrpc(c, RpcWrite);
+		if(r == nil)
+			return -1;
+		n = (*done)(r->data, r->count);
+		if(n == r->count)
+			break;
+		rpcrespond(c, "toosmall %d", n);
+	}
+
+	s = emalloc(r->count+1);
+	memmove(s, r->data, r->count);
+	s[r->count] = 0;
+	*ps = s;
+	rpcrespond(c, "ok");
+	return r->count;
+}
+
+/*
+ * read until we get a non-zero write.  assumes remote side
+ * knows something about the protocol (is not auth_proxy).
+ * the remote side typically won't bother with the zero-length
+ * write to find out the length -- the loop is there only so the
+ * test program can call auth_proxy on both sides of a pipe
+ * to play a conversation.
+ */
+int
+convreadm(Conv *c, char **ps)
+{
+	char *s;
+	Rpc *r;
+
+	for(;;){
+		r = convgetrpc(c, RpcWrite);
+		if(r == nil)
+			return -1;
+		if(r->count > 0)
+			break;
+		rpcrespond(c, "toosmall %d", AuthRpcMax);
+	}
+	s = emalloc(r->count+1);
+	memmove(s, r->data, r->count);
+	s[r->count] = 0;
+	*ps = s;
+	rpcrespond(c, "ok");
+	return r->count;
+}
+
+/* read exactly count bytes */
+int
+convread(Conv *c, void *data, int count)
+{
+	Rpc *r;
+
+	for(;;){
+		r = convgetrpc(c, RpcWrite);
+		if(r == nil)
+			return -1;
+		if(r->count == count)
+			break;
+		if(r->count < count)
+			rpcrespond(c, "toosmall %d", count);
+		else
+			rpcrespond(c, "error too much data; want %d got %d", count, r->count);
+	}
+	memmove(data, r->data, count);
+	rpcrespond(c, "ok");
+	return 0;
+}
+
+/* write exactly count bytes */
+int
+convwrite(Conv *c, void *data, int count)
+{
+	Rpc *r;
+
+	for(;;){
+		r = convgetrpc(c, RpcRead);
+		if(r == nil)
+			return -1;
+		break;
+	}
+	rpcrespondn(c, "ok", data, count);
+	return 0;
+}
+
+/* print to the conversation */
+int
+convprint(Conv *c, char *fmt, ...)
+{
+	char *s;
+	va_list arg;
+	int ret;
+
+	va_start(arg, fmt);
+	s = vsmprint(fmt, arg);
+	va_end(arg);
+	if(s == nil)
+		return -1;
+	ret = convwrite(c, s, strlen(s));
+	free(s);
+	return ret;
+}
+
+/* ask for a key */
+int
+convneedkey(Conv *c, Attr *a)
+{
+	/*
+	 * Piggyback key requests in the usual RPC channel.
+	 * Wait for the next RPC and then send a key request
+	 * in response.  The keys get added out-of-band (via the
+	 * ctl file), so assume the key has been added when the
+	 * next request comes in.
+	 */
+	if(convgetrpc(c, -1) == nil)
+		return -1;
+	rpcrespond(c, "needkey %A", a);
+	if(convgetrpc(c, -1) == nil)
+		return -1;
+	return 0;
+}
+
+/* ask for a replacement for a bad key*/
+int
+convbadkey(Conv *c, Key *k, char *msg, Attr *a)
+{
+	if(convgetrpc(c, -1) == nil)
+		return -1;
+	rpcrespond(c, "badkey %A %N\n%s\n%A",
+		k->attr, k->privattr, msg, a);
+	if(convgetrpc(c, -1) == nil)
+		return -1;
+	return 0;
+}
+
blob - /dev/null
blob + da8280ada929d6305234c49dc6c57fadea6876e7 (mode 644)
--- /dev/null
+++ src/cmd/factotum/cpu.c
@@ -0,0 +1,1117 @@
+/*
+ * cpu.c - Make a connection to a cpu server
+ *
+ *	   Invoked by listen as 'cpu -R | -N service net netdir'
+ *	    	   by users  as 'cpu [-h system] [-c cmd args ...]'
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <fcall.h>
+#include <libsec.h>
+
+#define	Maxfdata 8192
+
+void	remoteside(int);
+void	fatal(int, char*, ...);
+void	lclnoteproc(int);
+void	rmtnoteproc(void);
+void	catcher(void*, char*);
+void	usage(void);
+void	writestr(int, char*, char*, int);
+int	readstr(int, char*, int);
+char	*rexcall(int*, char*, char*);
+int	setamalg(char*);
+
+int 	notechan;
+char	system[32];
+int	cflag;
+int	hflag;
+int	dbg;
+char	*user;
+
+char	*srvname = "ncpu";
+char	*exportfs = "/bin/exportfs";
+char	*ealgs = "rc4_256 sha1";
+
+/* message size for exportfs; may be larger so we can do big graphics in CPU window */
+int	msgsize = 8192+IOHDRSZ;
+
+/* authentication mechanisms */
+static int	netkeyauth(int);
+static int	netkeysrvauth(int, char*);
+static int	p9auth(int);
+static int	srvp9auth(int, char*);
+static int	noauth(int);
+static int	srvnoauth(int, char*);
+
+typedef struct AuthMethod AuthMethod;
+struct AuthMethod {
+	char	*name;			/* name of method */
+	int	(*cf)(int);		/* client side authentication */
+	int	(*sf)(int, char*);	/* server side authentication */
+} authmethod[] =
+{
+	{ "p9",		p9auth,		srvp9auth,},
+	{ "netkey",	netkeyauth,	netkeysrvauth,},
+//	{ "none",	noauth,		srvnoauth,},
+	{ nil,	nil}
+};
+AuthMethod *am = authmethod;	/* default is p9 */
+
+char *p9authproto = "p9any";
+
+int setam(char*);
+
+void
+usage(void)
+{
+	fprint(2, "usage: cpu [-h system] [-a authmethod] [-e 'crypt hash'] [-c cmd args ...]\n");
+	exits("usage");
+}
+int fdd;
+
+void
+main(int argc, char **argv)
+{
+	char dat[128], buf[128], cmd[128], *p, *err;
+	int fd, ms, kms, data;
+
+	/* see if we should use a larger message size */
+	fd = open("/dev/draw", OREAD);
+	if(fd > 0){
+		ms = iounit(fd);
+		if(ms != 0 && ms < ms+IOHDRSZ)
+			msgsize = ms+IOHDRSZ;
+		close(fd);
+	}
+	kms = kiounit();
+	if(msgsize > kms-IOHDRSZ-100)	/* 100 for network packets, etc. */
+		msgsize = kms-IOHDRSZ-100;
+
+	user = getuser();
+	if(user == nil)
+		fatal(1, "can't read user name");
+	ARGBEGIN{
+	case 'a':
+		p = EARGF(usage());
+		if(setam(p) < 0)
+			fatal(0, "unknown auth method %s", p);
+		break;
+	case 'e':
+		ealgs = EARGF(usage());
+		if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
+			ealgs = nil;
+		break;
+	case 'd':
+		dbg++;
+		break;
+	case 'f':
+		/* ignored but accepted for compatibility */
+		break;
+	case 'O':
+		p9authproto = "p9sk2";
+		remoteside(1);				/* From listen */
+		break;
+	case 'R':				/* From listen */
+		remoteside(0);
+		break;
+	case 'h':
+		hflag++;
+		p = EARGF(usage());
+		strcpy(system, p);
+		break;
+	case 'c':
+		cflag++;
+		cmd[0] = '!';
+		cmd[1] = '\0';
+		while(p = ARGF()) {
+			strcat(cmd, " ");
+			strcat(cmd, p);
+		}
+		break;
+	case 'o':
+		p9authproto = "p9sk2";
+		srvname = "cpu";
+		break;
+	case 'u':
+		user = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+
+	if(argc != 0)
+		usage();
+
+	if(hflag == 0) {
+		p = getenv("cpu");
+		if(p == 0)
+			fatal(0, "set $cpu");
+		strcpy(system, p);
+	}
+
+	if(err = rexcall(&data, system, srvname))
+		fatal(1, "%s: %s", err, system);
+
+	/* Tell the remote side the command to execute and where our working directory is */
+	if(cflag)
+		writestr(data, cmd, "command", 0);
+	if(getwd(dat, sizeof(dat)) == 0)
+		writestr(data, "NO", "dir", 0);
+	else
+		writestr(data, dat, "dir", 0);
+
+	/* start up a process to pass along notes */
+	lclnoteproc(data);
+
+	/* 
+	 *  Wait for the other end to execute and start our file service
+	 *  of /mnt/term
+	 */
+	if(readstr(data, buf, sizeof(buf)) < 0)
+		fatal(1, "waiting for FS");
+	if(strncmp("FS", buf, 2) != 0) {
+		print("remote cpu: %s", buf);
+		exits(buf);
+	}
+
+	/* Begin serving the gnot namespace */
+	close(0);
+	dup(data, 0);
+	close(data);
+	sprint(buf, "%d", msgsize);
+	if(dbg)
+		execl(exportfs, exportfs, "-dm", buf, 0);
+	else
+		execl(exportfs, exportfs, "-m", buf, 0);
+	fatal(1, "starting exportfs");
+}
+
+void
+fatal(int syserr, char *fmt, ...)
+{
+	char buf[ERRMAX];
+	va_list arg;
+
+	va_start(arg, fmt);
+	doprint(buf, buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	if(syserr)
+		fprint(2, "cpu: %s: %r\n", buf);
+	else
+		fprint(2, "cpu: %s\n", buf);
+	exits(buf);
+}
+
+char *negstr = "negotiating authentication method";
+
+char bug[256];
+
+int
+old9p(int fd)
+{
+	int p[2];
+
+	if(pipe(p) < 0)
+		fatal(1, "pipe");
+
+	switch(rfork(RFPROC|RFFDG|RFNAMEG)) {
+	case -1:
+		fatal(1, "rfork srvold9p");
+	case 0:
+		if(fd != 1){
+			dup(fd, 1);
+			close(fd);
+		}
+		if(p[0] != 0){
+			dup(p[0], 0);
+			close(p[0]);
+		}
+		close(p[1]);
+		if(0){
+			fd = open("/sys/log/cpu", OWRITE);
+			if(fd != 2){
+				dup(fd, 2);
+				close(fd);
+			}
+			execl("/bin/srvold9p", "srvold9p", "-ds", 0);
+		} else
+			execl("/bin/srvold9p", "srvold9p", "-s", 0);
+		fatal(1, "exec srvold9p");
+	default:
+		close(fd);
+		close(p[0]);
+	}
+	return p[1];	
+}
+
+/* Invoked with stdin, stdout and stderr connected to the network connection */
+void
+remoteside(int old)
+{
+	char user[128], home[128], buf[128], xdir[128], cmd[128];
+	int i, n, fd, badchdir, gotcmd;
+
+	fd = 0;
+
+	/* negotiate authentication mechanism */
+	n = readstr(fd, cmd, sizeof(cmd));
+	if(n < 0)
+		fatal(1, "authenticating");
+	if(setamalg(cmd) < 0){
+		writestr(fd, "unsupported auth method", nil, 0);
+		fatal(1, "bad auth method %s", cmd);
+	} else
+		writestr(fd, "", "", 1);
+
+	fd = (*am->sf)(fd, user);
+	if(fd < 0)
+		fatal(1, "srvauth");
+
+	/* Set environment values for the user */
+	putenv("user", user);
+	sprint(home, "/usr/%s", user);
+	putenv("home", home);
+
+	/* Now collect invoking cpu's current directory or possibly a command */
+	gotcmd = 0;
+	if(readstr(fd, xdir, sizeof(xdir)) < 0)
+		fatal(1, "dir/cmd");
+	if(xdir[0] == '!') {
+		strcpy(cmd, &xdir[1]);
+		gotcmd = 1;
+		if(readstr(fd, xdir, sizeof(xdir)) < 0)
+			fatal(1, "dir");
+	}
+
+	/* Establish the new process at the current working directory of the
+	 * gnot */
+	badchdir = 0;
+	if(strcmp(xdir, "NO") == 0)
+		chdir(home);
+	else if(chdir(xdir) < 0) {
+		badchdir = 1;
+		chdir(home);
+	}
+
+	/* Start the gnot serving its namespace */
+	writestr(fd, "FS", "FS", 0);
+	writestr(fd, "/", "exportfs dir", 0);
+
+	n = read(fd, buf, sizeof(buf));
+	if(n != 2 || buf[0] != 'O' || buf[1] != 'K')
+		exits("remote tree");
+
+	if(old)
+		fd = old9p(fd);
+
+	/* make sure buffers are big by doing fversion explicitly; pick a huge number; other side will trim */
+	strcpy(buf, VERSION9P);
+	if(fversion(fd, 64*1024, buf, sizeof buf) < 0)
+		exits("fversion failed");
+	if(mount(fd, -1, "/mnt/term", MCREATE|MREPL, "") < 0)
+		exits("mount failed");
+
+	close(fd);
+
+	/* the remote noteproc uses the mount so it must follow it */
+	rmtnoteproc();
+
+	for(i = 0; i < 3; i++)
+		close(i);
+
+	if(open("/mnt/term/dev/cons", OREAD) != 0)
+		exits("open stdin");
+	if(open("/mnt/term/dev/cons", OWRITE) != 1)
+		exits("open stdout");
+	dup(1, 2);
+
+	if(badchdir)
+		print("cpu: failed to chdir to '%s'\n", xdir);
+
+	if(gotcmd)
+		execl("/bin/rc", "rc", "-lc", cmd, 0);
+	else
+		execl("/bin/rc", "rc", "-li", 0);
+	fatal(1, "exec shell");
+}
+
+char*
+rexcall(int *fd, char *host, char *service)
+{
+	char *na;
+	char dir[128];
+	char err[ERRMAX];
+	char msg[128];
+	int n;
+
+	na = netmkaddr(host, 0, service);
+	if((*fd = dial(na, 0, dir, 0)) < 0)
+		return "can't dial";
+
+	/* negotiate authentication mechanism */
+	if(ealgs != nil)
+		snprint(msg, sizeof(msg), "%s %s", am->name, ealgs);
+	else
+		snprint(msg, sizeof(msg), "%s", am->name);
+	writestr(*fd, msg, negstr, 0);
+	n = readstr(*fd, err, sizeof err);
+	if(n < 0)
+		return negstr;
+	if(*err){
+		werrstr(err);
+		return negstr;
+	}
+
+	/* authenticate */
+	*fd = (*am->cf)(*fd);
+	if(*fd < 0)
+		return "can't authenticate";
+	return 0;
+}
+
+void
+writestr(int fd, char *str, char *thing, int ignore)
+{
+	int l, n;
+
+	l = strlen(str);
+	n = write(fd, str, l+1);
+	if(!ignore && n < 0)
+		fatal(1, "writing network: %s", thing);
+}
+
+int
+readstr(int fd, char *str, int len)
+{
+	int n;
+
+	while(len) {
+		n = read(fd, str, 1);
+		if(n < 0) 
+			return -1;
+		if(*str == '\0')
+			return 0;
+		str++;
+		len--;
+	}
+	return -1;
+}
+
+static int
+readln(char *buf, int n)
+{
+	char *p = buf;
+
+	n--;
+	while(n > 0){
+		if(read(0, p, 1) != 1)
+			break;
+		if(*p == '\n' || *p == '\r'){
+			*p = 0;
+			return p-buf;
+		}
+		p++;
+	}
+	*p = 0;
+	return p-buf;
+}
+
+/*
+ *  user level challenge/response
+ */
+static int
+netkeyauth(int fd)
+{
+	char chall[32];
+	char resp[32];
+
+	strcpy(chall, getuser());
+	print("user[%s]: ", chall);
+	if(readln(resp, sizeof(resp)) < 0)
+		return -1;
+	if(*resp != 0)
+		strcpy(chall, resp);
+	writestr(fd, chall, "challenge/response", 1);
+
+	for(;;){
+		if(readstr(fd, chall, sizeof chall) < 0)
+			break;
+		if(*chall == 0)
+			return fd;
+		print("challenge: %s\nresponse: ", chall);
+		if(readln(resp, sizeof(resp)) < 0)
+			break;
+		writestr(fd, resp, "challenge/response", 1);
+	}
+	return -1;
+}
+
+static int
+netkeysrvauth(int fd, char *user)
+{
+	char response[32];
+	Chalstate *ch;
+	int tries;
+	AuthInfo *ai;
+
+	if(readstr(fd, user, 32) < 0)
+		return -1;
+
+	ai = nil;
+	ch = nil;
+	for(tries = 0; tries < 10; tries++){
+		if((ch = auth_challenge("p9cr", user, nil)) == nil)
+			return -1;
+		writestr(fd, ch->chal, "challenge", 1);
+		if(readstr(fd, response, sizeof response) < 0)
+			return -1;
+		ch->resp = response;
+		ch->nresp = strlen(response);
+		if((ai = auth_response(ch)) != nil)
+			break;
+	}
+	auth_freechal(ch);
+	if(ai == nil)
+		return -1;
+	writestr(fd, "", "challenge", 1);
+	if(auth_chuid(ai, 0) < 0)
+		fatal(1, "newns");
+	auth_freeAI(ai);
+	return fd;
+}
+
+static void
+mksecret(char *t, uchar *f)
+{
+	sprint(t, "%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
+		f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9]);
+}
+
+/*
+ *  plan9 authentication followed by rc4 encryption
+ */
+static int
+p9auth(int fd)
+{
+	uchar key[16];
+	uchar digest[SHA1dlen];
+	char fromclientsecret[21];
+	char fromserversecret[21];
+	int i;
+	AuthInfo *ai;
+
+	ai = auth_proxy(fd, auth_getkey, "proto=%q user=%q role=client", p9authproto, user);
+	if(ai == nil)
+		return -1;
+	memmove(key+4, ai->secret, ai->nsecret);
+	if(ealgs == nil)
+		return fd;
+
+	/* exchange random numbers */
+	srand(truerand());
+	for(i = 0; i < 4; i++)
+		key[i] = rand();
+	if(write(fd, key, 4) != 4)
+		return -1;
+	if(readn(fd, key+12, 4) != 4)
+		return -1;
+
+	/* scramble into two secrets */
+	sha1(key, sizeof(key), digest, nil);
+	mksecret(fromclientsecret, digest);
+	mksecret(fromserversecret, digest+10);
+
+	/* set up encryption */
+	i = pushssl(fd, ealgs, fromclientsecret, fromserversecret, nil);
+	if(i < 0)
+		werrstr("can't establish ssl connection: %r");
+	return i;
+}
+
+static int
+noauth(int fd)
+{
+	ealgs = nil;
+	return fd;
+}
+
+static int
+srvnoauth(int fd, char *user)
+{
+	strcpy(user, getuser());
+	ealgs = nil;
+	return fd;
+}
+
+void
+loghex(uchar *p, int n)
+{
+	char buf[100];
+	int i;
+
+	for(i = 0; i < n; i++)
+		sprint(buf+2*i, "%2.2ux", p[i]);
+	syslog(0, "cpu", buf);
+}
+
+static int
+srvp9auth(int fd, char *user)
+{
+	uchar key[16];
+	uchar digest[SHA1dlen];
+	char fromclientsecret[21];
+	char fromserversecret[21];
+	int i;
+	AuthInfo *ai;
+
+	ai = auth_proxy(0, nil, "proto=%q role=server", p9authproto);
+	if(ai == nil)
+		return -1;
+	if(auth_chuid(ai, nil) < 0)
+		return -1;
+	strcpy(user, ai->cuid);
+	memmove(key+4, ai->secret, ai->nsecret);
+
+	if(ealgs == nil)
+		return fd;
+
+	/* exchange random numbers */
+	srand(truerand());
+	for(i = 0; i < 4; i++)
+		key[i+12] = rand();
+	if(readn(fd, key, 4) != 4)
+		return -1;
+	if(write(fd, key+12, 4) != 4)
+		return -1;
+
+	/* scramble into two secrets */
+	sha1(key, sizeof(key), digest, nil);
+	mksecret(fromclientsecret, digest);
+	mksecret(fromserversecret, digest+10);
+
+	/* set up encryption */
+	i = pushssl(fd, ealgs, fromserversecret, fromclientsecret, nil);
+	if(i < 0)
+		werrstr("can't establish ssl connection: %r");
+	return i;
+}
+
+/*
+ *  set authentication mechanism
+ */
+int
+setam(char *name)
+{
+	for(am = authmethod; am->name != nil; am++)
+		if(strcmp(am->name, name) == 0)
+			return 0;
+	am = authmethod;
+	return -1;
+}
+
+/*
+ *  set authentication mechanism and encryption/hash algs
+ */
+int
+setamalg(char *s)
+{
+	ealgs = strchr(s, ' ');
+	if(ealgs != nil)
+		*ealgs++ = 0;
+	return setam(s);
+}
+
+char *rmtnotefile = "/mnt/term/dev/cpunote";
+
+/*
+ *  loop reading /mnt/term/dev/note looking for notes.
+ *  The child returns to start the shell.
+ */
+void
+rmtnoteproc(void)
+{
+	int n, fd, pid, notepid;
+	char buf[256];
+
+	/* new proc returns to start shell */
+	pid = rfork(RFPROC|RFFDG|RFNOTEG|RFNAMEG|RFMEM);
+	switch(pid){
+	case -1:
+		syslog(0, "cpu", "cpu -R: can't start noteproc: %r");
+		return;
+	case 0:
+		return;
+	}
+
+	/* new proc reads notes from other side and posts them to shell */
+	switch(notepid = rfork(RFPROC|RFFDG|RFMEM)){
+	case -1:
+		syslog(0, "cpu", "cpu -R: can't start wait proc: %r");
+		_exits(0);
+	case 0:
+		fd = open(rmtnotefile, OREAD);
+		if(fd < 0){
+			syslog(0, "cpu", "cpu -R: can't open %s", rmtnotefile);
+			_exits(0);
+		}
+	
+		for(;;){
+			n = read(fd, buf, sizeof(buf)-1);
+			if(n <= 0){
+				postnote(PNGROUP, pid, "hangup");
+				_exits(0);
+			}
+			buf[n] = 0;
+			postnote(PNGROUP, pid, buf);
+		}
+		break;
+	}
+
+	/* original proc waits for shell proc to die and kills note proc */
+	for(;;){
+		n = waitpid();
+		if(n < 0 || n == pid)
+			break;
+	}
+	postnote(PNPROC, notepid, "kill");
+	_exits(0);
+}
+
+enum
+{
+	Qdir,
+	Qcpunote,
+
+	Nfid = 32,
+};
+
+struct {
+	char	*name;
+	Qid	qid;
+	ulong	perm;
+} fstab[] =
+{
+	[Qdir]		{ ".",		{Qdir, 0, QTDIR},	DMDIR|0555	},
+	[Qcpunote]	{ "cpunote",	{Qcpunote, 0},		0444		},
+};
+
+typedef struct Note Note;
+struct Note
+{
+	Note *next;
+	char msg[ERRMAX];
+};
+
+typedef struct Request Request;
+struct Request
+{
+	Request *next;
+	Fcall f;
+};
+
+typedef struct Fid Fid;
+struct Fid
+{
+	int	fid;
+	int	file;
+};
+Fid fids[Nfid];
+
+struct {
+	Lock;
+	Note *nfirst, *nlast;
+	Request *rfirst, *rlast;
+} nfs;
+
+int
+fsreply(int fd, Fcall *f)
+{
+	uchar buf[IOHDRSZ+Maxfdata];
+	int n;
+
+	if(dbg)
+		fprint(2, "<-%F\n", f);
+	n = convS2M(f, buf, sizeof buf);
+	if(n > 0){
+		if(write(fd, buf, n) != n){
+			close(fd);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+/* match a note read request with a note, reply to the request */
+int
+kick(int fd)
+{
+	Request *rp;
+	Note *np;
+	int rv;
+
+	for(;;){
+		lock(&nfs);
+		rp = nfs.rfirst;
+		np = nfs.nfirst;
+		if(rp == nil || np == nil){
+			unlock(&nfs);
+			break;
+		}
+		nfs.rfirst = rp->next;
+		nfs.nfirst = np->next;
+		unlock(&nfs);
+
+		rp->f.type = Rread;
+		rp->f.count = strlen(np->msg);
+		rp->f.data = np->msg;
+		rv = fsreply(fd, &rp->f);
+		free(rp);
+		free(np);
+		if(rv < 0)
+			return -1;
+	}
+	return 0;
+}
+
+void
+flushreq(int tag)
+{
+	Request **l, *rp;
+
+	lock(&nfs);
+	for(l = &nfs.rfirst; *l != nil; l = &(*l)->next){
+		rp = *l;
+		if(rp->f.tag == tag){
+			*l = rp->next;
+			unlock(&nfs);
+			free(rp);
+			return;
+		}
+	}
+	unlock(&nfs);
+}
+
+Fid*
+getfid(int fid)
+{
+	int i, freefid;
+
+	freefid = -1;
+	for(i = 0; i < Nfid; i++){
+		if(freefid < 0 && fids[i].file < 0)
+			freefid = i;
+		if(fids[i].fid == fid)
+			return &fids[i];
+	}
+	if(freefid >= 0){
+		fids[freefid].fid = fid;
+		return &fids[freefid];
+	}
+	return nil;
+}
+
+int
+fsstat(int fd, Fid *fid, Fcall *f)
+{
+	Dir d;
+	uchar statbuf[256];
+
+	memset(&d, 0, sizeof(d));
+	d.name = fstab[fid->file].name;
+	d.uid = user;
+	d.gid = user;
+	d.muid = user;
+	d.qid = fstab[fid->file].qid;
+	d.mode = fstab[fid->file].perm;
+	d.atime = d.mtime = time(0);
+	f->stat = statbuf;
+	f->nstat = convD2M(&d, statbuf, sizeof statbuf);
+	return fsreply(fd, f);
+}
+
+int
+fsread(int fd, Fid *fid, Fcall *f)
+{
+	Dir d;
+	uchar buf[256];
+	Request *rp;
+
+	switch(fid->file){
+	default:
+		return -1;
+	case Qdir:
+		if(f->offset == 0 && f->count >0){
+			memset(&d, 0, sizeof(d));
+			d.name = fstab[Qcpunote].name;
+			d.uid = user;
+			d.gid = user;
+			d.muid = user;
+			d.qid = fstab[Qcpunote].qid;
+			d.mode = fstab[Qcpunote].perm;
+			d.atime = d.mtime = time(0);
+			f->count = convD2M(&d, buf, sizeof buf);
+			f->data = (char*)buf;
+		} else
+			f->count = 0;
+		return fsreply(fd, f);
+	case Qcpunote:
+		rp = mallocz(sizeof(*rp), 1);
+		if(rp == nil)
+			return -1;
+		rp->f = *f;
+		lock(&nfs);
+		if(nfs.rfirst == nil)
+			nfs.rfirst = rp;
+		else
+			nfs.rlast->next = rp;
+		nfs.rlast = rp;
+		unlock(&nfs);
+		return kick(fd);;
+	}
+}
+
+char Eperm[] = "permission denied";
+char Enofile[] = "out of files";
+char Enotdir[] = "not a directory";
+
+void
+notefs(int fd)
+{
+	uchar buf[IOHDRSZ+Maxfdata];
+	int i, j, n;
+	char err[ERRMAX];
+	Fcall f;
+	Fid *fid, *nfid;
+	int doreply;
+
+	rfork(RFNOTEG);
+	fmtinstall('F', fcallconv);
+
+	for(n = 0; n < Nfid; n++)
+		fids[n].file = -1;
+
+	for(;;){
+		n = read9pmsg(fd, buf, sizeof(buf));
+		if(n <= 0){
+			if(dbg)
+				fprint(2, "read9pmsg(%d) returns %d: %r\n", fd, n);
+			break;
+		}
+		if(convM2S(buf, n, &f) < 0)
+			break;
+		if(dbg)
+			fprint(2, "->%F\n", &f);
+		doreply = 1;
+		fid = getfid(f.fid);
+		if(fid == nil){
+nofids:
+			f.type = Rerror;
+			f.ename = Enofile;
+			fsreply(fd, &f);
+			continue;
+		}
+		switch(f.type++){
+		default:
+			f.type = Rerror;
+			f.ename = "unknown type";
+			break;
+		case Tflush:
+			flushreq(f.oldtag);
+			break;
+		case Tversion:
+			if(f.msize > IOHDRSZ+Maxfdata)
+				f.msize = IOHDRSZ+Maxfdata;
+			break;
+		case Tauth:
+			f.type = Rerror;
+			f.ename = "cpu: authentication not required";
+			break;
+		case Tattach:
+			f.qid = fstab[Qdir].qid;
+			fid->file = Qdir;
+			break;
+		case Twalk:
+			nfid = nil;
+			if(f.newfid != f.fid){
+				nfid = getfid(f.newfid);
+				if(nfid == nil)
+					goto nofids;
+				nfid->file = fid->file;
+				fid = nfid;
+			}
+
+			f.ename = nil;
+			for(i=0; i<f.nwname; i++){
+				if(i > MAXWELEM){
+					f.type = Rerror;
+					f.ename = "too many name elements";
+					break;
+				}
+				if(fid->file != Qdir){
+					f.type = Rerror;
+					f.ename = Enotdir;
+					break;
+				}
+				if(strcmp(f.wname[i], "cpunote") == 0){
+					fid->file = Qcpunote;
+					f.wqid[i] = fstab[Qcpunote].qid;
+					continue;
+				}
+				f.type = Rerror;
+				f.ename = err;
+				strcpy(err, "cpu: file \"");
+				for(j=0; j<=i; j++){
+					if(strlen(err)+1+strlen(f.wname[j])+32 > sizeof err)
+						break;
+					if(j != 0)
+						strcat(err, "/");
+					strcat(err, f.wname[j]);
+				}
+				strcat(err, "\" does not exist");
+				break;
+			}
+			if(nfid != nil && (f.ename != nil || i < f.nwname))
+				nfid ->file = -1;
+			if(f.type != Rerror)
+				f.nwqid = i;
+			break;
+		case Topen:
+			if(f.mode != OREAD){
+				f.type = Rerror;
+				f.ename = Eperm;
+			}
+			f.qid = fstab[fid->file].qid;
+			break;
+		case Tcreate:
+			f.type = Rerror;
+			f.ename = Eperm;
+			break;
+		case Tread:
+			if(fsread(fd, fid, &f) < 0)
+				goto err;
+			doreply = 0;
+			break;
+		case Twrite:
+			f.type = Rerror;
+			f.ename = Eperm;
+			break;
+		case Tclunk:
+			fid->file = -1;
+			break;
+		case Tremove:
+			f.type = Rerror;
+			f.ename = Eperm;
+			break;
+		case Tstat:
+			if(fsstat(fd, fid, &f) < 0)
+				goto err;
+			doreply = 0;
+			break;
+		case Twstat:
+			f.type = Rerror;
+			f.ename = Eperm;
+			break;
+		}
+		if(doreply)
+			if(fsreply(fd, &f) < 0)
+				break;
+	}
+err:
+	if(dbg)
+		fprint(2, "notefs exiting: %r\n");
+	close(fd);
+}
+
+char 	notebuf[ERRMAX];
+
+void
+catcher(void*, char *text)
+{
+	int n;
+
+	n = strlen(text);
+	if(n >= sizeof(notebuf))
+		n = sizeof(notebuf)-1;
+	memmove(notebuf, text, n);
+	notebuf[n] = '\0';
+	noted(NCONT);
+}
+
+/*
+ *  mount in /dev a note file for the remote side to read.
+ */
+void
+lclnoteproc(int netfd)
+{
+	int exportfspid;
+	Waitmsg *w;
+	Note *np;
+	int pfd[2];
+
+	if(pipe(pfd) < 0){
+		fprint(2, "cpu: can't start note proc: pipe: %r\n");
+		return;
+	}
+
+	/* new proc mounts and returns to start exportfs */
+	switch(exportfspid = rfork(RFPROC|RFNAMEG|RFFDG|RFMEM)){
+	case -1:
+		fprint(2, "cpu: can't start note proc: rfork: %r\n");
+		return;
+	case 0:
+		close(pfd[0]);
+		if(mount(pfd[1], -1, "/dev", MBEFORE, "") < 0)
+			fprint(2, "cpu: can't mount note proc: %r\n");
+		close(pfd[1]);
+		return;
+	}
+
+	close(netfd);
+	close(pfd[1]);
+
+	/* new proc listens for note file system rpc's */
+	switch(rfork(RFPROC|RFNAMEG|RFMEM)){
+	case -1:
+		fprint(2, "cpu: can't start note proc: rfork1: %r\n");
+		_exits(0);
+	case 0:
+		notefs(pfd[0]);
+		_exits(0);
+	}
+
+	/* original proc waits for notes */
+	notify(catcher);
+	w = nil;
+	for(;;) {
+		*notebuf = 0;
+		free(w);
+		w = wait();
+		if(w == nil) {
+			if(*notebuf == 0)
+				break;
+			np = mallocz(sizeof(Note), 1);
+			if(np != nil){
+				strcpy(np->msg, notebuf);
+				lock(&nfs);
+				if(nfs.nfirst == nil)
+					nfs.nfirst = np;
+				else
+					nfs.nlast->next = np;
+				nfs.nlast = np;
+				unlock(&nfs);
+				kick(pfd[0]);
+			}
+			unlock(&nfs);
+		} else if(w->pid == exportfspid)
+			break;
+	}
+
+	if(w == nil)
+		exits(nil);
+	exits(w->msg);
+}
blob - /dev/null
blob + df44b97da1a66add949dfc0478761c60fa4a2895 (mode 644)
--- /dev/null
+++ src/cmd/factotum/ctl.c
@@ -0,0 +1,158 @@
+#include "std.h"
+#include "dat.h"
+
+/*
+ *	key attr=val... - add a key
+ *		the attr=val pairs are protocol-specific.
+ *		for example, both of these are valid:
+ *			key p9sk1 gre cs.bell-labs.com mysecret
+ *			key p9sk1 gre cs.bell-labs.com 11223344556677 fmt=des7hex
+ *	delkey ... - delete a key
+ *		if given, the attr=val pairs are used to narrow the search
+ *		[maybe should require a password?]
+ *
+ *	debug - toggle debugging
+ */
+
+static char *msg[] = {
+	"key",
+	"delkey",
+	"debug",
+};
+
+static int
+classify(char *s)
+{
+	int i;
+
+	for(i=0; i<nelem(msg); i++)
+		if(strcmp(msg[i], s) == 0)
+			return i;
+	return -1;
+}
+
+int
+ctlwrite(char *a)
+{
+	char *p;
+	int i, nmatch, ret;
+	Attr *attr, **l, **lpriv, **lprotos, *pa, *priv, *protos;
+	Key *k;
+	Proto *proto;
+
+	if(a[0] == '#' || a[0] == '\0')
+		return 0;
+
+	/*
+	 * it would be nice to emit a warning of some sort here.
+	 * we ignore all but the first line of the write.  this helps
+	 * both with things like "echo delkey >/mnt/factotum/ctl"
+	 * and writes that (incorrectly) contain multiple key lines.
+	 */
+	if(p = strchr(a, '\n')){
+		if(p[1] != '\0'){
+			werrstr("multiline write not allowed");
+			return -1;
+		}
+		*p = '\0';
+	}
+
+	if((p = strchr(a, ' ')) == nil)
+		p = "";
+	else
+		*p++ = '\0';
+	switch(classify(a)){
+	default:
+		werrstr("unknown verb");
+		return -1;
+	case 0:	/* key */
+		attr = parseattr(p);
+		/* separate out proto= attributes */
+		lprotos = &protos;
+		for(l=&attr; (*l); ){
+			if(strcmp((*l)->name, "proto") == 0){
+				*lprotos = *l;
+				lprotos = &(*l)->next;
+				*l = (*l)->next;
+			}else
+				l = &(*l)->next;
+		}
+		*lprotos = nil;
+		if(protos == nil){
+			werrstr("key without protos");
+			freeattr(attr);
+			return -1;
+		}
+
+		/* separate out private attributes */
+		lpriv = &priv;
+		for(l=&attr; (*l); ){
+			if((*l)->name[0] == '!'){
+				*lpriv = *l;
+				lpriv = &(*l)->next;
+				*l = (*l)->next;
+			}else
+				l = &(*l)->next;
+		}
+		*lpriv = nil;
+
+		/* add keys */
+		ret = 0;
+		for(pa=protos; pa; pa=pa->next){
+			if((proto = protolookup(pa->val)) == nil){
+				werrstr("unknown proto %s", pa->val);
+				ret = -1;
+				continue;
+			}
+			if(proto->checkkey == nil){
+				werrstr("proto %s does not accept keys", proto->name);
+				ret = -1;
+				continue;
+			}
+			k = emalloc(sizeof(Key));
+			k->attr = mkattr(AttrNameval, "proto", proto->name, copyattr(attr));
+			k->privattr = copyattr(priv);
+			k->ref = 1;
+			k->proto = proto;
+			if((*proto->checkkey)(k) < 0){
+				ret = -1;
+				keyclose(k);
+				continue;
+			}
+			keyadd(k);
+			keyclose(k);
+		}
+		freeattr(attr);
+		freeattr(priv);
+		freeattr(protos);
+		return ret;
+	case 1:	/* delkey */
+		nmatch = 0;
+		attr = parseattr(p);
+		for(pa=attr; pa; pa=pa->next){
+			if(pa->type != AttrQuery && pa->name[0]=='!'){
+				werrstr("only !private? patterns are allowed for private fields");
+				freeattr(attr);
+				return -1;
+			}
+		}
+		for(i=0; i<ring.nkey; ){
+			if(matchattr(attr, ring.key[i]->attr, ring.key[i]->privattr)){
+				nmatch++;
+				keyclose(ring.key[i]);
+				ring.nkey--;
+				memmove(&ring.key[i], &ring.key[i+1], (ring.nkey-i)*sizeof(ring.key[0]));
+			}else
+				i++;
+		}
+		freeattr(attr);
+		if(nmatch == 0){
+			werrstr("found no keys to delete");
+			return -1;
+		}
+		return 0;
+	case 2:	/* debug */
+		debug ^= 1;
+		return 0;
+	}
+}
blob - /dev/null
blob + e0c5b4fb528ef058c637b0c9c4c3230289dd2d75 (mode 644)
--- /dev/null
+++ src/cmd/factotum/dat.h
@@ -0,0 +1,224 @@
+enum
+{
+	MaxRpc = 2048,	/* max size of any protocol message */
+
+	/* keep in sync with rpc.c:/rpcname */
+	RpcUnknown = 0,		/* Rpc.op */
+	RpcAuthinfo,
+	RpcAttr,
+	RpcRead,
+	RpcStart,
+	RpcWrite,
+
+	/* thread stack size */
+	STACK = 8192,
+};
+
+typedef struct Conv Conv;
+typedef struct Key Key;
+typedef struct Logbuf Logbuf;
+typedef struct Proto Proto;
+typedef struct Ring Ring;
+typedef struct Role Role;
+typedef struct Rpc Rpc;
+
+struct Rpc
+{
+	int op;
+	void *data;
+	int count;
+};
+
+struct Conv
+{
+	int ref;			/* ref count */
+	int hangup;		/* flag: please hang up */
+	int active;			/* flag: there is an active thread */
+	int done;			/* flag: conversation finished successfully */
+	ulong tag;			/* identifying tag */
+	Conv *next;		/* in linked list */
+	char *sysuser;		/* system name for user speaking to us */
+	char *state;		/* for debugging */
+	char statebuf[128];	/* for formatted states */
+	char err[ERRMAX];	/* last error */
+
+	Attr *attr;			/* current attributes */
+	Proto *proto;		/* protocol */
+
+	Channel *rpcwait;		/* wait here for an rpc */
+	Rpc rpc;				/* current rpc. op==RpcUnknown means none */
+	char rpcbuf[MaxRpc];	/* buffer for rpc */
+	char reply[MaxRpc];		/* buffer for response */
+	int nreply;				/* count of response */
+	void (*kickreply)(Conv*);	/* call to send response */
+	Req *req;				/* 9P call to read response */
+
+	Channel *keywait;	/* wait here for key confirmation */
+	
+};
+
+struct Key
+{
+	int ref;			/* ref count */
+	ulong tag;			/* identifying tag: sequence number */
+	Attr *attr;			/* public attributes */
+	Attr *privattr;		/* private attributes, like !password */
+	Proto *proto;		/* protocol owner of key */
+	void *priv;		/* protocol-specific storage */
+};
+
+struct Logbuf
+{
+	Req *wait;
+	Req **waitlast;
+	int rp;
+	int wp;
+	char *msg[128];
+};
+
+struct Ring
+{
+	Key **key;
+	int nkey;
+};
+
+struct Proto
+{
+	char *name;		/* name of protocol */
+	Role *roles;		/* list of roles and service functions */
+	char *keyprompt;	/* required attributes for key proto=name */
+	int (*checkkey)(Key*);	/* initialize k->priv or reject key */
+	void (*closekey)(Key*);	/* free k->priv */
+};
+
+struct Role
+{
+	char *name;		/* name of role */
+	int (*fn)(Conv*);	/* service function */
+};
+
+extern char	*authaddr;	/* plan9.c */
+extern int		*confirminuse;	/* fs.c */
+extern Conv*	conv;		/* conv.c */
+extern int		debug;		/* main.c */
+extern char	*factname;	/* main.c */
+extern Srv		fs;			/* fs.c */
+extern int		*needkeyinuse;	/* fs.c */
+extern char	*owner;		/* main.c */
+extern Proto	*prototab[];	/* main.c */
+extern Ring	ring;			/* key.c */
+extern char	*rpcname[];	/* rpc.c */
+
+extern char	Easproto[];	/* err.c */
+
+extern Proto	apop;		/* apop.c */
+extern Proto	chap;		/* chap.c */
+extern Proto	cram;		/* cram.c */
+extern Proto	mschap;		/* mschap.c */
+extern Proto	p9any;		/* p9any.c */
+extern Proto	p9sk1;		/* p9sk1.c */
+extern Proto	p9sk2;		/* p9sk2.c */
+
+/* provided by lib9p */
+#define emalloc	emalloc9p
+#define erealloc	erealloc9p
+#define estrdup	estrdup9p
+
+/* hidden in libauth */
+#define attrfmt		_attrfmt
+#define copyattr	_copyattr
+#define delattr		_delattr
+#define findattr		_findattr
+#define freeattr		_freeattr
+#define mkattr		_mkattr
+#define parseattr	_parseattr
+#define strfindattr	_strfindattr
+
+extern Attr*	addattr(Attr*, char*, ...);
+/* #pragma varargck argpos addattr 2 */
+extern Attr*	addattrs(Attr*, Attr*);
+extern Attr*	sortattr(Attr*);
+extern int		attrnamefmt(Fmt*);
+/* #pragma varargck type "N" Attr* */
+extern int		matchattr(Attr*, Attr*, Attr*);
+extern Attr*	parseattrfmt(char*, ...);
+/* #pragma varargck argpos parseattrfmt 1 */
+extern Attr*	parseattrfmtv(char*, va_list);
+
+extern void	confirmflush(Req*);
+extern void	confirmread(Req*);
+extern int		confirmwrite(char*);
+extern int		needkey(Conv*, Attr*);
+extern int		badkey(Conv*, Key*, char*, Attr*);
+extern int		confirmkey(Conv*, Key*);
+
+extern Conv*	convalloc(char*);
+extern void	convclose(Conv*);
+extern void	convhangup(Conv*);
+extern int		convneedkey(Conv*, Attr*);
+extern int		convbadkey(Conv*, Key*, char*, Attr*);
+extern int		convread(Conv*, void*, int);
+extern int		convreadm(Conv*, char**);
+extern int		convprint(Conv*, char*, ...);
+/* #pragma varargck argpos convprint 2 */
+extern int		convreadfn(Conv*, int(*)(void*, int), char**);
+extern void	convreset(Conv*);
+extern int		convwrite(Conv*, void*, int);
+
+extern int		ctlwrite(char*);
+
+extern char*	estrappend(char*, char*, ...);
+/* #pragma varargck argpos estrappend 2 */
+extern int		hexparse(char*, uchar*, int);
+
+extern void	keyadd(Key*);
+extern Key*	keylookup(char*, ...);
+/* #pragma varargck argpos keylookup 1 */
+extern Key*	keyfetch(Conv*, char*, ...);
+/* #pragma varargck argpos keyfetch 2 */
+extern void	keyclose(Key*);
+extern void	keyevict(Conv*, Key*, char*, ...);
+/* #pragma varargck argpos keyevict 3 */
+extern Key*	keyreplace(Conv*, Key*, char*, ...);
+/* #pragma varargck argpos keyreplace 3 */
+
+extern void	lbkick(Logbuf*);
+extern void	lbappend(Logbuf*, char*, ...);
+extern void	lbvappend(Logbuf*, char*, va_list);
+/* #pragma varargck argpos lbappend 2 */
+extern void	lbread(Logbuf*, Req*);
+extern void	lbflush(Logbuf*, Req*);
+extern void	flog(char*, ...);
+/* #pragma varargck argpos flog 1 */
+
+extern void	logflush(Req*);
+extern void	logread(Req*);
+extern void	logwrite(Req*);
+
+extern void	needkeyread(Req*);
+extern void	needkeyflush(Req*);
+extern int		needkeywrite(char*);
+extern int		needkeyqueue(void);
+
+extern Attr*	addcap(Attr*, char*, Ticket*);
+extern Key*	plan9authkey(Attr*);
+extern int		_authdial(char*, char*);
+
+extern int		memrandom(void*, int);
+
+extern Proto*	protolookup(char*);
+
+extern char*	readcons(char *prompt, char *def, int raw);
+
+extern int		rpcwrite(Conv*, void*, int);
+extern void	rpcrespond(Conv*, char*, ...);
+/* #pragma varargck argpos rpcrespond 2 */
+extern void	rpcrespondn(Conv*, char*, void*, int);
+extern void	rpcexec(Conv*);
+
+extern int		xioauthdial(char*, char*);
+extern void	xioclose(int);
+extern int		xiodial(char*, char*, char*, int*);
+extern int		xiowrite(int, void*, int);
+extern int		xioasrdresp(int, void*, int);
+extern int		xioasgetticket(int, char*, char*);
blob - /dev/null
blob + 47d2a4e17aeb1754c31a134722adbf4f85e074f6 (mode 644)
--- /dev/null
+++ src/cmd/factotum/fs.acid
@@ -0,0 +1,1686 @@
+sizeof_1_ = 8;
+aggr _1_
+{
+	'D' 0 lo;
+	'D' 4 hi;
+};
+
+defn
+_1_(addr) {
+	complex _1_ addr;
+	print("	lo	", addr.lo, "\n");
+	print("	hi	", addr.hi, "\n");
+};
+
+sizeofFPdbleword = 8;
+aggr FPdbleword
+{
+	'F' 0 x;
+	{
+	'D' 0 lo;
+	'D' 4 hi;
+	};
+};
+
+defn
+FPdbleword(addr) {
+	complex FPdbleword addr;
+	print("	x	", addr.x, "\n");
+	print("_1_ {\n");
+		_1_(addr+0);
+	print("}\n");
+};
+
+UTFmax = 3;
+Runesync = 128;
+Runeself = 128;
+Runeerror = 128;
+sizeofFconv = 24;
+aggr Fconv
+{
+	'X' 0 out;
+	'X' 4 eout;
+	'D' 8 f1;
+	'D' 12 f2;
+	'D' 16 f3;
+	'D' 20 chr;
+};
+
+defn
+Fconv(addr) {
+	complex Fconv addr;
+	print("	out	", addr.out\X, "\n");
+	print("	eout	", addr.eout\X, "\n");
+	print("	f1	", addr.f1, "\n");
+	print("	f2	", addr.f2, "\n");
+	print("	f3	", addr.f3, "\n");
+	print("	chr	", addr.chr, "\n");
+};
+
+sizeofTm = 40;
+aggr Tm
+{
+	'D' 0 sec;
+	'D' 4 min;
+	'D' 8 hour;
+	'D' 12 mday;
+	'D' 16 mon;
+	'D' 20 year;
+	'D' 24 wday;
+	'D' 28 yday;
+	'a' 32 zone;
+	'D' 36 tzoff;
+};
+
+defn
+Tm(addr) {
+	complex Tm addr;
+	print("	sec	", addr.sec, "\n");
+	print("	min	", addr.min, "\n");
+	print("	hour	", addr.hour, "\n");
+	print("	mday	", addr.mday, "\n");
+	print("	mon	", addr.mon, "\n");
+	print("	year	", addr.year, "\n");
+	print("	wday	", addr.wday, "\n");
+	print("	yday	", addr.yday, "\n");
+	print("	zone	", addr.zone, "\n");
+	print("	tzoff	", addr.tzoff, "\n");
+};
+
+PNPROC = 1;
+PNGROUP = 2;
+sizeofLock = 4;
+aggr Lock
+{
+	'D' 0 val;
+};
+
+defn
+Lock(addr) {
+	complex Lock addr;
+	print("	val	", addr.val, "\n");
+};
+
+sizeofQLp = 12;
+aggr QLp
+{
+	'D' 0 inuse;
+	'A' QLp 4 next;
+	'C' 8 state;
+};
+
+defn
+QLp(addr) {
+	complex QLp addr;
+	print("	inuse	", addr.inuse, "\n");
+	print("	next	", addr.next\X, "\n");
+	print("	state	", addr.state, "\n");
+};
+
+sizeofQLock = 16;
+aggr QLock
+{
+	Lock 0 lock;
+	'D' 4 locked;
+	'A' QLp 8 $head;
+	'A' QLp 12 $tail;
+};
+
+defn
+QLock(addr) {
+	complex QLock addr;
+	print("Lock lock {\n");
+	Lock(addr.lock);
+	print("}\n");
+	print("	locked	", addr.locked, "\n");
+	print("	$head	", addr.$head\X, "\n");
+	print("	$tail	", addr.$tail\X, "\n");
+};
+
+sizeofRWLock = 20;
+aggr RWLock
+{
+	Lock 0 lock;
+	'D' 4 readers;
+	'D' 8 writer;
+	'A' QLp 12 $head;
+	'A' QLp 16 $tail;
+};
+
+defn
+RWLock(addr) {
+	complex RWLock addr;
+	print("Lock lock {\n");
+	Lock(addr.lock);
+	print("}\n");
+	print("	readers	", addr.readers, "\n");
+	print("	writer	", addr.writer, "\n");
+	print("	$head	", addr.$head\X, "\n");
+	print("	$tail	", addr.$tail\X, "\n");
+};
+
+RFNAMEG = 1;
+RFENVG = 2;
+RFFDG = 4;
+RFNOTEG = 8;
+RFPROC = 16;
+RFMEM = 32;
+RFNOWAIT = 64;
+RFCNAMEG = 1024;
+RFCENVG = 2048;
+RFCFDG = 4096;
+RFREND = 8192;
+RFNOMNT = 16384;
+sizeofQid = 16;
+aggr Qid
+{
+	'W' 0 path;
+	'U' 8 vers;
+	'b' 12 type;
+};
+
+defn
+Qid(addr) {
+	complex Qid addr;
+	print("	path	", addr.path, "\n");
+	print("	vers	", addr.vers, "\n");
+	print("	type	", addr.type, "\n");
+};
+
+sizeofDir = 60;
+aggr Dir
+{
+	'u' 0 type;
+	'U' 4 dev;
+	Qid 8 qid;
+	'U' 24 mode;
+	'U' 28 atime;
+	'U' 32 mtime;
+	'V' 36 length;
+	'X' 44 name;
+	'X' 48 uid;
+	'X' 52 gid;
+	'X' 56 muid;
+};
+
+defn
+Dir(addr) {
+	complex Dir addr;
+	print("	type	", addr.type, "\n");
+	print("	dev	", addr.dev, "\n");
+	print("Qid qid {\n");
+	Qid(addr.qid);
+	print("}\n");
+	print("	mode	", addr.mode, "\n");
+	print("	atime	", addr.atime, "\n");
+	print("	mtime	", addr.mtime, "\n");
+	print("	length	", addr.length, "\n");
+	print("	name	", addr.name\X, "\n");
+	print("	uid	", addr.uid\X, "\n");
+	print("	gid	", addr.gid\X, "\n");
+	print("	muid	", addr.muid\X, "\n");
+};
+
+sizeofWaitmsg = 20;
+aggr Waitmsg
+{
+	'D' 0 pid;
+	'a' 4 time;
+	'X' 16 msg;
+};
+
+defn
+Waitmsg(addr) {
+	complex Waitmsg addr;
+	print("	pid	", addr.pid, "\n");
+	print("	time	", addr.time, "\n");
+	print("	msg	", addr.msg\X, "\n");
+};
+
+sizeofIOchunk = 8;
+aggr IOchunk
+{
+	'X' 0 addr;
+	'U' 4 len;
+};
+
+defn
+IOchunk(addr) {
+	complex IOchunk addr;
+	print("	addr	", addr.addr\X, "\n");
+	print("	len	", addr.len, "\n");
+};
+
+MAXCHLEN = 256;
+MAXNAMELEN = 256;
+MD5LEN = 16;
+ARok = 0;
+ARdone = 1;
+ARerror = 2;
+ARneedkey = 3;
+ARbadkey = 4;
+ARwritenext = 5;
+ARtoosmall = 6;
+ARtoobig = 7;
+ARrpcfailure = 8;
+ARphase = 9;
+AuthRpcMax = 4096;
+sizeofAuthRpc = 8208;
+aggr AuthRpc
+{
+	'D' 0 afd;
+	'X' 4 verb;
+	'a' 8 ibuf;
+	'a' 4104 obuf;
+	'X' 8200 arg;
+	'U' 8204 narg;
+};
+
+defn
+AuthRpc(addr) {
+	complex AuthRpc addr;
+	print("	afd	", addr.afd, "\n");
+	print("	verb	", addr.verb\X, "\n");
+	print("	ibuf	", addr.ibuf, "\n");
+	print("	obuf	", addr.obuf, "\n");
+	print("	arg	", addr.arg\X, "\n");
+	print("	narg	", addr.narg, "\n");
+};
+
+sizeofAuthInfo = 20;
+aggr AuthInfo
+{
+	'X' 0 cuid;
+	'X' 4 suid;
+	'X' 8 cap;
+	'D' 12 nsecret;
+	'X' 16 secret;
+};
+
+defn
+AuthInfo(addr) {
+	complex AuthInfo addr;
+	print("	cuid	", addr.cuid\X, "\n");
+	print("	suid	", addr.suid\X, "\n");
+	print("	cap	", addr.cap\X, "\n");
+	print("	nsecret	", addr.nsecret, "\n");
+	print("	secret	", addr.secret\X, "\n");
+};
+
+sizeofChalstate = 540;
+aggr Chalstate
+{
+	'X' 0 user;
+	'a' 4 chal;
+	'D' 260 nchal;
+	'X' 264 resp;
+	'D' 268 nresp;
+	'D' 272 afd;
+	'A' AuthRpc 276 rpc;
+	'a' 280 userbuf;
+	'D' 536 userinchal;
+};
+
+defn
+Chalstate(addr) {
+	complex Chalstate addr;
+	print("	user	", addr.user\X, "\n");
+	print("	chal	", addr.chal, "\n");
+	print("	nchal	", addr.nchal, "\n");
+	print("	resp	", addr.resp\X, "\n");
+	print("	nresp	", addr.nresp, "\n");
+	print("	afd	", addr.afd, "\n");
+	print("	rpc	", addr.rpc\X, "\n");
+	print("	userbuf	", addr.userbuf, "\n");
+	print("	userinchal	", addr.userinchal, "\n");
+};
+
+sizeofChapreply = 20;
+aggr Chapreply
+{
+	'b' 0 id;
+	'a' 1 resp;
+};
+
+defn
+Chapreply(addr) {
+	complex Chapreply addr;
+	print("	id	", addr.id, "\n");
+	print("	resp	", addr.resp, "\n");
+};
+
+sizeofMSchapreply = 48;
+aggr MSchapreply
+{
+	'a' 0 LMresp;
+	'a' 24 NTresp;
+};
+
+defn
+MSchapreply(addr) {
+	complex MSchapreply addr;
+	print("	LMresp	", addr.LMresp, "\n");
+	print("	NTresp	", addr.NTresp, "\n");
+};
+
+sizeofUserPasswd = 8;
+aggr UserPasswd
+{
+	'X' 0 user;
+	'X' 4 passwd;
+};
+
+defn
+UserPasswd(addr) {
+	complex UserPasswd addr;
+	print("	user	", addr.user\X, "\n");
+	print("	passwd	", addr.passwd\X, "\n");
+};
+
+ANAMELEN = 28;
+AERRLEN = 64;
+DOMLEN = 48;
+DESKEYLEN = 7;
+CHALLEN = 8;
+NETCHLEN = 16;
+CONFIGLEN = 14;
+SECRETLEN = 32;
+KEYDBOFF = 8;
+OKEYDBLEN = 41;
+KEYDBLEN = 73;
+OMD5LEN = 16;
+AuthTreq = 1;
+AuthChal = 2;
+AuthPass = 3;
+AuthOK = 4;
+AuthErr = 5;
+AuthMod = 6;
+AuthApop = 7;
+AuthOKvar = 9;
+AuthChap = 10;
+AuthMSchap = 11;
+AuthCram = 12;
+AuthHttp = 13;
+AuthVNC = 14;
+AuthTs = 64;
+AuthTc = 65;
+AuthAs = 66;
+AuthAc = 67;
+AuthTp = 68;
+AuthHr = 69;
+sizeofTicketreq = 144;
+aggr Ticketreq
+{
+	'C' 0 type;
+	'a' 1 authid;
+	'a' 29 authdom;
+	'a' 77 chal;
+	'a' 85 hostid;
+	'a' 113 uid;
+};
+
+defn
+Ticketreq(addr) {
+	complex Ticketreq addr;
+	print("	type	", addr.type, "\n");
+	print("	authid	", addr.authid, "\n");
+	print("	authdom	", addr.authdom, "\n");
+	print("	chal	", addr.chal, "\n");
+	print("	hostid	", addr.hostid, "\n");
+	print("	uid	", addr.uid, "\n");
+};
+
+sizeofTicket = 72;
+aggr Ticket
+{
+	'C' 0 num;
+	'a' 1 chal;
+	'a' 9 cuid;
+	'a' 37 suid;
+	'a' 65 key;
+};
+
+defn
+Ticket(addr) {
+	complex Ticket addr;
+	print("	num	", addr.num, "\n");
+	print("	chal	", addr.chal, "\n");
+	print("	cuid	", addr.cuid, "\n");
+	print("	suid	", addr.suid, "\n");
+	print("	key	", addr.key, "\n");
+};
+
+sizeofAuthenticator = 16;
+aggr Authenticator
+{
+	'C' 0 num;
+	'a' 1 chal;
+	'U' 12 id;
+};
+
+defn
+Authenticator(addr) {
+	complex Authenticator addr;
+	print("	num	", addr.num, "\n");
+	print("	chal	", addr.chal, "\n");
+	print("	id	", addr.id, "\n");
+};
+
+sizeofPasswordreq = 92;
+aggr Passwordreq
+{
+	'C' 0 num;
+	'a' 1 old;
+	'a' 29 new;
+	'C' 57 changesecret;
+	'a' 58 secret;
+};
+
+defn
+Passwordreq(addr) {
+	complex Passwordreq addr;
+	print("	num	", addr.num, "\n");
+	print("	old	", addr.old, "\n");
+	print("	new	", addr.new, "\n");
+	print("	changesecret	", addr.changesecret, "\n");
+	print("	secret	", addr.secret, "\n");
+};
+
+sizeofOChapreply = 48;
+aggr OChapreply
+{
+	'b' 0 id;
+	'a' 1 uid;
+	'a' 29 resp;
+};
+
+defn
+OChapreply(addr) {
+	complex OChapreply addr;
+	print("	id	", addr.id, "\n");
+	print("	uid	", addr.uid, "\n");
+	print("	resp	", addr.resp, "\n");
+};
+
+sizeofOMSchapreply = 76;
+aggr OMSchapreply
+{
+	'a' 0 uid;
+	'a' 28 LMresp;
+	'a' 52 NTresp;
+};
+
+defn
+OMSchapreply(addr) {
+	complex OMSchapreply addr;
+	print("	uid	", addr.uid, "\n");
+	print("	LMresp	", addr.LMresp, "\n");
+	print("	NTresp	", addr.NTresp, "\n");
+};
+
+NVwrite = 1;
+NVwriteonerr = 2;
+sizeofNvrsafe = 112;
+aggr Nvrsafe
+{
+	'a' 0 machkey;
+	'b' 7 machsum;
+	'a' 8 authkey;
+	'b' 15 authsum;
+	'a' 16 config;
+	'b' 30 configsum;
+	'a' 31 authid;
+	'b' 59 authidsum;
+	'a' 60 authdom;
+	'b' 108 authdomsum;
+};
+
+defn
+Nvrsafe(addr) {
+	complex Nvrsafe addr;
+	print("	machkey	", addr.machkey, "\n");
+	print("	machsum	", addr.machsum, "\n");
+	print("	authkey	", addr.authkey, "\n");
+	print("	authsum	", addr.authsum, "\n");
+	print("	config	", addr.config, "\n");
+	print("	configsum	", addr.configsum, "\n");
+	print("	authid	", addr.authid, "\n");
+	print("	authidsum	", addr.authidsum, "\n");
+	print("	authdom	", addr.authdom, "\n");
+	print("	authdomsum	", addr.authdomsum, "\n");
+};
+
+AESbsize = 16;
+AESmaxkey = 32;
+AESmaxrounds = 14;
+sizeofAESstate = 540;
+aggr AESstate
+{
+	'U' 0 setup;
+	'D' 4 rounds;
+	'D' 8 keybytes;
+	'a' 12 key;
+	'a' 44 ekey;
+	'a' 284 dkey;
+	'a' 524 ivec;
+};
+
+defn
+AESstate(addr) {
+	complex AESstate addr;
+	print("	setup	", addr.setup, "\n");
+	print("	rounds	", addr.rounds, "\n");
+	print("	keybytes	", addr.keybytes, "\n");
+	print("	key	", addr.key, "\n");
+	print("	ekey	", addr.ekey, "\n");
+	print("	dkey	", addr.dkey, "\n");
+	print("	ivec	", addr.ivec, "\n");
+};
+
+BFbsize = 8;
+BFrounds = 16;
+sizeofBFstate = 4236;
+aggr BFstate
+{
+	'U' 0 setup;
+	'a' 4 key;
+	'a' 60 ivec;
+	'a' 68 pbox;
+	'a' 140 sbox;
+};
+
+defn
+BFstate(addr) {
+	complex BFstate addr;
+	print("	setup	", addr.setup, "\n");
+	print("	key	", addr.key, "\n");
+	print("	ivec	", addr.ivec, "\n");
+	print("	pbox	", addr.pbox, "\n");
+	print("	sbox	", addr.sbox, "\n");
+};
+
+DESbsize = 8;
+sizeofDESstate = 148;
+aggr DESstate
+{
+	'U' 0 setup;
+	'a' 4 key;
+	'a' 12 expanded;
+	'a' 140 ivec;
+};
+
+defn
+DESstate(addr) {
+	complex DESstate addr;
+	print("	setup	", addr.setup, "\n");
+	print("	key	", addr.key, "\n");
+	print("	expanded	", addr.expanded, "\n");
+	print("	ivec	", addr.ivec, "\n");
+};
+
+DES3E = 0;
+DES3D = 1;
+DES3EEE = 0;
+DES3EDE = 2;
+DES3DED = 5;
+DES3DDD = 7;
+sizeofDES3state = 420;
+aggr DES3state
+{
+	'U' 0 setup;
+	'a' 4 key;
+	'a' 28 expanded;
+	'a' 412 ivec;
+};
+
+defn
+DES3state(addr) {
+	complex DES3state addr;
+	print("	setup	", addr.setup, "\n");
+	print("	key	", addr.key, "\n");
+	print("	expanded	", addr.expanded, "\n");
+	print("	ivec	", addr.ivec, "\n");
+};
+
+SHA1dlen = 20;
+MD4dlen = 16;
+MD5dlen = 16;
+sizeofDigestState = 160;
+aggr DigestState
+{
+	'U' 0 len;
+	'a' 4 state;
+	'a' 24 buf;
+	'D' 152 blen;
+	'C' 156 malloced;
+	'C' 157 seeded;
+};
+
+defn
+DigestState(addr) {
+	complex DigestState addr;
+	print("	len	", addr.len, "\n");
+	print("	state	", addr.state, "\n");
+	print("	buf	", addr.buf, "\n");
+	print("	blen	", addr.blen, "\n");
+	print("	malloced	", addr.malloced, "\n");
+	print("	seeded	", addr.seeded, "\n");
+};
+
+sizeofRC4state = 260;
+aggr RC4state
+{
+	'a' 0 state;
+	'b' 256 x;
+	'b' 257 y;
+};
+
+defn
+RC4state(addr) {
+	complex RC4state addr;
+	print("	state	", addr.state, "\n");
+	print("	x	", addr.x, "\n");
+	print("	y	", addr.y, "\n");
+};
+
+sizeofRSApub = 8;
+aggr RSApub
+{
+	'X' 0 n;
+	'X' 4 ek;
+};
+
+defn
+RSApub(addr) {
+	complex RSApub addr;
+	print("	n	", addr.n\X, "\n");
+	print("	ek	", addr.ek\X, "\n");
+};
+
+sizeofRSApriv = 32;
+aggr RSApriv
+{
+	RSApub 0 pub;
+	'X' 8 dk;
+	'X' 12 p;
+	'X' 16 q;
+	'X' 20 kp;
+	'X' 24 kq;
+	'X' 28 c2;
+};
+
+defn
+RSApriv(addr) {
+	complex RSApriv addr;
+	print("RSApub pub {\n");
+	RSApub(addr.pub);
+	print("}\n");
+	print("	dk	", addr.dk\X, "\n");
+	print("	p	", addr.p\X, "\n");
+	print("	q	", addr.q\X, "\n");
+	print("	kp	", addr.kp\X, "\n");
+	print("	kq	", addr.kq\X, "\n");
+	print("	c2	", addr.c2\X, "\n");
+};
+
+sizeofEGpub = 12;
+aggr EGpub
+{
+	'X' 0 p;
+	'X' 4 alpha;
+	'X' 8 key;
+};
+
+defn
+EGpub(addr) {
+	complex EGpub addr;
+	print("	p	", addr.p\X, "\n");
+	print("	alpha	", addr.alpha\X, "\n");
+	print("	key	", addr.key\X, "\n");
+};
+
+sizeofEGpriv = 16;
+aggr EGpriv
+{
+	EGpub 0 pub;
+	'X' 12 secret;
+};
+
+defn
+EGpriv(addr) {
+	complex EGpriv addr;
+	print("EGpub pub {\n");
+	EGpub(addr.pub);
+	print("}\n");
+	print("	secret	", addr.secret\X, "\n");
+};
+
+sizeofEGsig = 8;
+aggr EGsig
+{
+	'X' 0 r;
+	'X' 4 s;
+};
+
+defn
+EGsig(addr) {
+	complex EGsig addr;
+	print("	r	", addr.r\X, "\n");
+	print("	s	", addr.s\X, "\n");
+};
+
+sizeofString = 20;
+aggr String
+{
+	{
+	'D' 0 val;
+	};
+	'X' 4 base;
+	'X' 8 end;
+	'X' 12 ptr;
+	'd' 16 ref;
+	'b' 18 fixed;
+};
+
+defn
+String(addr) {
+	complex String addr;
+	print("Lock {\n");
+		Lock(addr+0);
+	print("}\n");
+	print("	base	", addr.base\X, "\n");
+	print("	end	", addr.end\X, "\n");
+	print("	ptr	", addr.ptr\X, "\n");
+	print("	ref	", addr.ref, "\n");
+	print("	fixed	", addr.fixed, "\n");
+};
+
+sizeofChannel = 156;
+aggr Channel
+{
+	'D' 0 s;
+	'U' 4 f;
+	'U' 8 n;
+	'D' 12 e;
+	'D' 16 freed;
+	'U' 20 qused;
+	'a' 24 qentry;
+	'a' 152 v;
+};
+
+defn
+Channel(addr) {
+	complex Channel addr;
+	print("	s	", addr.s, "\n");
+	print("	f	", addr.f, "\n");
+	print("	n	", addr.n, "\n");
+	print("	e	", addr.e, "\n");
+	print("	freed	", addr.freed, "\n");
+	print("	qused	", addr.qused, "\n");
+	print("	qentry	", addr.qentry, "\n");
+	print("	v	", addr.v, "\n");
+};
+
+sizeofAlt = 20;
+aggr Alt
+{
+	'A' Channel 0 c;
+	'X' 4 v;
+	'D' 8 op;
+	'A' Channel 12 tag;
+	'U' 16 q;
+};
+
+defn
+Alt(addr) {
+	complex Alt addr;
+	print("	c	", addr.c\X, "\n");
+	print("	v	", addr.v\X, "\n");
+	print("	op	", addr.op, "\n");
+	print("	tag	", addr.tag\X, "\n");
+	print("	q	", addr.q, "\n");
+};
+
+sizeofRef = 4;
+aggr Ref
+{
+	'D' 0 ref;
+};
+
+defn
+Ref(addr) {
+	complex Ref addr;
+	print("	ref	", addr.ref, "\n");
+};
+
+sizeof_2_ = 8;
+aggr _2_
+{
+	'U' 0 msize;
+	'X' 4 version;
+};
+
+defn
+_2_(addr) {
+	complex _2_ addr;
+	print("	msize	", addr.msize, "\n");
+	print("	version	", addr.version\X, "\n");
+};
+
+sizeof_3_ = 4;
+aggr _3_
+{
+	'u' 0 oldtag;
+};
+
+defn
+_3_(addr) {
+	complex _3_ addr;
+	print("	oldtag	", addr.oldtag, "\n");
+};
+
+sizeof_4_ = 4;
+aggr _4_
+{
+	'X' 0 ename;
+};
+
+defn
+_4_(addr) {
+	complex _4_ addr;
+	print("	ename	", addr.ename\X, "\n");
+};
+
+sizeof_5_ = 20;
+aggr _5_
+{
+	Qid 0 qid;
+	'U' 16 iounit;
+};
+
+defn
+_5_(addr) {
+	complex _5_ addr;
+	print("Qid qid {\n");
+	Qid(addr.qid);
+	print("}\n");
+	print("	iounit	", addr.iounit, "\n");
+};
+
+sizeof_6_ = 16;
+aggr _6_
+{
+	Qid 0 aqid;
+};
+
+defn
+_6_(addr) {
+	complex _6_ addr;
+	print("Qid aqid {\n");
+	Qid(addr.aqid);
+	print("}\n");
+};
+
+sizeof_7_ = 12;
+aggr _7_
+{
+	'U' 0 afid;
+	'X' 4 uname;
+	'X' 8 aname;
+};
+
+defn
+_7_(addr) {
+	complex _7_ addr;
+	print("	afid	", addr.afid, "\n");
+	print("	uname	", addr.uname\X, "\n");
+	print("	aname	", addr.aname\X, "\n");
+};
+
+sizeof_8_ = 12;
+aggr _8_
+{
+	'U' 0 perm;
+	'X' 4 name;
+	'b' 8 mode;
+};
+
+defn
+_8_(addr) {
+	complex _8_ addr;
+	print("	perm	", addr.perm, "\n");
+	print("	name	", addr.name\X, "\n");
+	print("	mode	", addr.mode, "\n");
+};
+
+sizeof_9_ = 72;
+aggr _9_
+{
+	'U' 0 newfid;
+	'u' 4 nwname;
+	'a' 8 wname;
+};
+
+defn
+_9_(addr) {
+	complex _9_ addr;
+	print("	newfid	", addr.newfid, "\n");
+	print("	nwname	", addr.nwname, "\n");
+	print("	wname	", addr.wname, "\n");
+};
+
+sizeof_10_ = 260;
+aggr _10_
+{
+	'u' 0 nwqid;
+	'a' 4 wqid;
+};
+
+defn
+_10_(addr) {
+	complex _10_ addr;
+	print("	nwqid	", addr.nwqid, "\n");
+	print("	wqid	", addr.wqid, "\n");
+};
+
+sizeof_11_ = 16;
+aggr _11_
+{
+	'V' 0 offset;
+	'U' 8 count;
+	'X' 12 data;
+};
+
+defn
+_11_(addr) {
+	complex _11_ addr;
+	print("	offset	", addr.offset, "\n");
+	print("	count	", addr.count, "\n");
+	print("	data	", addr.data\X, "\n");
+};
+
+sizeof_12_ = 8;
+aggr _12_
+{
+	'u' 0 nstat;
+	'X' 4 stat;
+};
+
+defn
+_12_(addr) {
+	complex _12_ addr;
+	print("	nstat	", addr.nstat, "\n");
+	print("	stat	", addr.stat\X, "\n");
+};
+
+sizeof_13_ = 260;
+aggr _13_
+{
+	{
+	'U' 0 msize;
+	'X' 4 version;
+	};
+	{
+	'u' 0 oldtag;
+	};
+	{
+	'X' 0 ename;
+	};
+	{
+	Qid 0 qid;
+	'U' 16 iounit;
+	};
+	{
+	Qid 0 aqid;
+	};
+	{
+	'U' 0 afid;
+	'X' 4 uname;
+	'X' 8 aname;
+	};
+	{
+	'U' 0 perm;
+	'X' 4 name;
+	'b' 8 mode;
+	};
+	{
+	'U' 0 newfid;
+	'u' 4 nwname;
+	'a' 8 wname;
+	};
+	{
+	'u' 0 nwqid;
+	'a' 4 wqid;
+	};
+	{
+	'V' 0 offset;
+	'U' 8 count;
+	'X' 12 data;
+	};
+	{
+	'u' 0 nstat;
+	'X' 4 stat;
+	};
+};
+
+defn
+_13_(addr) {
+	complex _13_ addr;
+	print("_2_ {\n");
+		_2_(addr+0);
+	print("}\n");
+	print("_3_ {\n");
+		_3_(addr+0);
+	print("}\n");
+	print("_4_ {\n");
+		_4_(addr+0);
+	print("}\n");
+	print("_5_ {\n");
+		_5_(addr+0);
+	print("}\n");
+	print("_6_ {\n");
+		_6_(addr+0);
+	print("}\n");
+	print("_7_ {\n");
+		_7_(addr+0);
+	print("}\n");
+	print("_8_ {\n");
+		_8_(addr+0);
+	print("}\n");
+	print("_9_ {\n");
+		_9_(addr+0);
+	print("}\n");
+	print("_10_ {\n");
+		_10_(addr+0);
+	print("}\n");
+	print("_11_ {\n");
+		_11_(addr+0);
+	print("}\n");
+	print("_12_ {\n");
+		_12_(addr+0);
+	print("}\n");
+};
+
+sizeofFcall = 272;
+aggr Fcall
+{
+	'b' 0 type;
+	'U' 4 fid;
+	'u' 8 tag;
+	{
+	{
+	'U' 12 msize;
+	'X' 16 version;
+	};
+	{
+	'u' 12 oldtag;
+	};
+	{
+	'X' 12 ename;
+	};
+	{
+	Qid 12 qid;
+	'U' 28 iounit;
+	};
+	{
+	Qid 12 aqid;
+	};
+	{
+	'U' 12 afid;
+	'X' 16 uname;
+	'X' 20 aname;
+	};
+	{
+	'U' 12 perm;
+	'X' 16 name;
+	'b' 20 mode;
+	};
+	{
+	'U' 12 newfid;
+	'u' 16 nwname;
+	'a' 20 wname;
+	};
+	{
+	'u' 12 nwqid;
+	'a' 16 wqid;
+	};
+	{
+	'V' 12 offset;
+	'U' 20 count;
+	'X' 24 data;
+	};
+	{
+	'u' 12 nstat;
+	'X' 16 stat;
+	};
+	};
+};
+
+defn
+Fcall(addr) {
+	complex Fcall addr;
+	print("	type	", addr.type, "\n");
+	print("	fid	", addr.fid, "\n");
+	print("	tag	", addr.tag, "\n");
+	print("_13_ {\n");
+		_13_(addr+12);
+	print("}\n");
+};
+
+Tversion = 100;
+Rversion = 101;
+Tauth = 102;
+Rauth = 103;
+Tattach = 104;
+Rattach = 105;
+Terror = 106;
+Rerror = 107;
+Tflush = 108;
+Rflush = 109;
+Twalk = 110;
+Rwalk = 111;
+Topen = 112;
+Ropen = 113;
+Tcreate = 114;
+Rcreate = 115;
+Tread = 116;
+Rread = 117;
+Twrite = 118;
+Rwrite = 119;
+Tclunk = 120;
+Rclunk = 121;
+Tremove = 122;
+Rremove = 123;
+Tstat = 124;
+Rstat = 125;
+Twstat = 126;
+Rwstat = 127;
+Tmax = 128;
+sizeofFid = 60;
+aggr Fid
+{
+	'U' 0 fid;
+	'C' 4 omode;
+	'X' 8 file;
+	'X' 12 uid;
+	Qid 16 qid;
+	'X' 32 aux;
+	'X' 36 rdir;
+	Ref 40 ref;
+	'X' 44 pool;
+	'V' 48 diroffset;
+	'D' 56 dirindex;
+};
+
+defn
+Fid(addr) {
+	complex Fid addr;
+	print("	fid	", addr.fid, "\n");
+	print("	omode	", addr.omode, "\n");
+	print("	file	", addr.file\X, "\n");
+	print("	uid	", addr.uid\X, "\n");
+	print("Qid qid {\n");
+	Qid(addr.qid);
+	print("}\n");
+	print("	aux	", addr.aux\X, "\n");
+	print("	rdir	", addr.rdir\X, "\n");
+	print("Ref ref {\n");
+	Ref(addr.ref);
+	print("}\n");
+	print("	pool	", addr.pool\X, "\n");
+	print("	diroffset	", addr.diroffset, "\n");
+	print("	dirindex	", addr.dirindex, "\n");
+};
+
+sizeofReq = 656;
+aggr Req
+{
+	'U' 0 tag;
+	'X' 4 aux;
+	Fcall 8 ifcall;
+	Fcall 280 ofcall;
+	Dir 552 d;
+	'A' Req 612 oldreq;
+	'A' Fid 616 fid;
+	'A' Fid 620 afid;
+	'A' Fid 624 newfid;
+	'X' 628 srv;
+	Ref 632 ref;
+	'X' 636 pool;
+	'X' 640 buf;
+	'b' 644 type;
+	'b' 645 responded;
+	'X' 648 error;
+	'X' 652 rbuf;
+};
+
+defn
+Req(addr) {
+	complex Req addr;
+	print("	tag	", addr.tag, "\n");
+	print("	aux	", addr.aux\X, "\n");
+	print("Fcall ifcall {\n");
+	Fcall(addr.ifcall);
+	print("}\n");
+	print("Fcall ofcall {\n");
+	Fcall(addr.ofcall);
+	print("}\n");
+	print("Dir d {\n");
+	Dir(addr.d);
+	print("}\n");
+	print("	oldreq	", addr.oldreq\X, "\n");
+	print("	fid	", addr.fid\X, "\n");
+	print("	afid	", addr.afid\X, "\n");
+	print("	newfid	", addr.newfid\X, "\n");
+	print("	srv	", addr.srv\X, "\n");
+	print("Ref ref {\n");
+	Ref(addr.ref);
+	print("}\n");
+	print("	pool	", addr.pool\X, "\n");
+	print("	buf	", addr.buf\X, "\n");
+	print("	type	", addr.type, "\n");
+	print("	responded	", addr.responded, "\n");
+	print("	error	", addr.error\X, "\n");
+	print("	rbuf	", addr.rbuf\X, "\n");
+};
+
+sizeofFidpool = 12;
+aggr Fidpool
+{
+	'X' 0 map;
+	'X' 4 destroy;
+	'X' 8 srv;
+};
+
+defn
+Fidpool(addr) {
+	complex Fidpool addr;
+	print("	map	", addr.map\X, "\n");
+	print("	destroy	", addr.destroy\X, "\n");
+	print("	srv	", addr.srv\X, "\n");
+};
+
+sizeofReqpool = 12;
+aggr Reqpool
+{
+	'X' 0 map;
+	'X' 4 destroy;
+	'X' 8 srv;
+};
+
+defn
+Reqpool(addr) {
+	complex Reqpool addr;
+	print("	map	", addr.map\X, "\n");
+	print("	destroy	", addr.destroy\X, "\n");
+	print("	srv	", addr.srv\X, "\n");
+};
+
+sizeofFile = 108;
+aggr File
+{
+	{
+	'D' 0 ref;
+	};
+	{
+	'u' 4 type;
+	'U' 8 dev;
+	Qid 12 qid;
+	'U' 28 mode;
+	'U' 32 atime;
+	'U' 36 mtime;
+	'V' 40 length;
+	'X' 48 name;
+	'X' 52 uid;
+	'X' 56 gid;
+	'X' 60 muid;
+	};
+	'A' File 64 parent;
+	'X' 68 aux;
+	{
+	Lock 72 lock;
+	'D' 76 readers;
+	'D' 80 writer;
+	'A' QLp 84 $head;
+	'A' QLp 88 $tail;
+	};
+	'X' 92 filelist;
+	'X' 96 tree;
+	'D' 100 nchild;
+	'D' 104 allocd;
+};
+
+defn
+File(addr) {
+	complex File addr;
+	print("Ref {\n");
+		Ref(addr+0);
+	print("}\n");
+	print("Dir {\n");
+		Dir(addr+4);
+	print("}\n");
+	print("	parent	", addr.parent\X, "\n");
+	print("	aux	", addr.aux\X, "\n");
+	print("RWLock {\n");
+		RWLock(addr+72);
+	print("}\n");
+	print("	filelist	", addr.filelist\X, "\n");
+	print("	tree	", addr.tree\X, "\n");
+	print("	nchild	", addr.nchild, "\n");
+	print("	allocd	", addr.allocd, "\n");
+};
+
+sizeofTree = 20;
+aggr Tree
+{
+	'A' File 0 root;
+	'X' 4 destroy;
+	Lock 8 genlock;
+	'U' 12 qidgen;
+	'U' 16 dirqidgen;
+};
+
+defn
+Tree(addr) {
+	complex Tree addr;
+	print("	root	", addr.root\X, "\n");
+	print("	destroy	", addr.destroy\X, "\n");
+	print("Lock genlock {\n");
+	Lock(addr.genlock);
+	print("}\n");
+	print("	qidgen	", addr.qidgen, "\n");
+	print("	dirqidgen	", addr.dirqidgen, "\n");
+};
+
+sizeofSrv = 136;
+aggr Srv
+{
+	'A' Tree 0 tree;
+	'X' 4 destroyfid;
+	'X' 8 destroyreq;
+	'X' 12 end;
+	'X' 16 aux;
+	'X' 20 attach;
+	'X' 24 auth;
+	'X' 28 open;
+	'X' 32 create;
+	'X' 36 read;
+	'X' 40 write;
+	'X' 44 remove;
+	'X' 48 flush;
+	'X' 52 stat;
+	'X' 56 wstat;
+	'X' 60 walk;
+	'X' 64 clone;
+	'X' 68 walk1;
+	'D' 72 infd;
+	'D' 76 outfd;
+	'D' 80 nopipe;
+	'A' Fidpool 84 fpool;
+	'A' Reqpool 88 rpool;
+	'U' 92 msize;
+	'X' 96 rbuf;
+	QLock 100 rlock;
+	'X' 116 wbuf;
+	QLock 120 wlock;
+};
+
+defn
+Srv(addr) {
+	complex Srv addr;
+	print("	tree	", addr.tree\X, "\n");
+	print("	destroyfid	", addr.destroyfid\X, "\n");
+	print("	destroyreq	", addr.destroyreq\X, "\n");
+	print("	end	", addr.end\X, "\n");
+	print("	aux	", addr.aux\X, "\n");
+	print("	attach	", addr.attach\X, "\n");
+	print("	auth	", addr.auth\X, "\n");
+	print("	open	", addr.open\X, "\n");
+	print("	create	", addr.create\X, "\n");
+	print("	read	", addr.read\X, "\n");
+	print("	write	", addr.write\X, "\n");
+	print("	remove	", addr.remove\X, "\n");
+	print("	flush	", addr.flush\X, "\n");
+	print("	stat	", addr.stat\X, "\n");
+	print("	wstat	", addr.wstat\X, "\n");
+	print("	walk	", addr.walk\X, "\n");
+	print("	clone	", addr.clone\X, "\n");
+	print("	walk1	", addr.walk1\X, "\n");
+	print("	infd	", addr.infd, "\n");
+	print("	outfd	", addr.outfd, "\n");
+	print("	nopipe	", addr.nopipe, "\n");
+	print("	fpool	", addr.fpool\X, "\n");
+	print("	rpool	", addr.rpool\X, "\n");
+	print("	msize	", addr.msize, "\n");
+	print("	rbuf	", addr.rbuf\X, "\n");
+	print("QLock rlock {\n");
+	QLock(addr.rlock);
+	print("}\n");
+	print("	wbuf	", addr.wbuf\X, "\n");
+	print("QLock wlock {\n");
+	QLock(addr.wlock);
+	print("}\n");
+};
+
+OMASK = 3;
+Maxname = 128;
+Maxrpc = 4096;
+Notstarted = -3;
+Broken = -2;
+Established = -1;
+RpcFailure = 0;
+RpcNeedkey = 1;
+RpcOk = 2;
+RpcErrstr = 3;
+RpcToosmall = 4;
+RpcPhase = 5;
+sizeofAttr = 12;
+aggr Attr
+{
+	'A' Attr 0 next;
+	'A' String 4 name;
+	'A' String 8 val;
+};
+
+defn
+Attr(addr) {
+	complex Attr addr;
+	print("	next	", addr.next\X, "\n");
+	print("	name	", addr.name\X, "\n");
+	print("	val	", addr.val\X, "\n");
+};
+
+sizeof_14_ = 4120;
+aggr _14_
+{
+	'X' 0 arg;
+	'a' 4 buf;
+	'X' 4100 verb;
+	'D' 4104 iverb;
+	'D' 4108 narg;
+	'D' 4112 nbuf;
+	'D' 4116 nwant;
+};
+
+defn
+_14_(addr) {
+	complex _14_ addr;
+	print("	arg	", addr.arg\X, "\n");
+	print("	buf	", addr.buf, "\n");
+	print("	verb	", addr.verb\X, "\n");
+	print("	iverb	", addr.iverb, "\n");
+	print("	narg	", addr.narg, "\n");
+	print("	nbuf	", addr.nbuf, "\n");
+	print("	nwant	", addr.nwant, "\n");
+};
+
+sizeofFsstate = 4700;
+aggr Fsstate
+{
+	'X' 0 sysuser;
+	'D' 4 listoff;
+	_14_ 8 rpc;
+	'a' 4128 err;
+	'a' 4256 keyinfo;
+	'X' 4640 phasename;
+	'D' 4644 isclient;
+	'D' 4648 haveai;
+	'D' 4652 maxphase;
+	'D' 4656 phase;
+	'D' 4660 started;
+	'A' Attr 4664 attr;
+	AuthInfo 4668 ai;
+	'X' 4688 proto;
+	'X' 4692 ps;
+	'X' 4696 ring;
+};
+
+defn
+Fsstate(addr) {
+	complex Fsstate addr;
+	print("	sysuser	", addr.sysuser\X, "\n");
+	print("	listoff	", addr.listoff, "\n");
+	print("_14_ rpc {\n");
+	_14_(addr.rpc);
+	print("}\n");
+	print("	err	", addr.err, "\n");
+	print("	keyinfo	", addr.keyinfo, "\n");
+	print("	phasename	", addr.phasename\X, "\n");
+	print("	isclient	", addr.isclient, "\n");
+	print("	haveai	", addr.haveai, "\n");
+	print("	maxphase	", addr.maxphase, "\n");
+	print("	phase	", addr.phase, "\n");
+	print("	started	", addr.started, "\n");
+	print("	attr	", addr.attr\X, "\n");
+	print("AuthInfo ai {\n");
+	AuthInfo(addr.ai);
+	print("}\n");
+	print("	proto	", addr.proto\X, "\n");
+	print("	ps	", addr.ps\X, "\n");
+	print("	ring	", addr.ring\X, "\n");
+};
+
+sizeofKey = 20;
+aggr Key
+{
+	'D' 0 ref;
+	'A' Attr 4 attr;
+	'A' Attr 8 privattr;
+	'X' 12 proto;
+	'X' 16 priv;
+};
+
+defn
+Key(addr) {
+	complex Key addr;
+	print("	ref	", addr.ref, "\n");
+	print("	attr	", addr.attr\X, "\n");
+	print("	privattr	", addr.privattr\X, "\n");
+	print("	proto	", addr.proto\X, "\n");
+	print("	priv	", addr.priv\X, "\n");
+};
+
+sizeofKeyring = 8;
+aggr Keyring
+{
+	'A' Key 0 key;
+	'D' 4 nkey;
+};
+
+defn
+Keyring(addr) {
+	complex Keyring addr;
+	print("	key	", addr.key\X, "\n");
+	print("	nkey	", addr.nkey, "\n");
+};
+
+sizeofLogbuf = 520;
+aggr Logbuf
+{
+	'D' 0 rp;
+	'D' 4 wp;
+	'a' 8 msg;
+};
+
+defn
+Logbuf(addr) {
+	complex Logbuf addr;
+	print("	rp	", addr.rp, "\n");
+	print("	wp	", addr.wp, "\n");
+	print("	msg	", addr.msg, "\n");
+};
+
+sizeofProto = 28;
+aggr Proto
+{
+	'X' 0 name;
+	'X' 4 init;
+	'X' 8 addkey;
+	'X' 12 closekey;
+	'X' 16 write;
+	'X' 20 read;
+	'X' 24 close;
+};
+
+defn
+Proto(addr) {
+	complex Proto addr;
+	print("	name	", addr.name\X, "\n");
+	print("	init	", addr.init\X, "\n");
+	print("	addkey	", addr.addkey\X, "\n");
+	print("	closekey	", addr.closekey\X, "\n");
+	print("	write	", addr.write\X, "\n");
+	print("	read	", addr.read\X, "\n");
+	print("	close	", addr.close\X, "\n");
+};
+
+complex Keyring ring;
+complex Logbuf logbuf;
+complex Proto apop;
+complex Proto cram;
+complex Proto p9any;
+complex Proto p9sk1;
+complex Proto p9sk2;
+complex Keyring ring;
+complex Srv fs;
+complex Proto main:p;
+Qroot = 0;
+Qfactotum = 1;
+Qrpc = 2;
+Qkeylist = 3;
+Qprotolist = 4;
+Qconfirm = 5;
+Qlog = 6;
+Qctl = 7;
+complex Qid mkqid:q;
+complex Req fsattach:r;
+sizeof_15_ = 12;
+aggr _15_
+{
+	'X' 0 name;
+	'D' 4 qidpath;
+	'U' 8 perm;
+};
+
+defn
+_15_(addr) {
+	complex _15_ addr;
+	print("	name	", addr.name\X, "\n");
+	print("	qidpath	", addr.qidpath, "\n");
+	print("	perm	", addr.perm, "\n");
+};
+
+complex Dir fillstat:dir;
+complex Dir fsdirgen:dir;
+complex Fid fswalk1:fid;
+complex Qid fswalk1:qid;
+complex Req fsstat:r;
+complex Req fsopen:r;
+complex Fsstate fsopen:fss;
+complex Fid fsdestroyfid:fid;
+complex Req readlist:r;
+complex Key keylist:k;
+complex Req fsread:r;
+complex Fsstate fsread:s;
+complex Req fswrite:r;
+complex Srv fs;
blob - /dev/null
blob + 0e4f2fd6e3e1ab44699445367d961581d27d1af9 (mode 644)
--- /dev/null
+++ src/cmd/factotum/fs.c
@@ -0,0 +1,524 @@
+#include "std.h"
+#include "dat.h"
+
+enum
+{
+	Qroot,
+	Qfactotum,
+	Qrpc,
+	Qkeylist,
+	Qprotolist,
+	Qconfirm,
+	Qlog,
+	Qctl,
+	Qneedkey,
+	Qconv,
+};
+
+Qid
+mkqid(int type, int path)
+{
+	Qid q;
+
+	q.type = type;
+	q.path = path;
+	q.vers = 0;
+	return q;
+}
+
+static struct
+{
+	char *name;
+	int qidpath;
+	ulong perm;
+} dirtab[] = {
+	/* positions of confirm and needkey known below */
+	"confirm",		Qconfirm,		0600|DMEXCL,
+	"needkey",	Qneedkey,	0600|DMEXCL,
+	"ctl",			Qctl,			0600,
+	"rpc",		Qrpc,		0666,
+	"proto",		Qprotolist,	0444,
+	"log",		Qlog,		0600|DMEXCL,
+	"conv",		Qconv,		0400,
+};
+
+static void
+fillstat(Dir *dir, char *name, int type, int path, ulong perm)
+{
+	dir->name = estrdup(name);
+	dir->uid = estrdup(owner);
+	dir->gid = estrdup(owner);
+	dir->mode = perm;
+	dir->length = 0;
+	dir->qid = mkqid(type, path);
+	dir->atime = time(0);
+	dir->mtime = time(0);
+	dir->muid = estrdup("");
+}
+
+static int
+rootdirgen(int n, Dir *dir, void *v)
+{
+	USED(v);
+
+	if(n > 0)
+		return -1;
+	
+	fillstat(dir, factname, QTDIR, Qfactotum, DMDIR|0555);
+	return 0;
+}
+
+static int
+fsdirgen(int n, Dir *dir, void *v)
+{
+	USED(v);
+
+	if(n >= nelem(dirtab))
+		return -1;
+	fillstat(dir, dirtab[n].name, 0, dirtab[n].qidpath, dirtab[n].perm);
+	return 0;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+	int i;
+
+	switch((int)fid->qid.path){
+	default:
+		return "fswalk1: cannot happen";
+	case Qroot:
+		if(strcmp(name, factname) == 0){
+			*qid = mkqid(QTDIR, Qfactotum);
+			fid->qid = *qid;
+			return nil;
+		}
+		if(strcmp(name, "..") == 0){
+			*qid = fid->qid;
+			return nil;
+		}
+		return "not found";
+	case Qfactotum:
+		for(i=0; i<nelem(dirtab); i++)
+			if(strcmp(name, dirtab[i].name) == 0){
+				*qid = mkqid(0, dirtab[i].qidpath);
+				fid->qid = *qid;
+				return nil;
+			}
+		if(strcmp(name, "..") == 0){
+			*qid = mkqid(QTDIR, Qroot);
+			fid->qid = *qid;
+			return nil;
+		}
+		return "not found";
+	}
+}
+
+static void
+fsstat(Req *r)
+{
+	int i, path;
+
+	path = r->fid->qid.path;
+	switch(path){
+	case Qroot:
+		fillstat(&r->d, "/", QTDIR, Qroot, 0555|DMDIR);
+		break;
+	case Qfactotum:
+		fillstat(&r->d, "factotum", QTDIR, Qfactotum, 0555|DMDIR);
+		break;
+	default:
+		for(i=0; i<nelem(dirtab); i++)
+			if(dirtab[i].qidpath == path){
+				fillstat(&r->d, dirtab[i].name, 0, dirtab[i].qidpath, dirtab[i].perm);
+				goto Break2;
+			}
+		respond(r, "file not found");
+		break;
+	}
+    Break2:
+	respond(r, nil);
+}
+
+static int
+readlist(int off, int (*gen)(int, char*, uint), Req *r)
+{
+	char *a, *ea;
+	int n;
+
+	a = r->ofcall.data;
+	ea = a+r->ifcall.count;
+	for(;;){
+		n = (*gen)(off, a, ea-a);
+		if(n == 0){
+			r->ofcall.count = a - (char*)r->ofcall.data;
+			return off;
+		}
+		a += n;
+		off++;
+	}
+	return -1;		/* not reached */
+}
+
+static int
+keylist(int i, char *a, uint nn)
+{
+	int n;
+	char buf[512];
+	Key *k;
+
+	if(i >= ring.nkey)
+		return 0;
+
+	k = ring.key[i];
+	k->attr = sortattr(k->attr);
+	n = snprint(buf, sizeof buf, "key %A %N\n", k->attr, k->privattr);
+	if(n >= sizeof(buf)-5)
+		strcpy(buf+sizeof(buf)-5, "...\n");
+	n = strlen(buf);
+	if(n > nn)
+		return 0;
+	memmove(a, buf, n);
+	return n;
+}
+
+static int
+protolist(int i, char *a, uint n)
+{
+	if(prototab[i] == nil)
+		return 0;
+	if(strlen(prototab[i]->name)+1 > n)
+		return 0;
+	n = strlen(prototab[i]->name)+1;
+	memmove(a, prototab[i]->name, n-1);
+	a[n-1] = '\n';
+	return n;
+}
+
+/* BUG this is O(n^2) to fill in the list */
+static int
+convlist(int i, char *a, uint nn)
+{
+	Conv *c;
+	char buf[512];
+	int n;
+
+	for(c=conv; c && i-- > 0; c=c->next)
+		;
+
+	if(c == nil)
+		return 0;
+
+	if(c->state)
+		n = snprint(buf, sizeof buf, "conv state=%q %A\n", c->state, c->attr);
+	else
+		n = snprint(buf, sizeof buf, "conv state=closed err=%q\n", c->err);
+
+	if(n >= sizeof(buf)-5)
+		strcpy(buf+sizeof(buf)-5, "...\n");
+	n = strlen(buf);
+	if(n > nn)
+		return 0;
+	memmove(a, buf, n);
+	return n;
+}
+	
+static void
+fskickreply(Conv *c)
+{
+	Req *r;
+
+	if(c->hangup){
+		if(c->req){
+			respond(c->req, "hangup");
+			c->req = nil;
+		}
+		return;
+	}
+
+	if(!c->req || !c->nreply)
+		return;
+
+	r = c->req;
+	r->ofcall.count = c->nreply;
+	r->ofcall.data = c->reply;
+	if(r->ofcall.count > r->ifcall.count)
+		r->ofcall.count = r->ifcall.count;
+	respond(r, nil);
+	c->req = nil;
+	c->nreply = 0;
+}
+		
+/*
+ * Some of the file system work happens in the fs proc, but
+ * fsopen, fsread, fswrite, fsdestroyfid, and fsflush happen in
+ * the main proc so that they can access the various shared
+ * data structures without worrying about locking.
+ */
+static int inuse[nelem(dirtab)];
+int *confirminuse = &inuse[0];
+int *needkeyinuse = &inuse[1];
+static void
+fsopen(Req *r)
+{
+	int i, *inusep, perm;
+	static int need[4] = { 4, 2, 6, 1 };
+	Conv *c;
+
+	inusep = nil;
+	perm = 5;	/* directory */
+	for(i=0; i<nelem(dirtab); i++)
+		if(dirtab[i].qidpath == r->fid->qid.path){
+			if(dirtab[i].perm & DMEXCL)
+				inusep = &inuse[i];
+			if(strcmp(r->fid->uid, owner) == 0)
+				perm = dirtab[i].perm>>6;
+			else
+				perm = dirtab[i].perm;
+			break;
+		}
+
+	if((r->ifcall.mode&~(OMASK|OTRUNC))
+	|| (need[r->ifcall.mode&3] & ~perm)){
+		respond(r, "permission denied");
+		return;
+	}
+
+	if(inusep){
+		if(*inusep){
+			respond(r, "file in use");
+			return;
+		}
+		*inusep = 1;
+	}
+
+	if(r->fid->qid.path == Qrpc){
+		if((c = convalloc(r->fid->uid)) == nil){
+			char e[ERRMAX];
+
+			rerrstr(e, sizeof e);
+			respond(r, e);
+			return;
+		}
+		c->kickreply = fskickreply;
+		r->fid->aux = c;
+	}
+	
+	respond(r, nil);
+}
+
+static void
+fsread(Req *r)
+{
+	Conv *c;
+
+	switch((int)r->fid->qid.path){
+	default:
+		respond(r, "fsread: cannot happen");
+		break;
+	case Qroot:
+		dirread9p(r, rootdirgen, nil);
+		respond(r, nil);
+		break;
+	case Qfactotum:
+		dirread9p(r, fsdirgen, nil);
+		respond(r, nil);
+		break;
+	case Qrpc:
+		c = r->fid->aux;
+		if(c->rpc.op == RpcUnknown){
+			respond(r, "no rpc pending");
+			break;
+		}
+		if(c->req){
+			respond(r, "read already pending");
+			break;
+		}
+		c->req = r;
+		if(c->nreply)
+			(*c->kickreply)(c);
+		else
+			rpcexec(c);
+		break;
+	case Qconfirm:
+		confirmread(r);
+		break;
+	case Qlog:
+		logread(r);
+		break;
+	case Qctl:
+		r->fid->aux = (void*)readlist((int)r->fid->aux, keylist, r);
+		respond(r, nil);
+		break;
+	case Qneedkey:
+		needkeyread(r);
+		break;
+	case Qprotolist:
+		r->fid->aux = (void*)readlist((int)r->fid->aux, protolist, r);
+		respond(r, nil);
+		break;
+	case Qconv:
+		r->fid->aux = (void*)readlist((int)r->fid->aux, convlist, r);
+		respond(r, nil);
+		break;
+	}
+}
+
+static void
+fswrite(Req *r)
+{
+	int ret;
+	char err[ERRMAX], *s;
+	int (*strfn)(char*);
+
+	switch((int)r->fid->qid.path){
+	default:
+		respond(r, "fswrite: cannot happen");
+		break;
+	case Qrpc:
+		if(rpcwrite(r->fid->aux, r->ifcall.data, r->ifcall.count) < 0){
+			rerrstr(err, sizeof err);
+			respond(r, err);
+		}else{
+			r->ofcall.count = r->ifcall.count;
+			respond(r, nil);
+		}
+		break;
+	case Qneedkey:
+		strfn = needkeywrite;
+		goto string;
+	case Qctl:
+		strfn = ctlwrite;
+		goto string;
+	case Qconfirm:
+		strfn = confirmwrite;
+	string:
+		s = emalloc(r->ifcall.count+1);
+		memmove(s, r->ifcall.data, r->ifcall.count);
+		s[r->ifcall.count] = '\0';
+		ret = (*strfn)(s);
+		free(s);
+		if(ret < 0){
+			rerrstr(err, sizeof err);
+			respond(r, err);
+		}else{
+			r->ofcall.count = r->ifcall.count;
+			respond(r, nil);
+		}
+		break;
+	}
+}
+
+static void
+fsflush(Req *r)
+{
+	confirmflush(r);
+	logflush(r);
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+	if(fid->qid.path == Qrpc){
+		convhangup(fid->aux);
+		convclose(fid->aux);
+	}
+}
+
+static Channel *creq;
+static Channel *cfid, *cfidr;
+
+static void
+fsreqthread(void *v)
+{
+	Req *r;
+
+	USED(v);
+
+	creq = chancreate(sizeof(Req*), 0);
+
+	while((r = recvp(creq)) != nil){
+		switch(r->ifcall.type){
+		default:
+			respond(r, "bug in fsreqthread");
+			break;
+		case Topen:
+			fsopen(r);
+			break;
+		case Tread:
+			fsread(r);
+			break;
+		case Twrite:
+			fswrite(r);
+			break;
+		case Tflush:
+			fsflush(r);
+			break;
+		}
+	}
+}
+
+static void
+fsclunkthread(void *v)
+{
+	Fid *f;
+
+	USED(v);
+	cfid = chancreate(sizeof(Fid*), 0);
+	cfidr = chancreate(sizeof(Fid*), 0);
+
+	while((f = recvp(cfid)) != nil){
+		fsdestroyfid(f);
+		sendp(cfidr, 0);
+	}
+}
+
+static void
+fsproc(void *v)
+{
+	USED(v);
+
+	threadcreate(fsreqthread, nil, STACK);
+	threadcreate(fsclunkthread, nil, STACK);
+	threadexits(nil);
+}
+
+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);
+}
+
+static void
+fssend(Req *r)
+{
+	sendp(creq, r);
+}
+
+static void
+fssendclunk(Fid *f)
+{
+	sendp(cfid, f);
+	recvp(cfidr);
+}
+
+Srv fs = {
+.attach=	fsattach,
+.walk1=	fswalk1,
+.open=	fssend,
+.read=	fssend,
+.write=	fssend,
+.stat=	fsstat,
+.flush=	fssend,
+.destroyfid=	fssendclunk,
+};
+
blob - /dev/null
blob + 7293e54b63e77381e187e4e86d8db5cd28f9d9bd (mode 755)
--- /dev/null
+++ src/cmd/factotum/guide
@@ -0,0 +1,3 @@
+kill 8.out|rc
+unmount /srv/factotum /mnt
+8.out
blob - /dev/null
blob + 72f245d0e3d52b86bc84284a3eb4b088057173fb (mode 644)
--- /dev/null
+++ src/cmd/factotum/guide2
@@ -0,0 +1,6 @@
+kill 8.out|rc
+unmount /mnt/factotum
+8.out -m /mnt/factotum
+cat /mnt/factotum/log &
+unmount /factotum
+bind 8.out /factotum
blob - /dev/null
blob + 9023fed509e1bcd4e560efb778f077c322f35cdd (mode 644)
--- /dev/null
+++ src/cmd/factotum/key.c
@@ -0,0 +1,190 @@
+#include "std.h"
+#include "dat.h"
+
+Ring ring;
+
+Key*
+keylookup(char *fmt, ...)
+{
+	int i;
+	Attr *a;
+	Key *k;
+	va_list arg;
+
+	va_start(arg, fmt);
+	a = parseattrfmtv(fmt, arg);
+	va_end(arg);
+
+	for(i=0; i<ring.nkey; i++){
+		k = ring.key[i];
+		if(matchattr(a, k->attr, k->privattr)){
+			k->ref++;
+			freeattr(a);
+			return k;
+		}
+	}
+	freeattr(a);
+	werrstr("no key found");
+	return nil;
+}
+
+Key*
+keyfetch(Conv *c, char *fmt, ...)
+{
+	int i, tag;
+	Attr *a;
+	Key *k;
+	va_list arg;
+
+	va_start(arg, fmt);
+	a = parseattrfmtv(fmt, arg);
+	va_end(arg);
+
+	tag = 0;
+
+	for(i=0; i<ring.nkey; i++){
+		k = ring.key[i];
+		if(tag < k->tag)
+			tag = k->tag;
+		if(matchattr(a, k->attr, k->privattr)){
+			k->ref++;
+			if(strfindattr(k->attr, "confirm") && confirmkey(c, k) != 1){
+				k->ref--;
+				continue;
+			}
+			freeattr(a);
+			return k;
+		}
+	}
+
+	if(needkey(c, a) < 0)
+		convneedkey(c, a);
+
+	for(i=0; i<ring.nkey; i++){
+		k = ring.key[i];
+		if(k->tag <= tag)
+			continue;
+		if(matchattr(a, k->attr, k->privattr)){
+			k->ref++;
+			if(strfindattr(k->attr, "confirm") && confirmkey(c, k) != 1){
+				k->ref--;
+				continue;
+			}
+			freeattr(a);
+			return k;
+		}
+	}
+	freeattr(a);
+	werrstr("no key found");
+	return nil;
+}
+
+static int taggen;
+
+void
+keyadd(Key *k)
+{
+	int i;
+
+	k->ref++;
+	k->tag = ++taggen;
+	for(i=0; i<ring.nkey; i++){
+		if(matchattr(k->attr, ring.key[i]->attr, nil)
+		&& matchattr(ring.key[i]->attr, k->attr, nil)){
+			keyclose(ring.key[i]);
+			ring.key[i] = k;
+			return;
+		}
+	}
+
+	ring.key = erealloc(ring.key, (ring.nkey+1)*sizeof(ring.key[0]));
+	ring.key[ring.nkey++] = k;
+}
+
+void
+keyclose(Key *k)
+{
+	if(k == nil)
+		return;
+
+	if(--k->ref > 0)
+		return;
+
+	if(k->proto->closekey)
+		(*k->proto->closekey)(k);
+
+	freeattr(k->attr);
+	freeattr(k->privattr);
+	free(k);
+}
+
+Key*
+keyreplace(Conv *c, Key *k, char *fmt, ...)
+{
+	Key *kk;
+	char *msg;
+	Attr *a, *b, *bp;
+	va_list arg;
+
+	va_start(arg, fmt);
+	msg = vsmprint(fmt, arg);
+	if(msg == nil)
+		sysfatal("out of memory");
+	va_end(arg);
+
+	/* replace prompted values with prompts */	
+	a = copyattr(k->attr);
+	bp = parseattr(k->proto->keyprompt);
+	for(b=bp; b; b=b->next){
+		a = delattr(a, b->name);
+		a = addattr(a, "%q?", b->name);
+	}
+	freeattr(bp);
+
+	if(badkey(c, k, msg, a) < 0)
+		convbadkey(c, k, msg, a);
+	kk = keylookup("%A", a);
+	freeattr(a);
+	keyclose(k);
+	if(kk == k){
+		keyclose(kk);
+		werrstr("%s", msg);
+		return nil;
+	}
+
+	if(strfindattr(kk->attr, "confirm")){
+		if(confirmkey(c, kk) != 1){
+			werrstr("key use not confirmed");
+			keyclose(kk);
+			return nil;
+		}
+	}
+	return kk;
+}
+
+void
+keyevict(Conv *c, Key *k, char *fmt, ...)
+{
+	char *msg;
+	Attr *a, *b, *bp;
+	va_list arg;
+
+	va_start(arg, fmt);
+	msg = vsmprint(fmt, arg);
+	if(msg == nil)
+		sysfatal("out of memory");
+	va_end(arg);
+
+	/* replace prompted values with prompts */	
+	a = copyattr(k->attr);
+	bp = parseattr(k->proto->keyprompt);
+	for(b=bp; b; b=b->next){
+		a = delattr(a, b->name);
+		a = addattr(a, "%q?", b->name);
+	}
+	freeattr(bp);
+
+	if(badkey(c, k, msg, nil) < 0)
+		convbadkey(c, k, msg, nil);
+	keyclose(k);
+}
blob - /dev/null
blob + 6c2d69ddbf6a5d152737ebbb1ef680f8a42eab2f (mode 644)
--- /dev/null
+++ src/cmd/factotum/log.c
@@ -0,0 +1,121 @@
+#include "std.h"
+#include "dat.h"
+
+void
+lbkick(Logbuf *lb)
+{
+	char *s;
+	int n;
+	Req *r;
+
+	while(lb->wait && lb->rp != lb->wp){
+		r = lb->wait;
+		lb->wait = r->aux;
+		if(lb->wait == nil)
+			lb->waitlast = &lb->wait;
+		r->aux = nil;
+		if(r->ifcall.count < 5){
+			respond(r, "factotum: read request count too short");
+			continue;
+		}
+		s = lb->msg[lb->rp];
+		lb->msg[lb->rp] = nil;
+		if(++lb->rp == nelem(lb->msg))
+			lb->rp = 0;
+		n = r->ifcall.count;
+		if(n < strlen(s)+1+1){
+			memmove(r->ofcall.data, s, n-5);
+			n -= 5;
+			r->ofcall.data[n] = '\0';
+			/* look for first byte of UTF-8 sequence by skipping continuation bytes */
+			while(n>0 && (r->ofcall.data[--n]&0xC0)==0x80)
+				;
+			strcpy(r->ofcall.data+n, "...\n");
+		}else{
+			strcpy(r->ofcall.data, s);
+			strcat(r->ofcall.data, "\n");
+		}
+		r->ofcall.count = strlen(r->ofcall.data);
+		free(s);
+		respond(r, nil);
+	}
+}
+
+void
+lbread(Logbuf *lb, Req *r)
+{
+	if(lb->waitlast == nil)
+		lb->waitlast = &lb->wait;
+	*(lb->waitlast) = r;
+	lb->waitlast = (Req**)&r->aux;
+	r->aux = nil;
+	lbkick(lb);
+}
+
+void
+lbflush(Logbuf *lb, Req *r)
+{
+	Req **l;
+
+	for(l=&lb->wait; *l; l=(Req**)&(*l)->aux){
+		if(*l == r){
+			*l = r->aux;
+			r->aux = nil;
+			if(*l == nil)
+				lb->waitlast = l;
+			closereq(r);
+			break;
+		}
+	}
+}
+
+void
+lbappend(Logbuf *lb, char *fmt, ...)
+{
+	va_list arg;
+
+	va_start(arg, fmt);
+	lbvappend(lb, fmt, arg);
+	va_end(arg);
+}
+
+void
+lbvappend(Logbuf *lb, char *fmt, va_list arg)
+{
+	char *s;
+
+	s = smprint(fmt, arg);
+	if(s == nil)
+		sysfatal("out of memory");
+	if(lb->msg[lb->wp])
+		free(lb->msg[lb->wp]);
+	lb->msg[lb->wp] = s;
+	if(++lb->wp == nelem(lb->msg))
+		lb->wp = 0;
+	lbkick(lb);
+}
+
+Logbuf logbuf;
+
+void
+logread(Req *r)
+{
+	lbread(&logbuf, r);
+}
+
+void
+logflush(Req *r)
+{
+	lbflush(&logbuf, r);
+}
+
+void
+flog(char *fmt, ...)
+{
+	va_list arg;
+
+	va_start(arg, fmt);
+	lbvappend(&logbuf, fmt, arg);
+	va_end(arg);
+}
+
blob - /dev/null
blob + 3e9f89f8ef467a02483d67ddb639f4a4838aed04 (mode 644)
--- /dev/null
+++ src/cmd/factotum/main.c
@@ -0,0 +1,163 @@
+#include "std.h"
+#include "dat.h"
+
+int debug;
+char *factname = "factotum";
+char *service = nil;
+char *owner;
+char *authaddr;
+void gflag(char*);
+
+void
+usage(void)
+{
+	fprint(2, "usage: factotum [-Dd] [-a authaddr] [-m mtpt]\n");
+	fprint(2, " or   factotum -g keypattern\n");
+	fprint(2, " or   factotum -g 'badkeyattr\nmsg\nkeypattern'");
+	exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	char *mtpt;
+
+	mtpt = "/mnt";
+	owner = getuser();
+	quotefmtinstall();
+	fmtinstall('A', attrfmt);
+	fmtinstall('H', encodefmt);
+	fmtinstall('N', attrnamefmt);
+
+	if(argc == 3 && strcmp(argv[1], "-g") == 0){
+		gflag(argv[2]);
+		exits(nil);
+	}
+
+	ARGBEGIN{
+	default:
+		usage();
+	case 'D':
+		chatty9p++;
+		break;
+	case 'a':
+		authaddr = EARGF(usage());
+		break;
+	case 'g':
+		usage();
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 's':
+		service = EARGF(usage());
+		break;
+	}ARGEND
+
+	if(argc != 0)
+		usage();
+
+	threadpostmountsrv(&fs, service, mtpt, MBEFORE);
+	threadexits(nil);
+}
+
+/*
+ *  prompt user for a key.  don't care about memory leaks, runs standalone
+ */
+static Attr*
+promptforkey(int fd, char *params)
+{
+	char *v;
+	Attr *a, *attr;
+	char *def;
+
+	attr = _parseattr(params);
+	fprint(fd, "!adding key:");
+	for(a=attr; a; a=a->next)
+		if(a->type != AttrQuery && a->name[0] != '!')
+			fprint(fd, " %q=%q", a->name, a->val);
+	fprint(fd, "\n");
+
+	for(a=attr; a; a=a->next){
+		v = 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 = 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;
+}
+
+static void
+askuser(int fd, char *params)
+{
+	Attr *attr;
+
+	attr = promptforkey(fd, params);
+	if(attr == nil)
+		sysfatal("no key supplied");
+	if(sendkey(attr) < 0)
+		sysfatal("sending key to factotum: %r");
+}
+
+void
+gflag(char *s)
+{
+	char *f[4];
+	int nf;
+	int fd;
+
+	if((fd = open("/dev/cons", ORDWR)) < 0)
+		sysfatal("open /dev/cons: %r");
+
+	nf = getfields(s, f, nelem(f), 0, "\n");
+	if(nf == 1){	/* needkey or old badkey */
+		fprint(fd, "\n");
+		askuser(fd, s);
+		exits(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);
+	}
+	usage();
+}
blob - /dev/null
blob + 3aa9bb7071bc4062bb60867081e0d1c64a027319 (mode 644)
--- /dev/null
+++ src/cmd/factotum/mkfile
@@ -0,0 +1,34 @@
+PLAN9=../../..
+<$PLAN9/src/mkhdr
+
+TARG=factotum
+PROTO=\
+	apop.$O\
+	chap.$O\
+	p9any.$O\
+	p9sk1.$O\
+
+OFILES=\
+	$PROTO\
+	attr.$O\
+	confirm.$O\
+	conv.$O\
+	ctl.$O\
+	fs.$O\
+	key.$O\
+	log.$O\
+	main.$O\
+	plan9.$O\
+	proto.$O\
+	rpc.$O\
+	util.$O\
+	xio.$O\
+
+HFILES=dat.h
+
+SHORTLIB=auth authsrv sec mp 9p thread 9
+<$PLAN9/src/mkone
+
+$O.test: test.$O
+	$LD -o $target $prereq
+
blob - /dev/null
blob + 971c7e50c243fb0d678a88d938d6cebc542bd27f (mode 644)
--- /dev/null
+++ src/cmd/factotum/p9any.c
@@ -0,0 +1,266 @@
+#include "std.h"
+#include "dat.h"
+
+/*
+ * p9any - protocol negotiator
+ *
+ * Protocol:
+ *	S->C: v.2 proto@dom proto@dom proto@dom... NUL
+ *	C->S: proto dom NUL
+ *	[negotiated proto continues]
+ */
+
+static Proto* okproto[] =
+{
+	&p9sk1,
+	nil,
+};
+
+static int
+rolecall(Role *r, char *name, Conv *c)
+{
+	for(; r->name; r++)
+		if(strcmp(r->name, name) == 0)
+			return (*r->fn)(c);
+	werrstr("unknown role");
+	return -1;
+}
+
+static int
+hasnul(void *v, int n)
+{
+	char *c;
+
+	c = v;
+	if(n > 0 && c[n-1] == '\0')
+		return n;
+	else
+		return n+1;
+}
+
+static int
+p9anyserver(Conv *c)
+{
+	char *s, *dom;
+	int i, j, n, m, ret;
+	char *tok[3];
+	Attr *attr;
+	Key *k;
+
+	ret = -1;
+	s = estrdup("v.2");
+	n = 0;
+	attr = delattr(copyattr(c->attr), "proto");
+
+	for(i=0; i<ring.nkey; i++){
+		k = ring.key[i];
+		for(j=0; okproto[j]; j++)
+			if(k->proto == okproto[j]
+			&& (dom = strfindattr(k->attr, "dom")) != nil
+			&& matchattr(attr, k->attr, k->privattr)){
+				s = estrappend(s, " %s@%s", k->proto->name, dom);
+				n++;
+			}
+	}
+
+	if(n == 0){
+		werrstr("no valid keys");
+		goto out;
+	}
+
+	c->state = "write offer";
+	if(convwrite(c, s, strlen(s)+1) < 0)
+		goto out;
+	free(s);
+	s = nil;
+
+	c->state = "read choice";
+	if(convreadfn(c, hasnul, &s) < 0)
+		goto out;
+
+	m = tokenize(s, tok, nelem(tok));
+	if(m != 2){
+		werrstr("bad protocol message");
+		goto out;
+	}
+
+	for(i=0; okproto[i]; i++)
+		if(strcmp(okproto[i]->name, tok[0]) == 0)
+			break;
+	if(!okproto[i]){
+		werrstr("bad chosen protocol %q", tok[0]);
+		goto out;
+	}
+
+	c->state = "write ok";
+	if(convwrite(c, "OK\0", 3) < 0)
+		goto out;
+
+	c->state = "start choice";
+	attr = addattr(attr, "proto=%q dom=%q", tok[0], tok[1]);
+	free(c->attr);
+	c->attr = attr;
+	attr = nil;
+	c->proto = okproto[i];
+
+	if(rolecall(c->proto->roles, "server", c) < 0){
+		werrstr("%s: %r", tok[0]);
+		goto out;
+	}
+
+	ret = 0;
+	
+out:
+	free(s);
+	freeattr(attr);
+	return ret;
+}
+
+static int
+p9anyclient(Conv *c)
+{
+	char *s, **f, *tok[20], ok[3], *q, *user, *dom;
+	int i, n, ret, version;
+	Key *k;
+	Attr *attr;
+	Proto *p;
+
+	ret = -1;
+	s = nil;
+	k = nil;
+
+	user = strfindattr(c->attr, "user");
+	dom = strfindattr(c->attr, "dom");
+
+	/*
+	 * if the user is the factotum owner, any key will do.
+	 * if not, then if we have a speakfor key,
+	 * we will only vouch for the user's local identity.
+	 *
+	 * this logic is duplicated in p9sk1.c
+	 */
+	attr = delattr(copyattr(c->attr), "role");
+	attr = delattr(attr, "proto");
+	if(strcmp(c->sysuser, owner) == 0)
+		attr = addattr(attr, "role=client");
+	else if(user==nil || strcmp(c->sysuser, user)==0){
+		attr = delattr(attr, "user");
+		attr = addattr(attr, "role=speakfor");
+	}else{
+		werrstr("will not authenticate for %q as %q", c->sysuser, user);
+		goto out;
+	}
+
+	c->state = "read offer";
+	if(convreadfn(c, hasnul, &s) < 0)
+		goto out;
+
+	c->state = "look for keys";
+	n = tokenize(s, tok, nelem(tok));
+	f = tok;
+	version = 1;
+	if(n > 0 && memcmp(f[0], "v.", 2) == 0){
+		version = atoi(f[0]+2);
+		if(version != 2){
+			werrstr("unknown p9any version: %s", f[0]);
+			goto out;
+		}
+		f++;
+		n--;
+	}
+
+	/* look for keys that don't need confirmation */
+	for(i=0; i<n; i++){
+		if((q = strchr(f[i], '@')) == nil)
+			continue;
+		if(dom && strcmp(q+1, dom) != 0)
+			continue;
+		*q++ = '\0';
+		if((k = keylookup("%A proto=%q dom=%q", attr, f[i], q))
+		&& strfindattr(k->attr, "confirm") == nil)
+			goto found;
+		*--q = '@';
+	}
+
+	/* look for any keys at all */
+	for(i=0; i<n; i++){
+		if((q = strchr(f[i], '@')) == nil)
+			continue;
+		if(dom && strcmp(q+1, dom) != 0)
+			continue;
+		*q++ = '\0';
+		if(k = keyfetch(c, "%A proto=%q dom=%q", attr, f[i], q))
+			goto found;
+		*--q = '@';
+	}
+
+	/* ask for new keys */
+	c->state = "ask for keys";
+	for(i=0; i<n; i++){
+		if((q = strchr(f[i], '@')) == nil)
+			continue;
+		if(dom && strcmp(q+1, dom) != 0)
+			continue;
+		*q++ = '\0';
+		p = protolookup(f[i]);
+		if(p == nil || p->keyprompt == nil){
+			*--q = '@';
+			continue;
+		}
+		if(k = keyfetch(c, "%A proto=%q dom=%q %s", attr, f[i], q, p->keyprompt))
+			goto found;
+		*--q = '@';
+	}
+
+	/* nothing worked */
+	werrstr("unable to find common key");
+	goto out;
+
+found:
+	/* f[i] is the chosen protocol, q the chosen domain */
+	attr = addattr(attr, "proto=%q dom=%q", f[i], q);
+	c->state = "write choice";
+	/* have a key: go for it */
+	if(convprint(c, "%q %q", f[i], q) < 0
+	|| convwrite(c, "\0", 1) < 0)
+		goto out;
+
+	if(version == 2){
+		c->state = "read ok";
+		if(convread(c, ok, 3) < 0 || memcmp(ok, "OK\0", 3) != 0)
+			goto out;
+	}
+
+	c->state = "start choice";
+	c->proto = protolookup(f[i]);
+	freeattr(c->attr);
+	c->attr = attr;
+	attr = nil;
+
+	if(rolecall(c->proto->roles, "client", c) < 0){
+		werrstr("%s: %r", c->proto->name);
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	keyclose(k);
+	freeattr(attr);
+	free(s);
+	return ret;
+}
+
+static Role
+p9anyroles[] = 
+{
+	"client",	p9anyclient,
+	"server",	p9anyserver,
+	0
+};
+
+Proto p9any = {
+.name=		"p9any",
+.roles=		p9anyroles,
+};
+
blob - /dev/null
blob + 7f53e44782057f8f4aa67ba923ab8530b8490e38 (mode 644)
--- /dev/null
+++ src/cmd/factotum/p9cr.c
@@ -0,0 +1,545 @@
+/*
+ * p9cr, vnc - one-sided challenge/response authentication
+ *
+ * Protocol:
+ *
+ *	C -> S: user
+ *	S -> C: challenge
+ *	C -> S: response
+ *	S -> C: ok or bad
+ *
+ * Note that this is the protocol between factotum and the local
+ * program, not between the two factotums.  The information 
+ * exchanged here is wrapped in other protocols by the local
+ * programs.
+ */
+
+#include "std.h"
+#include "dat.h"
+
+static int
+p9crcheck(Key *k)
+{
+	if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
+		werrstr("need user and !password attributes");
+		return -1;
+	}
+	return 0;
+}
+
+static int
+p9crclient(Conv *c)
+{
+	char *chal, *pw, *res, *user;
+	int astype, nchal, npw, ntry, ret;
+	uchar resp[MD5dlen];
+	Attr *attr;
+	DigestState *ds;
+	Key *k;
+	
+	chal = nil;
+	k = nil;
+	res = nil;
+	ret = -1;
+	attr = c->attr;
+
+	if(c->proto == &p9cr){
+		astype = AuthChal;
+		challen = NETCHLEN;
+	}else if(c->proto == &vnc){
+		astype = AuthVnc;
+		challen = MAXCHAL;
+	}else{
+		werrstr("bad proto");
+		goto out;
+	}
+
+	c->state = "find key";
+	k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
+	if(k == nil)
+		goto out;
+
+	for(ntry=1;; ntry++){
+		if(c->attr != attr)
+			freeattr(c->attr);
+		c->attr = addattrs(copyattr(attr), k->attr);
+		if((pw = strfindattr(k->privattr, "!password")) == nil){
+			werrstr("key has no !password (cannot happen)");
+			goto out;
+		}
+		npw = strlen(pw);
+
+		if((user = strfindattr(k->attr, "user")) == nil){
+			werrstr("key has no user (cannot happen)");
+			goto out;
+		}
+
+		if(convprint(c, "%s", user) < 0)
+			goto out;
+
+		if(convreadm(c, &chal) < 0)
+			goto out;
+
+		if((nresp = (*response)(chal, resp)) < 0)
+			goto out;
+
+		if(convwrite(c, resp, nresp) < 0)
+			goto out;
+
+		if(convreadm(c, &res) < 0)
+			goto out;
+
+		if(strcmp(res, "ok") == 0)
+			break;
+
+		if((k = keyreplace(c, k, "%s", res)) == nil){
+			c->state = "auth failed";
+			werrstr("%s", res);
+			goto out;
+		}
+	}
+
+	werrstr("succeeded");
+	ret = 0;
+
+out:
+	keyclose(k);
+	free(chal);
+	if(c->attr != attr)
+		freeattr(attr);
+	return ret;
+}
+
+static int
+p9crserver(Conv *c)
+{
+	char chal[APOPCHALLEN], *user, *resp;
+	ServerState s;
+	int astype, ret;
+	Attr *a;
+
+	ret = -1;
+	user = nil;
+	resp = nil;
+	memset(&s, 0, sizeof s);
+	s.asfd = -1;
+
+	if(c->proto == &apop)
+		astype = AuthApop;
+	else if(c->proto == &cram)
+		astype = AuthCram;
+	else{
+		werrstr("bad proto");
+		goto out;
+	}
+
+	c->state = "find key";
+	if((s.k = plan9authkey(c->attr)) == nil)
+		goto out;
+
+	a = copyattr(s.k->attr);
+	a = delattr(a, "proto");
+	c->attr = addattrs(c->attr, a);
+	freeattr(a);
+
+	c->state = "authdial";
+	s.hostid = strfindattr(s.k->attr, "user");
+	s.dom = strfindattr(s.k->attr, "dom");
+	if((s.asfd = xioauthdial(nil, s.dom)) < 0){
+		werrstr("authdial %s: %r", s.dom);
+		goto out;
+	}
+
+	c->state = "authchal";
+	if(p9crchal(&s, astype, chal) < 0)
+		goto out;
+
+	c->state = "write challenge";
+	if(convprint(c, "%s", chal) < 0)
+		goto out;
+
+	for(;;){
+		c->state = "read user";
+		if(convreadm(c, &user) < 0)
+			goto out;
+
+		c->state = "read response";
+		if(convreadm(c, &resp) < 0)
+			goto out;
+
+		c->state = "authwrite";
+		switch(apopresp(&s, user, resp)){
+		case -1:
+			goto out;
+		case 0:
+			c->state = "write status";
+			if(convprint(c, "bad authentication failed") < 0)
+				goto out;
+			break;
+		case 1:
+			c->state = "write status";
+			if(convprint(c, "ok") < 0)
+				goto out;
+			goto ok;
+		}
+		free(user);
+		free(resp);
+		user = nil;
+		resp = nil;
+	}
+
+ok:
+	ret = 0;
+	c->attr = addcap(c->attr, c->sysuser, &s.t);
+
+out:
+	keyclose(s.k);
+	free(user);
+	free(resp);
+//	xioclose(s.asfd);
+	return ret;
+}
+
+enum
+{
+	MAXCHAL = 64,
+};
+
+typedef struct State State;
+struct State
+{
+	Key	*key;
+	int	astype;
+	int	asfd;
+	Ticket	t;
+	Ticketreq tr;
+	char	chal[MAXCHAL];
+	int	challen;
+	char	resp[MAXCHAL];
+	int	resplen;
+};
+
+enum
+{
+	CNeedChal,
+	CHaveResp,
+
+	SHaveChal,
+	SNeedResp,
+
+	Maxphase,
+};
+
+static char *phasenames[Maxphase] =
+{
+[CNeedChal]	"CNeedChal",
+[CHaveResp]	"CHaveResp",
+
+[SHaveChal]	"SHaveChal",
+[SNeedResp]	"SNeedResp",
+};
+
+static void
+p9crclose(Fsstate *fss)
+{
+	State *s;
+
+	s = fss->ps;
+	if(s->asfd >= 0){
+		close(s->asfd);
+		s->asfd = -1;
+	}
+	free(s);
+}
+
+static int getchal(State*, Fsstate*);
+
+static int
+p9crinit(Proto *p, Fsstate *fss)
+{
+	int iscli, ret;
+	char *user;
+	State *s;
+	Attr *attr;
+
+	if((iscli = isclient(_str_findattr(fss->attr, "role"))) < 0)
+		return failure(fss, nil);
+	
+	s = emalloc(sizeof(*s));
+	s->asfd = -1;
+	if(p == &p9cr){
+		s->astype = AuthChal;
+		s->challen = NETCHLEN;
+	}else if(p == &vnc){
+		s->astype = AuthVNC;
+		s->challen = Maxchal;
+	}else
+		abort();
+
+	if(iscli){
+		fss->phase = CNeedChal;
+		if(p == &p9cr)
+			attr = setattr(_copyattr(fss->attr), "proto=p9sk1");
+		else
+			attr = nil;
+		ret = findkey(&s->key, fss, Kuser, 0, attr ? attr : fss->attr,
+			"role=client %s", p->keyprompt);
+		_freeattr(attr);
+		if(ret != RpcOk){
+			free(s);
+			return ret;
+		}
+		fss->ps = s;
+	}else{
+		if((ret = findp9authkey(&s->key, fss)) != RpcOk){
+			free(s);
+			return ret;
+		}
+		if((user = _str_findattr(fss->attr, "user")) == nil){
+			free(s);
+			return failure(fss, "no user name specified in start msg");
+		}
+		if(strlen(user) >= sizeof s->tr.uid){
+			free(s);
+			return failure(fss, "user name too long");
+		}
+		fss->ps = s;
+		strcpy(s->tr.uid, user);
+		ret = getchal(s, fss);
+		if(ret != RpcOk){
+			p9crclose(fss);	/* frees s */
+			fss->ps = nil;
+		}
+	}
+	fss->phasename = phasenames;
+	fss->maxphase = Maxphase;
+	return ret;
+}
+
+static int
+p9crread(Fsstate *fss, void *va, uint *n)
+{
+	int m;
+	State *s;
+
+	s = fss->ps;
+	switch(fss->phase){
+	default:
+		return phaseerror(fss, "read");
+
+	case CHaveResp:
+		if(s->resplen < *n)
+			*n = s->resplen;
+		memmove(va, s->resp, *n);
+		fss->phase = Established;
+		return RpcOk;
+
+	case SHaveChal:
+		if(s->astype == AuthChal)
+			m = strlen(s->chal);	/* ascii string */
+		else
+			m = s->challen;		/* fixed length binary */
+		if(m > *n)
+			return toosmall(fss, m);
+		*n = m;
+		memmove(va, s->chal, m);
+		fss->phase = SNeedResp;
+		return RpcOk;
+	}
+}
+
+static int
+p9response(Fsstate *fss, State *s)
+{
+	char key[DESKEYLEN];
+	uchar buf[8];
+	ulong chal;
+	char *pw;
+
+	pw = _str_findattr(s->key->privattr, "!password");
+	if(pw == nil)
+		return failure(fss, "vncresponse cannot happen");
+	passtokey(key, pw);
+	memset(buf, 0, 8);
+	sprint((char*)buf, "%d", atoi(s->chal));
+	if(encrypt(key, buf, 8) < 0)
+		return failure(fss, "can't encrypt response");
+	chal = (buf[0]<<24)+(buf[1]<<16)+(buf[2]<<8)+buf[3];
+	s->resplen = snprint(s->resp, sizeof s->resp, "%.8lux", chal);
+	return RpcOk;
+}
+
+static uchar tab[256];
+
+/* VNC reverses the bits of each byte before using as a des key */
+static void
+mktab(void)
+{
+	int i, j, k;
+	static int once;
+
+	if(once)
+		return;
+	once = 1;
+
+	for(i=0; i<256; i++) {
+		j=i;
+		tab[i] = 0;
+		for(k=0; k<8; k++) {
+			tab[i] = (tab[i]<<1) | (j&1);
+			j >>= 1;
+		}
+	}
+}
+
+static int
+vncaddkey(Key *k)
+{
+	uchar *p;
+	char *s;
+
+	k->priv = emalloc(8+1);
+	if(s = _str_findattr(k->privattr, "!password")){
+		mktab();
+		memset(k->priv, 0, 8+1);
+		strncpy((char*)k->priv, s, 8);
+		for(p=k->priv; *p; p++)
+			*p = tab[*p];
+	}else{
+		werrstr("no key data");
+		return -1;
+	}
+	return replacekey(k);
+}
+
+static void
+vncclosekey(Key *k)
+{
+	free(k->priv);
+}
+
+static int
+vncresponse(Fsstate*, State *s)
+{
+	DESstate des;
+
+	memmove(s->resp, s->chal, sizeof s->chal);
+	setupDESstate(&des, s->key->priv, nil);
+	desECBencrypt((uchar*)s->resp, s->challen, &des);
+	s->resplen = s->challen;
+	return RpcOk;
+}
+
+static int
+p9crwrite(Fsstate *fss, void *va, uint n)
+{
+	char tbuf[TICKETLEN+AUTHENTLEN];
+	State *s;
+	char *data = va;
+	Authenticator a;
+	char resp[Maxchal];
+	int ret;
+
+	s = fss->ps;
+	switch(fss->phase){
+	default:
+		return phaseerror(fss, "write");
+
+	case CNeedChal:
+		if(n >= sizeof(s->chal))
+			return failure(fss, Ebadarg);
+		memset(s->chal, 0, sizeof s->chal);
+		memmove(s->chal, data, n);
+		s->challen = n;
+
+		if(s->astype == AuthChal)
+			ret = p9response(fss, s);
+		else
+			ret = vncresponse(fss, s);
+		if(ret != RpcOk)
+			return ret;
+		fss->phase = CHaveResp;
+		return RpcOk;
+
+	case SNeedResp:
+		/* send response to auth server and get ticket */
+		if(n > sizeof(resp))
+			return failure(fss, Ebadarg);
+		memset(resp, 0, sizeof resp);
+		memmove(resp, data, n);
+		if(write(s->asfd, resp, s->challen) != s->challen)
+			return failure(fss, Easproto);
+
+		/* get ticket plus authenticator from auth server */
+		if(_asrdresp(s->asfd, tbuf, TICKETLEN+AUTHENTLEN) < 0)
+			return failure(fss, nil);
+
+		/* check ticket */
+		convM2T(tbuf, &s->t, s->key->priv);
+		if(s->t.num != AuthTs
+		|| memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0)
+			return failure(fss, Easproto);
+		convM2A(tbuf+TICKETLEN, &a, s->t.key);
+		if(a.num != AuthAc
+		|| memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
+		|| a.id != 0)
+			return failure(fss, Easproto);
+
+		fss->haveai = 1;
+		fss->ai.cuid = s->t.cuid;
+		fss->ai.suid = s->t.suid;
+		fss->ai.nsecret = 0;
+		fss->ai.secret = nil;
+		fss->phase = Established;
+		return RpcOk;
+	}
+}
+
+static int
+getchal(State *s, Fsstate *fss)
+{
+	char trbuf[TICKREQLEN];
+	int n;
+
+	safecpy(s->tr.hostid, _str_findattr(s->key->attr, "user"), sizeof(s->tr.hostid));
+	safecpy(s->tr.authdom, _str_findattr(s->key->attr, "dom"), sizeof(s->tr.authdom));
+	s->tr.type = s->astype;
+	convTR2M(&s->tr, trbuf);
+
+	/* get challenge from auth server */
+	s->asfd = _authdial(nil, _str_findattr(s->key->attr, "dom"));
+	if(s->asfd < 0)
+		return failure(fss, Easproto);
+	if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+		return failure(fss, Easproto);
+	n = _asrdresp(s->asfd, s->chal, s->challen);
+	if(n <= 0){
+		if(n == 0)
+			werrstr("_asrdresp short read");
+		return failure(fss, nil);
+	}
+	s->challen = n;
+	fss->phase = SHaveChal;
+	return RpcOk;
+}
+
+Proto p9cr =
+{
+.name=		"p9cr",
+.init=		p9crinit,
+.write=		p9crwrite,
+.read=		p9crread,
+.close=		p9crclose,
+.keyprompt=	"user? !password?",
+};
+
+Proto vnc =
+{
+.name=		"vnc",
+.init=		p9crinit,
+.write=		p9crwrite,
+.read=		p9crread,
+.close=		p9crclose,
+.keyprompt=	"!password?",
+.addkey=	vncaddkey,
+};
blob - /dev/null
blob + 2828e708c879be1d6abcec0c76cc1e047606ef95 (mode 644)
--- /dev/null
+++ src/cmd/factotum/p9sk1.c
@@ -0,0 +1,352 @@
+/*
+ * p9sk1, p9sk2 - Plan 9 secret (private) key authentication.
+ * p9sk2 is an incomplete flawed variant of p9sk1.
+ *
+ * Client protocol:
+ *	write challenge[challen]	(p9sk1 only)
+ *	read tickreq[tickreqlen]
+ *	write ticket[ticketlen]
+ *	read authenticator[authentlen]
+ *
+ * Server protocol:
+ * 	read challenge[challen]	(p9sk1 only)
+ *	write tickreq[tickreqlen]
+ *	read ticket[ticketlen]
+ *	write authenticator[authentlen]
+ */
+
+#include "std.h"
+#include "dat.h"
+
+static int gettickets(Ticketreq*, char*, Key*);
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
+enum
+{
+	MAXAUTH = max(TICKREQLEN, TICKETLEN+max(TICKETLEN, AUTHENTLEN))
+};
+
+static int
+p9skclient(Conv *c)
+{
+	char *user;
+	char cchal[CHALLEN];
+	uchar secret[8];
+	char buf[MAXAUTH];
+	int speakfor, ret;
+	Attr *a;
+	Authenticator au;
+	Key *k;
+	Ticket t;
+	Ticketreq tr;
+
+	ret = -1;
+	a = nil;
+	k = nil;
+
+	/* p9sk1: send client challenge */
+	if(c->proto == &p9sk1){
+		c->state = "write challenge";
+		memrandom(cchal, CHALLEN);
+		if(convwrite(c, cchal, CHALLEN) < 0)
+			goto out;
+	}
+
+	/* read ticket request */
+	c->state = "read tickreq";
+	if(convread(c, buf, TICKREQLEN) < 0)
+		goto out;
+	convM2TR(buf, &tr);
+
+	/* p9sk2: use server challenge as client challenge */
+	if(c->proto == &p9sk2)
+		memmove(cchal, tr.chal, CHALLEN);
+
+	/*
+	 * find a key.
+	 *
+	 * if the user is the factotum owner, any key will do.
+	 * if not, then if we have a speakfor key,
+	 * we will only vouch for the user's local identity.
+	 *
+	 * this logic is duplicated in p9any.c
+	 */
+	user = strfindattr(c->attr, "user");
+	a = delattr(copyattr(c->attr), "role");
+	a = addattr(a, "proto=p9sk1");
+
+	if(strcmp(c->sysuser, owner) == 0){
+		speakfor = 0;
+		a = addattr(a, "proto=p9sk1 user? dom=%q", tr.authdom);
+	}else if(user==nil || strcmp(c->sysuser, user)==0){
+		speakfor = 1;
+		a = delattr(a, "user");
+		a = addattr(a, "proto=p9sk1 user? dom=%q role=speakfor", tr.authdom);
+	}else{
+		werrstr("will not authenticate for %q as %q", c->sysuser, user);
+		goto out;
+	}
+
+	for(;;){
+		c->state = "find key";
+		k = keyfetch(c, "%A", a);
+		if(k == nil)
+			goto out;
+		
+		/* relay ticket request to auth server, get tickets */
+		strcpy(tr.hostid, strfindattr(k->attr, "user"));
+		if(speakfor)
+			strcpy(tr.uid, c->sysuser);
+		else
+			strcpy(tr.uid, tr.hostid);
+
+		c->state = "get tickets";
+		if(gettickets(&tr, buf, k) < 0)
+			goto out;
+
+		convM2T(buf, &t, k->priv);
+		if(t.num == AuthTc)
+			break;
+
+		/* we don't agree with the auth server about the key; try again */
+		c->state = "replace key";
+		if((k = keyreplace(c, k, "key mismatch with auth server")) == nil){
+			werrstr("key mismatch with auth server");
+			goto out;
+		}
+	}
+
+	/* send second ticket and authenticator to server */
+	c->state = "write ticket+auth";
+	memmove(buf, buf+TICKETLEN, TICKETLEN);
+	au.num = AuthAc;
+	memmove(au.chal, tr.chal, CHALLEN);
+	au.id = 0;
+	convA2M(&au, buf+TICKETLEN, t.key);
+	if(convwrite(c, buf, TICKETLEN+AUTHENTLEN) < 0)
+		goto out;
+
+	/* read authenticator from server */
+	c->state = "read auth";
+	if(convread(c, buf, AUTHENTLEN) < 0)
+		goto out;
+	convM2A(buf, &au, t.key);
+	if(au.num != AuthAs || memcmp(au.chal, cchal, CHALLEN) != 0 || au.id != 0){
+		werrstr("server lies through his teeth");
+		goto out;
+	}
+
+	/* success */
+	c->attr = addcap(c->attr, c->sysuser, &t);
+	des56to64((uchar*)t.key, secret);
+	c->attr = addattr(c->attr, "secret=%.8H", secret);
+	ret = 0;
+
+out:
+	freeattr(a);
+	keyclose(k);
+	return ret;
+}
+
+static int
+p9skserver(Conv *c)
+{
+	char cchal[CHALLEN], buf[MAXAUTH];
+	uchar secret[8];
+	int ret;
+	Attr *a;
+	Authenticator au;
+	Key *k;
+	Ticketreq tr;
+	Ticket t;
+
+	ret = -1;
+
+	a = addattr(copyattr(c->attr), "user? dom?");
+	a = addattr(a, "user? dom? proto=p9sk1");
+	if((k = keyfetch(c, "%A", a)) == nil)
+		goto out;
+
+	/* p9sk1: read client challenge */
+	if(c->proto == &p9sk1){
+		if(convread(c, cchal, CHALLEN) < 0)
+			goto out;
+	}
+
+	/* send ticket request */
+	memset(&tr, 0, sizeof tr);
+	tr.type = AuthTreq;
+	strcpy(tr.authid, strfindattr(k->attr, "user"));
+	strcpy(tr.authdom, strfindattr(k->attr, "dom"));
+	memrandom(tr.chal, sizeof tr.chal);
+	convTR2M(&tr, buf);
+	if(convwrite(c, buf, TICKREQLEN) < 0)
+		goto out;
+
+	/* p9sk2: use server challenge as client challenge */
+	if(c->proto == &p9sk2)
+		memmove(cchal, tr.chal, sizeof tr.chal);
+
+	/* read ticket+authenticator */
+	if(convread(c, buf, TICKETLEN+AUTHENTLEN) < 0)
+		goto out;
+
+	convM2T(buf, &t, k->priv);
+	if(t.num != AuthTs || memcmp(t.chal, tr.chal, CHALLEN) != 0){
+		/* BUG badkey */
+		werrstr("key mismatch with auth server");
+		goto out;
+	}
+
+	convM2A(buf+TICKETLEN, &au, t.key);
+	if(au.num != AuthAc || memcmp(au.chal, tr.chal, CHALLEN) != 0 || au.id != 0){
+		werrstr("client lies through his teeth");
+		goto out;
+	}
+
+	/* send authenticator */
+	au.num = AuthAs;
+	memmove(au.chal, cchal, CHALLEN);
+	convA2M(&au, buf, t.key);
+	if(convwrite(c, buf, AUTHENTLEN) < 0)
+		goto out;
+
+	/* success */
+	c->attr = addcap(c->attr, c->sysuser, &t);
+	des56to64((uchar*)t.key, secret);
+	c->attr = addattr(c->attr, "secret=%.8H", secret);
+	ret = 0;
+
+out:
+	freeattr(a);
+	keyclose(k);
+	return ret;
+}
+
+int
+_asgetticket(int fd, char *trbuf, char *tbuf)
+{
+	if(write(fd, trbuf, TICKREQLEN) < 0){
+		close(fd);
+		return -1;
+	}
+	return _asrdresp(fd, tbuf, 2*TICKETLEN);
+}
+static int
+getastickets(Ticketreq *tr, char *buf)
+{
+	int asfd;
+	int ret;
+
+	if((asfd = xioauthdial(nil, tr->authdom)) < 0)
+		return -1;
+	convTR2M(tr, buf);
+	ret = xioasgetticket(asfd, buf, buf);
+	xioclose(asfd);
+	return ret;
+}
+
+static int
+mktickets(Ticketreq *tr, char *buf, Key *k)
+{
+	Ticket t;
+
+	if(strcmp(tr->authid, tr->hostid) != 0)
+		return -1;
+
+	memset(&t, 0, sizeof t);
+	memmove(t.chal, tr->chal, CHALLEN);
+	strcpy(t.cuid, tr->uid);
+	strcpy(t.suid, tr->uid);
+	memrandom(t.key, DESKEYLEN);
+	t.num = AuthTc;
+	convT2M(&t, buf, k->priv);
+	t.num = AuthTs;
+	convT2M(&t, buf+TICKETLEN, k->priv);
+	return 0;
+}
+
+static int
+gettickets(Ticketreq *tr, char *buf, Key *k)
+{
+	if(getastickets(tr, buf) == 0)
+		return 0;
+	if(mktickets(tr, buf, k) == 0)
+		return 0;
+	werrstr("gettickets: %r");
+	return -1;
+}
+
+static int
+p9sk1check(Key *k)
+{
+	char *user, *dom, *pass;
+	Ticketreq tr;
+
+	user = strfindattr(k->attr, "user");
+	dom = strfindattr(k->attr, "dom");
+	if(user==nil || dom==nil){
+		werrstr("need user and dom attributes");
+		return -1;
+	}
+	if(strlen(user) >= sizeof tr.authid){
+		werrstr("user name too long");
+		return -1;
+	}
+	if(strlen(dom) >= sizeof tr.authdom){
+		werrstr("auth dom name too long");
+		return -1;
+	}
+
+	k->priv = emalloc(DESKEYLEN);
+	if(pass = strfindattr(k->privattr, "!password"))
+		passtokey(k->priv, pass);
+	else if(pass = strfindattr(k->privattr, "!hex")){
+		if(hexparse(pass, k->priv, 7) < 0){
+			werrstr("malformed !hex key data");
+			return -1;
+		}
+	}else{
+		werrstr("need !password or !hex attribute");
+		return -1;
+	}
+
+	return 0;
+}
+
+static void
+p9sk1close(Key *k)
+{
+	free(k->priv);
+	k->priv = nil;
+}
+
+static Role
+p9sk1roles[] = 
+{
+	"client",	p9skclient,
+	"server",	p9skserver,
+	0
+};
+
+static Role
+p9sk2roles[] = 
+{
+	"client",	p9skclient,
+	"server",	p9skserver,
+	0
+};
+
+Proto p9sk1 = {
+.name=		"p9sk1",
+.roles=		p9sk1roles,
+.checkkey=	p9sk1check,
+.closekey=	p9sk1close,
+.keyprompt=	"user? dom? !password?",
+};
+
+Proto p9sk2 = {
+.name=		"p9sk2",
+.roles=		p9sk2roles,
+};
+
blob - /dev/null
blob + b3d4cb6ad2c87b9b7755d314e63f41bebde4c72a (mode 644)
--- /dev/null
+++ src/cmd/factotum/pass.c
@@ -0,0 +1,100 @@
+/*
+ * This is just a repository for a password.
+ * We don't want to encourage this, there's
+ * no server side.
+ */
+
+#include "dat.h"
+
+typedef struct State State;
+struct State 
+{
+	Key *key;
+};
+
+enum
+{
+	HavePass,
+	Maxphase,
+};
+
+static char *phasenames[Maxphase] =
+{
+[HavePass]	"HavePass",
+};
+
+static int
+passinit(Proto *p, Fsstate *fss)
+{
+	int ask;
+	Key *k;
+	State *s;
+
+	k = findkey(fss, Kuser, &ask, 0, fss->attr, "%s", p->keyprompt);
+	if(k == nil){
+		if(ask)
+			return RpcNeedkey;
+		return failure(fss, nil);
+	}
+	setattrs(fss->attr, k->attr);
+	s = emalloc(sizeof(*s));
+	s->key = k;
+	fss->ps = s;
+	return RpcOk;
+}
+
+static void
+passclose(Fsstate *fss)
+{
+	State *s;
+
+	s = fss->ps;
+	if(s->key)
+		closekey(s->key);
+	free(s);
+}
+
+static int
+passread(Fsstate *fss, void *va, uint *n)
+{
+	int m;
+	char buf[500];
+	char *pass, *user;
+	State *s;
+
+	s = fss->ps;
+	switch(fss->phase){
+	default:
+		return phaseerror(fss, "read");
+
+	case HavePass:
+		user = strfindattr(s->key->attr, "user");
+		pass = strfindattr(s->key->privattr, "!password");
+		if(user==nil || pass==nil)
+			return failure(fss, "passread cannot happen");
+		snprint(buf, sizeof buf, "%q %q", user, pass);
+		m = strlen(buf);
+		if(m > *n)
+			return toosmall(fss, m);
+		*n = m;
+		memmove(va, buf, m);
+		return RpcOk;
+	}
+}
+
+static int
+passwrite(Fsstate *fss, void*, uint)
+{
+	return phaseerror(fss, "write");
+}
+
+Proto pass =
+{
+.name=		"pass",
+.init=		passinit,
+.write=		passwrite,
+.read=		passread,
+.close=		passclose,
+.addkey=		replacekey,
+.keyprompt=	"user? !password?",
+};
blob - /dev/null
blob + 048c190663c4b0779df9e00ddfe99deda97b2540 (mode 644)
--- /dev/null
+++ src/cmd/factotum/plan9.c
@@ -0,0 +1,189 @@
+#include "std.h"
+#include "dat.h"
+#include <bio.h>
+
+int
+memrandom(void *p, int n)
+{
+	uchar *cp;
+
+	for(cp = (uchar*)p; n > 0; n--)
+		*cp++ = fastrand();
+	return 0;
+}
+
+/*
+ *  create a change uid capability 
+ */
+static int caphashfd;
+
+static char*
+mkcap(char *from, char *to)
+{
+	uchar rand[20];
+	char *cap;
+	char *key;
+	int nfrom, nto;
+	uchar hash[SHA1dlen];
+
+	if(caphashfd < 0)
+		return nil;
+
+	/* create the capability */
+	nto = strlen(to);
+	nfrom = strlen(from);
+	cap = emalloc(nfrom+1+nto+1+sizeof(rand)*3+1);
+	sprint(cap, "%s@%s", from, to);
+	memrandom(rand, sizeof(rand));
+	key = cap+nfrom+1+nto+1;
+	enc64(key, sizeof(rand)*3, rand, sizeof(rand));
+
+	/* hash the capability */
+	hmac_sha1((uchar*)cap, strlen(cap), (uchar*)key, strlen(key), hash, nil);
+
+	/* give the kernel the hash */
+	key[-1] = '@';
+	if(write(caphashfd, hash, SHA1dlen) < 0){
+		free(cap);
+		return nil;
+	}
+
+	return cap;
+}
+
+Attr*
+addcap(Attr *a, char *from, Ticket *t)
+{
+	char *cap;
+
+	cap = mkcap(from, t->suid);
+	return addattr(a, "cuid=%q suid=%q cap=%q", t->cuid, t->suid, cap);
+}
+
+/* bind in the default network and cs */
+static int
+bindnetcs(void)
+{
+	int srvfd;
+
+	if(access("/net/tcp", AEXIST) < 0)
+		bind("#I", "/net", MBEFORE);
+
+	if(access("/net/cs", AEXIST) < 0){
+		if((srvfd = open("#s/cs", ORDWR)) >= 0){
+			/* mount closes srvfd on success */
+			if(mount(srvfd, -1, "/net", MBEFORE, "") >= 0)
+				return 0;
+			close(srvfd);
+		}
+		return -1;
+	}
+	return 0;
+}
+
+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);
+}
+
+Key*
+plan9authkey(Attr *a)
+{
+	char *dom;
+	Key *k;
+
+	/*
+	 * The only important part of a is dom.
+	 * We don't care, for example, about user name.
+	 */
+	dom = strfindattr(a, "dom");
+	if(dom)
+		k = keylookup("proto=p9sk1 role=server user? dom=%q", dom);
+	else
+		k = keylookup("proto=p9sk1 role=server user? dom?");
+	if(k == nil)
+		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 */
+}
blob - /dev/null
blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644)
blob - /dev/null
blob + 64bb2e3411fe666d9858d2dd3e9cf2acccb4fe2a (mode 644)
--- /dev/null
+++ src/cmd/factotum/proto.c
@@ -0,0 +1,22 @@
+#include "std.h"
+#include "dat.h"
+
+Proto *prototab[] = {
+	&apop,
+	&cram,
+	&p9any,
+	&p9sk1,
+	&p9sk2,
+	nil,
+};
+
+Proto*
+protolookup(char *name)
+{
+	int i;
+
+	for(i=0; prototab[i]; i++)
+		if(strcmp(prototab[i]->name, name) == 0)
+			return prototab[i];
+	return nil;
+}
blob - /dev/null
blob + e9c163aa4eff3753961142f1b20fd319388158fc (mode 644)
--- /dev/null
+++ src/cmd/factotum/rpc.c
@@ -0,0 +1,315 @@
+#include "std.h"
+#include "dat.h"
+
+/*
+ * Factotum RPC
+ *
+ * Must be paired write/read cycles on /mnt/factotum/rpc.
+ * The format of a request is verb, single space, data.
+ * Data format is verb-dependent; in particular, it can be binary.
+ * The format of a response is the same.  The write only sets up
+ * the RPC.  The read tries to execute it.  If the /mnt/factotum/key
+ * file is open, we ask for new keys using that instead of returning
+ * an error in the RPC.  This means the read blocks.
+ * Textual arguments are parsed with tokenize, so rc-style quoting
+ * rules apply.
+ *
+ * Only authentication protocol messages go here.  Configuration
+ * is still via ctl (below).
+ *
+ * Request RPCs are:
+ *	start attrs - initializes protocol for authentication, can fail.
+ *		returns "ok read" or "ok write" on success.
+ *	read - execute protocol read
+ *	write - execute protocol write
+ *	authinfo - if the protocol is finished, return the AI if any
+ *	attr - return protocol information
+ * Return values are:
+ *	error message - an error happened.
+ *	ok [data] - success, possible data is request dependent.
+ *	needkey attrs - request aborted, get me this key and try again
+ *	badkey attrs - request aborted, this key might be bad
+ *	done [haveai] - authentication is done [haveai: you can get an ai with authinfo]
+ */
+
+char *rpcname[] = 
+{
+	"unknown",
+	"authinfo",
+	"attr",
+	"read",
+	"start",
+	"write",
+};
+
+static int
+classify(char *s)
+{
+	int i;
+
+	for(i=1; i<nelem(rpcname); i++)
+		if(strcmp(s, rpcname[i]) == 0)
+			return i;
+	return RpcUnknown;
+}
+
+int
+rpcwrite(Conv *c, void *data, int count)
+{
+	int op;
+	uchar *p;
+
+	if(count >= MaxRpc){
+		werrstr("rpc too large");
+		return -1;
+	}
+
+	/* cancel any current rpc */
+	c->rpc.op = RpcUnknown;
+	c->nreply = 0;
+
+	/* parse new rpc */
+	memmove(c->rpcbuf, data, count);
+	c->rpcbuf[count] = 0;
+	if(p = (uchar*)strchr((char*)c->rpcbuf, ' ')){
+		*p++ = '\0';
+		c->rpc.data = p;
+		c->rpc.count = count - (p - (uchar*)c->rpcbuf);
+	}else{
+		c->rpc.data = "";
+		c->rpc.count = 0;
+	}
+	op = classify(c->rpcbuf);
+	if(op == RpcUnknown){
+		werrstr("bad rpc verb: %s", c->rpcbuf);
+		return -1;
+	}
+
+	c->rpc.op = op;
+	return 0;
+}
+
+void
+convthread(void *v)
+{
+	Conv *c;
+	Attr *a;
+	char *role, *proto;
+	Proto *p;
+	Role *r;
+
+	c = v;
+	a = parseattr(c->rpc.data);
+	if(a == nil){
+		werrstr("empty attr");
+		goto out;
+	}
+	c->attr = a;
+	proto = strfindattr(a, "proto");
+	role = strfindattr(a, "role");
+
+	if(proto == nil){
+		werrstr("no proto in attrs");
+		goto out;
+	}
+	if(role == nil){
+		werrstr("no role in attrs");
+		goto out;
+	}
+
+	p = protolookup(proto);
+	if(p == nil){
+		werrstr("unknown proto %s", proto);
+		goto out;
+	}
+
+	c->proto = p;
+	for(r=p->roles; r->name; r++){
+		if(strcmp(r->name, role) != 0)
+			continue;
+		rpcrespond(c, "ok");
+		c->active = 1;
+		if((*r->fn)(c) == 0){
+			c->done = 1;
+			werrstr("protocol finished");
+		}else
+			werrstr("%s %s %s: %r", p->name, r->name, c->state);
+		goto out;
+	}
+	werrstr("unknown role");
+
+out:
+	c->active = 0;
+	c->state = 0;
+	rerrstr(c->err, sizeof c->err);
+	rpcrespond(c, "error %r");
+	convclose(c);
+}
+
+static uchar* convAI2M(uchar *p, int n, char *cuid, char *suid, char *cap, char *hex);
+
+void
+rpcexec(Conv *c)
+{
+	uchar *p;
+
+	switch(c->rpc.op){
+	case RpcRead:
+		if(c->rpc.count > 0){
+			rpcrespond(c, "error read takes no parameters");
+			break;
+		}
+		/* fall through */
+	default:
+		if(!c->active){
+			if(c->done)
+				rpcrespond(c, "done");
+			else
+				rpcrespond(c, "error %s", c->err);
+			break;
+		}
+		nbsendp(c->rpcwait, 0);
+		break;
+	case RpcUnknown:
+		break;
+	case RpcAuthinfo:
+		/* deprecated */
+		if(c->active)
+			rpcrespond(c, "error conversation still active");
+		else if(!c->done)
+			rpcrespond(c, "error conversation not successful");
+		else{
+			/* make up an auth info using the attr */
+			p = convAI2M((uchar*)c->reply+3, sizeof c->reply-3, 
+				strfindattr(c->attr, "cuid"),
+				strfindattr(c->attr, "suid"),
+				strfindattr(c->attr, "cap"),
+				strfindattr(c->attr, "secret"));
+			if(p == nil)
+				rpcrespond(c, "error %r");
+			else
+				rpcrespondn(c, "ok", c->reply+3, p-(uchar*)(c->reply+3));
+		}
+		break;
+	case RpcAttr:
+		rpcrespond(c, "ok %A", c->attr);
+		break;
+	case RpcStart:
+		convreset(c);
+		c->ref++;
+		threadcreate(convthread, c, STACK);
+		break;
+	}
+}
+
+void
+rpcrespond(Conv *c, char *fmt, ...)
+{
+	va_list arg;
+
+	if(c->hangup)
+		return;
+
+	if(fmt == nil)
+		fmt = "";
+
+	va_start(arg, fmt);
+	c->nreply = vsnprint(c->reply, sizeof c->reply, fmt, arg);
+	va_end(arg);
+	(*c->kickreply)(c);
+	c->rpc.op = RpcUnknown;
+}
+
+void
+rpcrespondn(Conv *c, char *verb, void *data, int count)
+{
+	char *p;
+
+	if(c->hangup)
+		return;
+
+	if(strlen(verb)+1+count > sizeof c->reply){
+		print("RPC response too large; caller %#lux", getcallerpc(&c));
+		return;
+	}
+
+	strcpy(c->reply, verb);
+	p = c->reply + strlen(c->reply);
+	*p++ = ' ';
+	memmove(p, data, count);
+	c->nreply = count + (p - c->reply);
+	(*c->kickreply)(c);
+	c->rpc.op = RpcUnknown;
+}
+
+/* deprecated */
+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;
+}
+
+static uchar*
+convAI2M(uchar *p, int n, char *cuid, char *suid, char *cap, char *hex)
+{
+	uchar *e = p+n;
+	uchar *secret;
+	int nsecret;
+
+	if(cuid == nil)
+		cuid = "";
+	if(suid == nil)
+		suid = "";
+	if(cap == nil)
+		cap = "";
+	if(hex == nil)
+		hex = "";
+	nsecret = strlen(hex)/2;
+	secret = emalloc(nsecret);
+	if(hexparse(hex, secret, nsecret) < 0){
+		werrstr("hexparse %s failed", hex);	/* can't happen */
+		free(secret);
+		return nil;
+	}
+	p = pstring(p, e, cuid);
+	p = pstring(p, e, suid);
+	p = pstring(p, e, cap);
+	p = pcarray(p, e, secret, nsecret);
+	free(secret);
+	if(p == nil)
+		werrstr("authinfo too big");
+	return p;
+}
+
blob - /dev/null
blob + 4c88bfe655f0f4a62326110184f1a3ff1a1c95a6 (mode 644)
--- /dev/null
+++ src/cmd/factotum/ssh.c
@@ -0,0 +1,135 @@
+#include "dat.h"
+#include <mp.h>
+#include <libsec.h>
+
+typedef struct Sshrsastate Sshrsastate;
+
+enum {
+	CReadpub,
+	CWritechal,
+	CReadresp,
+};
+struct State
+{
+	RSApriv *priv;
+	Key *k;
+	mpint *resp;
+	int phase;
+};
+
+static RSApriv*
+readrsapriv(char *s)
+{
+	RSApriv *priv;
+
+	priv = rsaprivalloc();
+
+	strtoul(s, &s, 10);
+	if((priv->pub.ek=strtomp(s, &s, 16, nil)) == nil)
+		goto Error;
+	if((priv->dk=strtomp(s, &s, 16, nil)) == nil)
+		goto Error;
+	if((priv->pub.n=strtomp(s, &s, 16, nil)) == nil)
+		goto Error;
+	if((priv->p=strtomp(s, &s, 16, nil)) == nil)
+		goto Error;
+	if((priv->q=strtomp(s, &s, 16, nil)) == nil)
+		goto Error;
+	if((priv->kp=strtomp(s, &s, 16, nil)) == nil)
+		goto Error;
+	if((priv->kq=strtomp(s, &s, 16, nil)) == nil)
+		goto Error;
+	if((priv->c2=strtomp(s, &s, 16, nil)) == nil)
+		goto Error;
+
+	return priv;
+
+Error:
+	rsaprivfree(priv);
+	return nil;
+}
+
+int
+sshinit(Fsstate *fss, 
+sshrsaopen(Key *k, char*, int client)
+{
+	Sshrsastate *s;
+
+	fmtinstall('B', mpconv);
+	assert(client);
+	s = emalloc(sizeof *s);
+	s->priv = readrsapriv(s_to_c(k->data));
+	s->k = k;
+	if(s->priv == nil){
+		agentlog("error parsing ssh key %s", k->file);
+		free(s);
+		return nil;
+	}
+	return s;
+}
+
+int
+sshrsaread(void *va, void *buf, int n)
+{
+	Sshrsastate *s;
+
+	s = va;
+	switch(s->phase){
+	case Readpub:
+		s->phase = Done;
+		return snprint(buf, n, "%B", s->priv->pub.n);
+	case Readresp:
+		s->phase = Done;
+		return snprint(buf, n, "%B", s->resp);
+	default:
+		return 0;
+	}
+}
+
+int
+sshrsawrite(void *va, void *vbuf, int n)
+{
+	mpint *m;
+	char *buf;
+	Sshrsastate *s;
+
+	s = va;
+	if((s->k->flags&Fconfirmuse) && confirm("ssh use") < 0)
+		return -1;
+
+	buf = emalloc(n+1);
+	memmove(buf, vbuf, n);
+	buf[n] = '\0';
+	m = strtomp(buf, nil, 16, nil);
+	free(buf);
+	if(m == nil){
+		werrstr("bad bignum");
+		return -1;
+	}
+
+	agentlog("ssh use");
+	m = rsadecrypt(s->priv, m, m);
+	s->resp = m;
+	s->phase = Readresp;
+	return n;
+}
+
+void
+sshrsaclose(void *v)
+{
+	Sshrsastate *s;
+
+	s = v;
+	rsaprivfree(s->priv);
+	mpfree(s->resp);
+	free(s);
+}
+
+Proto sshrsa = {
+.name=	"ssh-rsa",
+.perm=	0666,
+.open=	sshrsaopen,
+.read=	sshrsaread,
+.write=	sshrsawrite,
+.close=	sshrsaclose,
+};
blob - /dev/null
blob + 7227c85566a54cece660b3900a723ec8ab3a92cc (mode 644)
--- /dev/null
+++ src/cmd/factotum/sshrsa.c
@@ -0,0 +1,172 @@
+/*
+ * SSH RSA authentication.
+ * 
+ * Client protocol:
+ *	read public key
+ *		if you don't like it, read another, repeat
+ *	write challenge
+ *	read response
+ * all numbers are hexadecimal biginits parsable with strtomp.
+ */
+
+#include "dat.h"
+
+enum {
+	CHavePub,
+	CHaveResp,
+
+	Maxphase,
+};
+
+static char *phasenames[] = {
+[CHavePub]	"CHavePub",
+[CHaveResp]	"CHaveResp",
+};
+
+struct State
+{
+	RSApriv *priv;
+	mpint *resp;
+	int off;
+	Key *key;
+};
+
+static RSApriv*
+readrsapriv(Key *k)
+{
+	char *a;
+	RSApriv *priv;
+
+	priv = rsaprivalloc();
+
+	if((a=strfindattr(k->attr, "ek"))==nil || (priv->pub.ek=strtomp(a, nil, 16, nil))==nil)
+		goto Error;
+	if((a=strfindattr(k->attr, "n"))==nil || (priv->pub.n=strtomp(a, nil, 16, nil))==nil)
+		goto Error;
+	if((a=strfindattr(k->privattr, "!p"))==nil || (priv->p=strtomp(a, nil, 16, nil))==nil)
+		goto Error;
+	if((a=strfindattr(k->privattr, "!q"))==nil || (priv->q=strtomp(a, nil, 16, nil))==nil)
+		goto Error;
+	if((a=strfindattr(k->privattr, "!kp"))==nil || (priv->kp=strtomp(a, nil, 16, nil))==nil)
+		goto Error;
+	if((a=strfindattr(k->privattr, "!kq"))==nil || (priv->kq=strtomp(a, nil, 16, nil))==nil)
+		goto Error;
+	if((a=strfindattr(k->privattr, "!c2"))==nil || (priv->c2=strtomp(a, nil, 16, nil))==nil)
+		goto Error;
+	if((a=strfindattr(k->privattr, "!dk"))==nil || (priv->dk=strtomp(a, nil, 16, nil))==nil)
+		goto Error;
+	return priv;
+
+Error:
+	rsaprivfree(priv);
+	return nil;
+}
+
+static int
+sshrsainit(Proto*, Fsstate *fss)
+{
+	int iscli;
+	State *s;
+
+	if((iscli = isclient(strfindattr(fss->attr, "role"))) < 0)
+		return failure(fss, nil);
+	if(iscli==0)
+		return failure(fss, "sshrsa server unimplemented");
+
+	s = emalloc(sizeof *s);
+	fss->phasename = phasenames;
+	fss->maxphase = Maxphase;
+	fss->phase = CHavePub;
+	fss->ps = s;
+	return RpcOk;
+}
+
+static int
+sshrsaread(Fsstate *fss, void *va, uint *n)
+{
+	RSApriv *priv;
+	State *s;
+
+	s = fss->ps;
+	switch(fss->phase){
+	default:
+		return phaseerror(fss, "read");
+	case CHavePub:
+		if(s->key){
+			closekey(s->key);
+			s->key = nil;
+		}
+		if((s->key = findkey(fss, Kuser, nil, s->off, fss->attr, nil)) == nil)
+			return failure(fss, nil);
+		s->off++;
+		priv = s->key->priv;
+		*n = snprint(va, *n, "%B", priv->pub.n);
+		return RpcOk;
+	case CHaveResp:
+		*n = snprint(va, *n, "%B", s->resp);
+		fss->phase = Established;
+		return RpcOk;
+	}
+}
+
+static int
+sshrsawrite(Fsstate *fss, void *va, uint)
+{
+	mpint *m;
+	State *s;
+
+	s = fss->ps;
+	switch(fss->phase){
+	default:
+		return phaseerror(fss, "write");
+	case CHavePub:
+		if(s->key == nil)
+			return failure(fss, "no current key");
+		m = strtomp(va, nil, 16, nil);
+		m = rsadecrypt(s->key->priv, m, m);
+		s->resp = m;
+		fss->phase = CHaveResp;
+		return RpcOk;
+	}
+}
+
+static void
+sshrsaclose(Fsstate *fss)
+{
+	State *s;
+
+	s = fss->ps;
+	if(s->key)
+		closekey(s->key);
+	if(s->resp)
+		mpfree(s->resp);
+	free(s);
+}
+
+static int
+sshrsaaddkey(Key *k)
+{
+	fmtinstall('B', mpconv);
+
+	if((k->priv = readrsapriv(k)) == nil){
+		werrstr("malformed key data");
+		return -1;
+	}
+	return replacekey(k);
+}
+
+static void
+sshrsaclosekey(Key *k)
+{
+	rsaprivfree(k->priv);
+}
+
+Proto sshrsa = {
+.name=	"sshrsa",
+.init=		sshrsainit,
+.write=	sshrsawrite,
+.read=	sshrsaread,
+.close=	sshrsaclose,
+.addkey=	sshrsaaddkey,
+.closekey=	sshrsaclosekey,
+};
blob - /dev/null
blob + 814664e0fb482cf11e5da201fb5f5c63d6b5b17a (mode 644)
--- /dev/null
+++ src/cmd/factotum/std.h
@@ -0,0 +1,10 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <authsrv.h>
+#include <mp.h>
+#include <libsec.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+
blob - /dev/null
blob + b41048980556fe7a81615ffbf1af9b703d5757b0 (mode 644)
--- /dev/null
+++ src/cmd/factotum/test.c
@@ -0,0 +1,121 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+typedef struct Test Test;
+
+struct Test
+{
+	char *name;
+	int (*server)(Test*, AuthRpc*, int);
+	int (*client)(Test*, int);
+};
+
+int
+ai2status(AuthInfo *ai)
+{
+	if(ai == nil)
+		return -1;
+	auth_freeAI(ai);
+	return 0;
+}
+
+int
+proxyserver(Test *t, AuthRpc *rpc, int fd)
+{
+	char buf[1024];
+
+	sprint(buf, "proto=%q role=server", t->name);
+	return ai2status(fauth_proxy(fd, rpc, nil, buf));
+}
+
+int
+proxyclient(Test *t, int fd)
+{
+	return ai2status(auth_proxy(fd, auth_getkey, "proto=%q role=client", t->name));
+}
+
+Test test[] =
+{
+	"apop",		proxyserver,		proxyclient,
+	"cram",		proxyserver,		proxyclient,
+	"p9sk1",		proxyserver,		proxyclient,
+	"p9sk2",		proxyserver,		proxyclient,
+	"p9any",		proxyserver,		proxyclient,
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: test [name]...\n");
+	exits("usage");
+}
+
+void
+runtest(AuthRpc *srpc, Test *t)
+{
+	int p[2], bad;
+	Waitmsg *w;
+
+	if(pipe(p) < 0)
+		sysfatal("pipe: %r");
+
+	print("%s...", t->name);
+
+	switch(fork()){
+	case -1:
+		sysfatal("fork: %r");
+
+	case 0:
+		close(p[0]);
+		if((*t->server)(t, srpc, p[1]) < 0){
+			print("\n\tserver: %r");
+			_exits("oops");
+		}
+		close(p[1]);
+		_exits(nil);
+	default:
+		close(p[1]);
+		if((*t->client)(t, p[0]) < 0){
+			print("\n\tclient: %r");
+			bad = 1;
+		}
+		close(p[0]);
+		break;
+	}
+	w = wait();
+	if(w->msg[0])
+		bad = 1;
+	print("\n");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, j;
+	int afd;
+	AuthRpc *srpc;
+
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+
+	quotefmtinstall();
+	afd = open("/n/kremvax/factotum/rpc", ORDWR);
+	if(afd < 0)
+		sysfatal("open /n/kremvax/factotum/rpc: %r");
+	srpc = auth_allocrpc(afd);
+	if(srpc == nil)
+		sysfatal("auth_allocrpc: %r");
+
+	if(argc == 0)
+		for(i=0; i<nelem(test); i++)
+			runtest(srpc, &test[i]);
+	else
+		for(i=0; i<argc; i++)
+			for(j=0; j<nelem(test); j++)
+				if(strcmp(argv[i], test[j].name) == 0)
+					runtest(srpc, &test[j]);
+	exits(nil);
+}
blob - /dev/null
blob + 2f9f9b1d80dee38976f76647d3257f6ca1ae5ca0 (mode 755)
--- /dev/null
+++ src/cmd/factotum/testsetup
@@ -0,0 +1,11 @@
+#!/bin/rc
+
+slay 8.out|rc
+8.out $* -s fact.s -m /n/kremvax
+8.out $* -s fact.c
+ramfs -m /n/sid >[2]/dev/null
+auth/aescbc -d < /usr/rsc/lib/factotum.aes >/n/sid/all
+read -m /n/sid/all >/n/kremvax/factotum/ctl
+read -m /n/sid/all >/mnt/factotum/ctl
+unmount /n/sid
+
blob - /dev/null
blob + 54b335190dc97925bd6c84a492a2341613e635a9 (mode 644)
--- /dev/null
+++ src/cmd/factotum/util.c
@@ -0,0 +1,52 @@
+#include "std.h"
+#include "dat.h"
+
+static int
+unhex(char c)
+{
+	if('0' <= c && c <= '9')
+		return c-'0';
+	if('a' <= c && c <= 'f')
+		return c-'a'+10;
+	if('A' <= c && c <= 'F')
+		return c-'A'+10;
+	abort();
+	return -1;
+}
+
+int
+hexparse(char *hex, uchar *dat, int ndat)
+{
+	int i, n;
+
+	n = strlen(hex);
+	if(n%2)
+		return -1;
+	n /= 2;
+	if(n > ndat)
+		return -1;
+	if(hex[strspn(hex, "0123456789abcdefABCDEF")] != '\0')
+		return -1;
+	for(i=0; i<n; i++)
+		dat[i] = (unhex(hex[2*i])<<4)|unhex(hex[2*i+1]);
+	return n;
+}
+
+char*
+estrappend(char *s, char *fmt, ...)
+{
+	char *t;
+	va_list arg;
+
+	va_start(arg, fmt);
+	t = vsmprint(fmt, arg);
+	if(t == nil)
+		sysfatal("out of memory");
+	va_end(arg);
+	s = erealloc(s, strlen(s)+strlen(t)+1);
+	strcat(s, t);
+	free(t);
+	return s;
+}
+
+
blob - /dev/null
blob + 3bedfdd45bc9e8998736da056c57fea47421b402 (mode 644)
--- /dev/null
+++ src/cmd/factotum/x.c
@@ -0,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+void
+f(void*)
+{
+}
+
+void
+main(void)
+{
+	f(auth_challenge);
+	f(auth_response);
+}
blob - /dev/null
blob + 508908476a0a4d8845e0f34c2f4a6de175c920e4 (mode 644)
--- /dev/null
+++ src/cmd/factotum/xio.c
@@ -0,0 +1,165 @@
+#include "std.h"
+#include "dat.h"
+
+static Ioproc *cache[5];
+static int ncache;
+
+static Ioproc*
+xioproc(void)
+{
+	Ioproc *c;
+	int i;
+	
+	for(i=0; i<ncache; i++){
+		if(c = cache[i]){
+			cache[i] = nil;
+			return c;
+		}
+	}
+
+	return ioproc();
+}
+
+static void
+closexioproc(Ioproc *io)
+{
+	int i;
+
+	for(i=0; i<ncache; i++)
+		if(cache[i] == nil){
+			cache[i] = io;
+			return;
+		}
+
+	closeioproc(io);
+}
+
+int
+xiodial(char *ds, char *local, char *dir, int *cfdp)
+{
+	int fd;
+	Ioproc *io;
+
+	if((io = xioproc()) == nil)
+		return -1;
+	fd = iodial(io, ds, local, dir, cfdp);
+	closexioproc(io);
+	return fd;
+}
+
+void
+xioclose(int fd)
+{
+	Ioproc *io;
+
+	if((io = xioproc()) == nil){
+		close(fd);
+		return;
+	}
+
+	ioclose(io, fd);
+	closexioproc(io);
+}
+
+int
+xiowrite(int fd, void *v, int n)
+{
+	int m;
+	Ioproc *io;
+
+	if((io = xioproc()) == nil)
+		return -1;
+	m = iowrite(io, fd, v, n);
+	closexioproc(io);
+	if(m != n)
+		return -1;
+	return n;
+}
+
+static long
+_ioauthdial(va_list *arg)
+{
+	char *net;
+	char *dom;
+	int fd;
+
+	net = va_arg(*arg, char*);
+	dom = va_arg(*arg, char*);
+	fd = _authdial(net, dom);
+	if(fd < 0)
+		fprint(2, "authdial: %r");
+	return fd;
+}
+
+int
+xioauthdial(char *net, char *dom)
+{
+	int fd;
+	Ioproc *io;
+
+	if((io = xioproc()) == nil)
+		return -1;
+	fd = iocall(io, _ioauthdial, net, dom);
+	closexioproc(io);
+	return fd;
+}
+
+static long
+_ioasrdresp(va_list *arg)
+{
+	int fd;
+	void *a;
+	int n;
+
+	fd = va_arg(*arg, int);
+	a = va_arg(*arg, void*);
+	n = va_arg(*arg, int);
+
+	return _asrdresp(fd, a, n);
+}
+
+int
+xioasrdresp(int fd, void *a, int n)
+{
+	Ioproc *io;
+
+	if((io = xioproc()) == nil)
+		return -1;
+
+	n = iocall(io, _ioasrdresp, fd, a, n);
+	closexioproc(io);
+	return n;
+}
+
+static long
+_ioasgetticket(va_list *arg)
+{
+	int asfd;
+	char *trbuf;
+	char *tbuf;
+
+	asfd = va_arg(*arg, int);
+	trbuf = va_arg(*arg, char*);
+	tbuf = va_arg(*arg, char*);
+
+	return _asgetticket(asfd, trbuf, tbuf);
+}
+
+int
+xioasgetticket(int fd, char *trbuf, char *tbuf)
+{
+	int n;
+	Ioproc *io;
+
+	if((io = xioproc()) == nil)
+		return -1;
+
+	n = iocall(io, _ioasgetticket, fd, trbuf, tbuf);
+	closexioproc(io);
+	if(n != 2*TICKETLEN)
+		n = -1;
+	else
+		n = 0;
+	return n;
+}
+
blob - bf7200de1bdbaf42194a4ff75668cd136a63e357
blob + 3ad7ea7a99c444c1502a21d18c3da4b6cb8f9339
--- src/cmd/mkfile
+++ src/cmd/mkfile
@@ -7,7 +7,7 @@ SHORTLIB=sec fs mux regexp9 thread bio 9
 
 <$PLAN9/src/mkmany
 
-BUGGERED='CVS|faces|factotum|oplumb|plumb2|mk|vac|9term|upas|venti|htmlfmt'
+BUGGERED='CVS|9term|faces|factotum|htmlfmt|mk|rio|upas|vac|venti'
 DIRS=`ls -l |sed -n 's/^d.* //p' |egrep -v "^($BUGGERED)$"`
 
 <$PLAN9/src/mkdirs
blob - 54c605215e8c72f632c57eecb53c9eb08da93d11
blob + 80a57af3ee40e134770b1f70576ceb9cbc2fca67
--- src/cmd/plumb/plumber.c
+++ src/cmd/plumb/plumber.c
@@ -54,6 +54,8 @@ threadmain(int argc, char *argv[])
 		error("can't initialize $user or $home: %r");
 	if(plumbfile == nil){
 		sprint(buf, "%s/lib/plumbing", home);
+		if(access(buf, 0) < 0)
+			sprint(buf, "#9/plumb/initial.plumbing");
 		plumbfile = estrdup(buf);
 	}
 
blob - 6e9d59f130195f3ca6a1509a1cec1e72d17b1ac6
blob + eb93a4f5280d82b0e65ba73d76180f4eff03644c
--- src/cmd/rc/plan9ish.c
+++ src/cmd/rc/plan9ish.c
@@ -81,7 +81,7 @@ void Vinit(void){
 		for(s=*env;*s && *s!='(' && *s!='=';s++);
 		switch(*s){
 		case '\0':
-			pfmt(err, "environment %q?\n", *env);
+			pfmt(err, "rc: odd environment %q?\n", *env);
 			break;
 		case '=':
 			*s='\0';
blob - c81b6280deb406f009469d8a787282dc2a578827
blob + 7df5fea016df13f2bacc0c8361bf93ae952411e9
--- src/cmd/rc/rc.h
+++ src/cmd/rc/rc.h
@@ -26,6 +26,9 @@
 #include "x.tab.h"
 #endif
 #endif
+
+#undef pipe	/* so that /dev/fd works */
+
 typedef struct tree tree;
 typedef struct word word;
 typedef struct io io;
blob - 7c4ed39b86faba29c867d48a6837407cdf9435d7
blob + f8614d3f9a7f6357f28530d95119178efab278f7
--- src/cmd/samterm/plan9.c
+++ src/cmd/samterm/plan9.c
@@ -10,6 +10,12 @@
 #include <cursor.h>
 #include <keyboard.h>
 #include <frame.h>
+#define Tversion Tversion9p
+#define Twrite Twrite9p
+#include <fcall.h>
+#undef Tversion
+#undef Twrite
+#include <fs.h>
 #include <plumb.h>
 #include "flayer.h"
 #include "samterm.h"
@@ -212,27 +218,22 @@ plumbformat(Plumbmsg *m, int i)
 }
 
 void
-plumbproc(void *argv)
+plumbproc(void *arg)
 {
-	Channel *c;
-	int i, *fdp;
-	void **arg;
+	Fid *fid;
+	int i;
 	Plumbmsg *m;
 
-	arg = argv;
-	c = arg[0];
-	fdp = arg[1];
-
-	i = 0;
-	threadfdnoblock(*fdp);
+	fid = arg;
+	i = 0;
 	for(;;){
-		m = threadplumbrecv(*fdp);
+		m = plumbrecvfid(fid);
 		if(m == nil){
 			fprint(2, "samterm: plumb read error: %r\n");
 			threadexits("plumb");	/* not a fatal error */
 		}
 		if(plumbformat(m, i)){
-			send(c, &i);
+			send(plumbc, &i);
 			i = 1-i;	/* toggle */
 		}
 	}
@@ -241,21 +242,18 @@ plumbproc(void *argv)
 int
 plumbstart(void)
 {
-	static int fd;
-	static void *arg[2];
+	Fid *fid;
 
 	plumbfd = plumbopen("send", OWRITE|OCEXEC);	/* not open is ok */
-	fd = plumbopen("edit", OREAD|OCEXEC);
-	if(fd < 0)
+	fid = plumbopenfid("edit", OREAD|OCEXEC);
+	if(fid == nil)
 		return -1;
 	plumbc = chancreate(sizeof(int), 0);
 	if(plumbc == nil){
-		close(fd);
+		fsclose(fid);
 		return -1;
 	}
-	arg[0] = plumbc;
-	arg[1] = &fd;
-	threadcreate(plumbproc, arg, STACK);
+	threadcreate(plumbproc, fid, STACK);
 	return 1;
 }
 
blob - f6efb917114a5736bdfed1cd41b029a823559a18
blob + a10ce3d0bd5f2a9c09e218568d54aa5868959f3a
--- src/cmd/vac/vac.c
+++ src/cmd/vac/vac.c
@@ -1,8 +1,11 @@
+#include <sys/stat.h>
 #include "stdinc.h"
 #include "vac.h"
 #include "dat.h"
 #include "fns.h"
 
+int mainstacksize = 128*1024;
+
 typedef struct Sink Sink;
 typedef struct MetaSink MetaSink;
 typedef struct DirSink DirSink;
@@ -170,6 +173,9 @@ threadmain(int argc, char *argv[])
 		break;
 	}ARGEND;
 
+	if(argc == 0)
+		usage();
+
 	if(bsize < 512)
 		bsize = 512;
 	if(bsize > VtMaxLumpSize)
@@ -215,8 +221,6 @@ vacwrite(VtConn *z, uchar score[VtScoreSize], int type
 		sha1(buf, n, score, nil);
 		return 0;
 	}
-sha1(buf, n, score, nil);
-fprint(2, "write %V %d\n", score, type);
 	return vtwrite(z, score, type, buf, n);
 }
 
@@ -374,6 +378,18 @@ isexcluded(char *name)
 		else /* x > 0 */
 			top = i;
 	}
+	return 0;
+}
+
+static int
+islink(char *name)
+{
+	struct stat st;
+
+	if(lstat(name, &st) < 0)
+		return 0;
+	if((st.st_mode&S_IFMT) == S_IFLNK)
+		return 1;
 	return 0;
 }
 
@@ -393,6 +409,9 @@ vacfile(DirSink *dsink, char *lname, char *sname, VacF
 	if(merge && vacmerge(dsink, lname, sname) >= 0)
 		return;
 
+	if(islink(sname))
+		return;
+
 	fd = open(sname, OREAD);
 	if(fd < 0) {
 		warn("could not open file: %s: %r", lname);
@@ -820,10 +839,8 @@ sinkclose(Sink *k)
 		if(k->pbuf[n] > k->buf + kd->psize*n)
 			break;
 
-fprint(2, "type %d -> ", kd->type);
 	base = kd->type&~VtTypeDepthMask;
 	kd->type = base + sizetodepth(kd->size, kd->psize, kd->dsize);
-fprint(2, "%d ", kd->type);
 
 	/* skip full part of tree */
 	for(i=0; i<n && k->pbuf[i] == k->buf + kd->psize*i; i++)
@@ -831,7 +848,6 @@ fprint(2, "%d ", kd->type);
 
 	/* is the tree completely full */
 	if(i == n && k->pbuf[n] == k->buf + kd->psize*n + VtScoreSize) {
-fprint(2, "full\n");
 		memmove(kd->score, k->pbuf[n] - VtScoreSize, VtScoreSize);
 		return;
 	}
@@ -846,7 +862,6 @@ fprint(2, "full\n");
 		k->pbuf[i+1] += VtScoreSize;
 	}
 	memmove(kd->score, k->pbuf[i] - VtScoreSize, VtScoreSize);
-fprint(2, "%V\n", kd->score);
 }
 
 void
@@ -881,7 +896,6 @@ dirsinkwrite(DirSink *k, VtEntry *dir)
 		sinkwrite(k->sink, k->buf, k->p - k->buf);
 		k->p = k->buf;
 	}
-fprint(2, "write entry %V %d\n", dir->score, dir->type);
 	vtentrypack(dir, k->p, 0);
 	k->nentry++;
 	k->p += VtEntrySize;
blob - 2a2d1391338543560e7cb5209cfa0016dc0c26b2
blob + 384196cf4281d0bd9351fbc66b9713726f855801
--- src/lib9/getenv.c
+++ src/lib9/getenv.c
@@ -22,6 +22,5 @@ p9putenv(char *s, char *v)
 	if(t == nil)
 		return -1;
 	putenv(t);
-	free(t);
 	return 0;
 }
blob - 7824ff18d26d8d74d03f3c45d196d522416275ac
blob + d388d224e9787a3328043b613193caede3d1ab22
--- src/lib9/mkfile
+++ src/lib9/mkfile
@@ -107,6 +107,8 @@ LIB9OFILES=\
 	getuser.$O\
 	getwd.$O\
 	jmp.$O\
+	lrand.$O\
+	lnrand.$O\
 	lock.$O\
 	main.$O\
 	malloc.$O\
@@ -119,6 +121,7 @@ LIB9OFILES=\
 	nrand.$O\
 	nulldir.$O\
 	open.$O\
+	opentemp.$O\
 	pipe.$O\
 	post9p.$O\
 	postnote.$O\
blob - f9fe2420e57a5ca68c6c61a38f3e54f4ac7879e7
blob + 01b244ef61434728657aa4b68ced75f51612b0da
--- src/lib9/pipe.c
+++ src/lib9/pipe.c
@@ -3,6 +3,11 @@
 #include <libc.h>
 #include <sys/socket.h>
 
+/*
+ * We use socketpair to get a two-way pipe.
+ * The pipe still doesn't preserve message boundaries.
+ * Worse, it cannot be reopened via /dev/fd/NNN on Linux.
+ */
 int
 p9pipe(int fd[2])
 {
blob - /dev/null
blob + e8313be1e4a67ba2b74b2e8b5900f26c19648628 (mode 644)
--- /dev/null
+++ src/lib9p/_post.c
@@ -0,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <auth.h>
+#include "post.h"
+
+Postcrud*
+_post1(Srv *s, char *name, char *mtpt, int flag)
+{
+	Postcrud *p;
+
+	p = emalloc9p(sizeof *p);
+	if(!s->nopipe){
+		if(pipe(p->fd) < 0)
+			sysfatal("pipe: %r");
+		s->infd = s->outfd = p->fd[1];
+		s->srvfd = p->fd[0];
+	}
+	if(name)
+		if(postfd(name, s->srvfd) < 0)
+			sysfatal("postfd %s: %r", name);
+	p->s = s;
+	p->mtpt = mtpt;
+	p->flag = flag;
+	return p;
+}
+
+void
+_post2(void *v)
+{
+	Srv *s;
+
+	s = v;
+	rfork(RFNOTEG);
+	if(!s->leavefdsopen){
+		rendezvous((ulong)s, 0);
+		close(s->srvfd);
+	}
+	srv(s);
+}
+
+void
+_post3(Postcrud *p)
+{
+	/*
+	 * Normally the server is posting as the last thing it does
+	 * before exiting, so the correct thing to do is drop into
+	 * a different fd space and close the 9P server half of the
+	 * pipe before trying to mount the kernel half.  This way,
+	 * if the file server dies, we don't have a ref to the 9P server
+	 * half of the pipe.  Then killing the other procs will drop
+	 * all the refs on the 9P server half, and the mount will fail.
+	 * Otherwise the mount hangs forever.
+	 *
+	 * Libthread in general and acme win in particular make
+	 * it hard to make this fd bookkeeping work out properly,
+	 * so leaveinfdopen is a flag that win sets to opt out of this
+	 * safety net.
+	 */
+	if(!p->s->leavefdsopen){
+		rfork(RFFDG);
+		rendezvous((ulong)p->s, 0);
+		close(p->s->infd);
+		if(p->s->infd != p->s->outfd)
+			close(p->s->outfd);
+	}
+
+	if(p->mtpt){
+		if(amount(p->s->srvfd, p->mtpt, p->flag, "") == -1)
+			sysfatal("mount %s: %r", p->mtpt);
+	}else
+		close(p->s->srvfd);
+	free(p);
+}
+
blob - /dev/null
blob + 7fb88305058858b3701f4b83722d7d0e2fe7cf30 (mode 644)
--- /dev/null
+++ src/lib9p/dirread.c
@@ -0,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+void
+dirread9p(Req *r, Dirgen *gen, void *aux)
+{
+	int start;
+	uchar *p, *ep;
+	uint rv;
+	Dir d;
+
+	if(r->ifcall.offset == 0)
+		start = 0;
+	else
+		start = r->fid->dirindex;
+
+	p = (uchar*)r->ofcall.data;
+	ep = p+r->ifcall.count;
+
+	while(p < ep){
+		memset(&d, 0, sizeof d);
+		if((*gen)(start, &d, aux) < 0)
+			break;
+		rv = convD2M(&d, p, ep-p);
+		free(d.name);
+		free(d.muid);
+		free(d.uid);
+		free(d.gid);
+		if(rv <= BIT16SZ)
+			break;
+		p += rv;
+		start++;
+	}
+	r->fid->dirindex = start;
+	r->ofcall.count = p - (uchar*)r->ofcall.data;
+}
blob - /dev/null
blob + 2393f1c4c155fcdde1736ce99e66a0aee6fd6be8 (mode 644)
--- /dev/null
+++ src/lib9p/fid.c
@@ -0,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include "9p.h"
+
+static void
+incfidref(void *v)
+{
+	Fid *f;
+
+	f = v;
+	if(f)
+		incref(&f->ref);
+}
+
+Fidpool*
+allocfidpool(void (*destroy)(Fid*))
+{
+	Fidpool *f;
+
+	f = emalloc9p(sizeof *f);
+	f->map = allocmap(incfidref);
+	f->destroy = destroy;
+	return f;
+}
+
+void
+freefidpool(Fidpool *p)
+{
+	freemap(p->map, (void(*)(void*))p->destroy);
+	free(p);
+}
+
+Fid*
+allocfid(Fidpool *pool, ulong fid)
+{
+	Fid *f;
+
+	f = emalloc9p(sizeof *f);
+	f->fid = fid;
+	f->omode = -1;
+	f->pool = pool;
+
+	incfidref(f);
+	incfidref(f);
+	if(caninsertkey(pool->map, fid, f) == 0){
+		closefid(f);
+		return nil;
+	}
+
+	return f;
+}
+
+Fid*
+lookupfid(Fidpool *pool, ulong fid)
+{
+	return lookupkey(pool->map, fid);
+}
+
+void
+closefid(Fid *f)
+{
+	if(decref(&f->ref) == 0) {
+		if(f->rdir)
+			closedirfile(f->rdir);
+		if(f->pool->destroy)
+			f->pool->destroy(f);
+		if(f->file)
+			closefile(f->file);
+		free(f->uid);
+		free(f);
+	}
+}
+
+Fid*
+removefid(Fidpool *pool, ulong fid)
+{
+	return deletekey(pool->map, fid);
+}
blob - /dev/null
blob + 79a8a11d4050d8c529170b1139ea0b81dcf2e25e (mode 644)
--- /dev/null
+++ src/lib9p/file.c
@@ -0,0 +1,372 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+/*
+ * To avoid deadlock, the following rules must be followed.
+ * Always lock child then parent, never parent then child.
+ * If holding the free file lock, do not lock any Files.
+ */
+struct Filelist {
+	File *f;
+	Filelist *link;
+};
+
+static QLock filelk;
+static File *freefilelist;
+
+static File*
+allocfile(void)
+{
+	int i, a;
+	File *f;
+	enum { N = 16 };
+
+	qlock(&filelk);
+	if(freefilelist == nil){
+		f = emalloc9p(N*sizeof(*f));
+		for(i=0; i<N-1; i++)
+			f[i].aux = &f[i+1];
+		f[N-1].aux = nil;
+		f[0].allocd = 1;
+		freefilelist = f;
+	}
+
+	f = freefilelist;
+	freefilelist = f->aux;
+	qunlock(&filelk);
+
+	a = f->allocd;
+	memset(f, 0, sizeof *f);
+	f->allocd = a;
+	return f;
+}
+
+static void
+freefile(File *f)
+{
+	Filelist *fl, *flnext;
+
+	for(fl=f->filelist; fl; fl=flnext){
+		flnext = fl->link;
+		assert(fl->f == nil);
+		free(fl);
+	}
+
+	free(f->dir.name);
+	free(f->dir.uid);
+	free(f->dir.gid);
+	free(f->dir.muid);
+	qlock(&filelk);
+	assert(f->ref.ref == 0);
+	f->aux = freefilelist;
+	freefilelist = f;
+	qunlock(&filelk);
+}
+
+void
+closefile(File *f)
+{
+	if(decref(&f->ref) == 0){
+		f->tree->destroy(f);
+		freefile(f);
+	}
+}
+
+static void
+nop(File *f)
+{
+	USED(f);
+}
+
+int
+removefile(File *f)
+{
+	File *fp;
+	Filelist *fl;
+	
+	fp = f->parent;
+	if(fp == nil){
+		werrstr("no parent");
+		closefile(f);
+		return -1;
+	}
+
+	if(fp == f){
+		werrstr("cannot remove root");
+		closefile(f);
+		return -1;
+	}
+
+	wlock(&fp->rwlock);
+	wlock(&f->rwlock);
+	if(f->nchild != 0){
+		werrstr("has children");
+		wunlock(&f->rwlock);
+		wunlock(&fp->rwlock);
+		closefile(f);
+		return -1;
+	}
+
+	if(f->parent != fp){
+		werrstr("parent changed underfoot");
+		wunlock(&f->rwlock);
+		wunlock(&fp->rwlock);
+		closefile(f);
+		return -1;
+	}
+
+	for(fl=fp->filelist; fl; fl=fl->link)
+		if(fl->f == f)
+			break;
+	assert(fl != nil && fl->f == f);
+
+	fl->f = nil;
+	fp->nchild--;
+	f->parent = nil;
+	wunlock(&fp->rwlock);
+	wunlock(&f->rwlock);
+
+	closefile(fp);	/* reference from child */
+	closefile(f);	/* reference from tree */
+	closefile(f);
+	return 0;
+}
+
+File*
+createfile(File *fp, char *name, char *uid, ulong perm, void *aux)
+{
+	File *f;
+	Filelist *fl, *freel;
+	Tree *t;
+
+	if((fp->dir.qid.type&QTDIR) == 0){
+		werrstr("create in non-directory");
+		return nil;
+	}
+
+	freel = nil;
+	wlock(&fp->rwlock);
+	for(fl=fp->filelist; fl; fl=fl->link){
+		if(fl->f == nil)
+			freel = fl;
+		else if(strcmp(fl->f->dir.name, name) == 0){
+			wunlock(&fp->rwlock);
+			werrstr("file already exists");
+			return nil;
+		}
+	}
+
+	if(freel == nil){
+		freel = emalloc9p(sizeof *freel);
+		freel->link = fp->filelist;
+		fp->filelist = freel;
+	}
+
+	f = allocfile();
+	f->dir.name = estrdup9p(name);
+	f->dir.uid = estrdup9p(uid ? uid : fp->dir.uid);
+	f->dir.gid = estrdup9p(fp->dir.gid);
+	f->dir.muid = estrdup9p(uid ? uid : "unknown");
+	f->aux = aux;
+	f->dir.mode = perm;
+
+	t = fp->tree;
+	lock(&t->genlock);
+	f->dir.qid.path = t->qidgen++;
+	unlock(&t->genlock);
+	if(perm & DMDIR)
+		f->dir.qid.type |= QTDIR;
+	if(perm & DMAPPEND)
+		f->dir.qid.type |= QTAPPEND;
+	if(perm & DMEXCL)
+		f->dir.qid.type |= QTEXCL;
+
+	f->dir.mode = perm;
+	f->dir.atime = f->dir.mtime = time(0);
+	f->dir.length = 0;
+	f->parent = fp;
+	incref(&fp->ref);
+	f->tree = fp->tree;
+
+	incref(&f->ref);	/* being returned */
+	incref(&f->ref);	/* for the tree */
+	freel->f = f;
+	fp->nchild++;
+	wunlock(&fp->rwlock);
+
+	return f;
+}
+
+static File*
+walkfile1(File *dir, char *elem)
+{
+	File *fp;
+	Filelist *fl;
+
+	rlock(&dir->rwlock);
+	if(strcmp(elem, "..") == 0){
+		fp = dir->parent;
+		incref(&fp->ref);
+		runlock(&dir->rwlock);
+		closefile(dir);
+		return fp;
+	}
+
+	fp = nil;
+	for(fl=dir->filelist; fl; fl=fl->link)
+		if(fl->f && strcmp(fl->f->dir.name, elem)==0){
+			fp = fl->f;
+			incref(&fp->ref);
+			break;
+		}
+
+	runlock(&dir->rwlock);
+	closefile(dir);
+	return fp;
+}
+
+File*
+walkfile(File *f, char *path)
+{
+	char *os, *s, *nexts;
+	File *nf;
+
+	if(strchr(path, '/') == nil)
+		return walkfile1(f, path);	/* avoid malloc */
+
+	os = s = estrdup9p(path);
+	incref(&f->ref);
+	for(; *s; s=nexts){
+		if(nexts = strchr(s, '/'))
+			*nexts++ = '\0';
+		else
+			nexts = s+strlen(s);
+		nf = walkfile1(f, s);
+		decref(&f->ref);
+		f = nf;
+		if(f == nil)
+			break;
+	}
+	free(os);
+	return f;
+}
+			
+Tree*
+alloctree(char *uid, char *gid, ulong mode, void (*destroy)(File*))
+{
+	char *muid;
+	Tree *t;
+	File *f;
+
+	t = emalloc9p(sizeof *t);
+	f = allocfile();
+	f->dir.name = estrdup9p("/");
+	if(uid == nil){
+		if(uid = getuser())
+			uid = estrdup9p(uid);
+	}
+	if(uid == nil)
+		uid = estrdup9p("none");
+	else
+		uid = estrdup9p(uid);
+
+	if(gid == nil)
+		gid = estrdup9p(uid);
+	else
+		gid = estrdup9p(gid);
+
+	muid = estrdup9p(uid);
+
+	f->dir.qid = (Qid){0, 0, QTDIR};
+	f->dir.length = 0;
+	f->dir.atime = f->dir.mtime = time(0);
+	f->dir.mode = DMDIR | mode;
+	f->tree = t;
+	f->parent = f;
+	f->dir.uid = uid;
+	f->dir.gid = gid;
+	f->dir.muid = muid;
+
+	incref(&f->ref);
+	t->root = f;
+	t->qidgen = 0;
+	t->dirqidgen = 1;
+	if(destroy == nil)
+		destroy = nop;
+	t->destroy = destroy;
+
+	return t;
+}
+
+static void
+_freefiles(File *f)
+{
+	Filelist *fl, *flnext;
+
+	for(fl=f->filelist; fl; fl=flnext){
+		flnext = fl->link;
+		_freefiles(fl->f);
+		free(fl);
+	}
+
+	f->tree->destroy(f);
+	freefile(f);
+}
+
+void
+freetree(Tree *t)
+{
+	_freefiles(t->root);
+	free(t);
+}
+
+struct Readdir {
+	Filelist *fl;
+};
+
+Readdir*
+opendirfile(File *dir)
+{
+	Readdir *r;
+
+	rlock(&dir->rwlock);
+	if((dir->dir.mode & DMDIR)==0){
+		runlock(&dir->rwlock);
+		return nil;
+	}
+	r = emalloc9p(sizeof(*r));
+
+	/*
+	 * This reference won't go away while we're using it
+	 * since we are dir->rdir.
+	 */
+	r->fl = dir->filelist;
+	runlock(&dir->rwlock);
+	return r;
+}
+
+long
+readdirfile(Readdir *r, uchar *buf, long n)
+{
+	long x, m;
+	Filelist *fl;
+
+	for(fl=r->fl, m=0; fl && m+2<=n; fl=fl->link, m+=x){
+		if(fl->f == nil)
+			x = 0;
+		else if((x=convD2M(&fl->f->dir, buf+m, n-m)) <= BIT16SZ)
+			break;
+	}
+	r->fl = fl;
+	return m;
+}
+
+void
+closedirfile(Readdir *r)
+{
+	free(r);
+}
blob - /dev/null
blob + 6692b7f16446f3024241e6f6d5605cb79219caed (mode 644)
--- /dev/null
+++ src/lib9p/ftest.c
@@ -0,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include "9p.h"
+
+void
+main(void)
+{
+	Tree *t;
+	File *hello, *goodbye, *world;
+
+	t = mktree();
+
+	hello = fcreate(t->root, "hello", CHDIR|0777);
+	assert(hello != nil);
+
+	goodbye = fcreate(t->root, "goodbye", CHDIR|0777);
+	assert(goodbye != nil);
+
+	world = fcreate(hello, "world", 0666);
+	assert(world != nil);
+	world = fcreate(goodbye, "world", 0666);
+	assert(world != nil);
+	fdump(t->root, 0);
+
+	fremove(world);
+	fdump(t->root, 0);
+}
blob - /dev/null
blob + 4869567523ec56c5363cfee49f0955ba08cd0634 (mode 644)
--- /dev/null
+++ src/lib9p/intmap.c
@@ -0,0 +1,166 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+enum {
+	NHASH = 128
+};
+
+typedef struct Intlist	Intlist;
+struct Intlist
+{
+	ulong	id;
+	void*	aux;
+	Intlist*	link;
+};
+
+struct Intmap
+{
+	RWLock	rwlock;
+	Intlist*	hash[NHASH];
+	void (*inc)(void*);
+};
+
+static ulong
+hashid(ulong id)
+{
+	return id%NHASH;
+}
+
+static void
+nop(void *v)
+{
+	USED(v);
+}
+
+Intmap*
+allocmap(void (*inc)(void*))
+{
+	Intmap *m;
+
+	m = emalloc9p(sizeof(*m));
+	if(inc == nil)
+		inc = nop;
+	m->inc = inc;
+	return m;
+}
+
+void
+freemap(Intmap *map, void (*destroy)(void*))
+{
+	int i;
+	Intlist *p, *nlink;
+
+	if(destroy == nil)
+		destroy = nop;
+	for(i=0; i<NHASH; i++){
+		for(p=map->hash[i]; p; p=nlink){
+			nlink = p->link;
+			destroy(p->aux);
+			free(p);
+		}
+	}
+			
+	free(map);
+}
+
+static Intlist**
+llookup(Intmap *map, ulong id)
+{
+	Intlist **lf;
+
+	for(lf=&map->hash[hashid(id)]; *lf; lf=&(*lf)->link)
+		if((*lf)->id == id)
+			break;
+	return lf;	
+}
+
+/*
+ * The RWlock is used as expected except that we allow
+ * inc() to be called while holding it.  This is because we're
+ * locking changes to the tree structure, not to the references.
+ * Inc() is expected to have its own locking.
+ */
+void*
+lookupkey(Intmap *map, ulong id)
+{
+	Intlist *f;
+	void *v;
+
+	rlock(&map->rwlock);
+	if(f = *llookup(map, id)){
+		v = f->aux;
+		map->inc(v);
+	}else
+		v = nil;
+	runlock(&map->rwlock);
+	return v;
+}
+
+void*
+insertkey(Intmap *map, ulong id, void *v)
+{
+	Intlist *f;
+	void *ov;
+	ulong h;
+
+	wlock(&map->rwlock);
+	if(f = *llookup(map, id)){
+		/* no decrement for ov because we're returning it */
+		ov = f->aux;
+		f->aux = v;
+	}else{
+		f = emalloc9p(sizeof(*f));
+		f->id = id;
+		f->aux = v;
+		h = hashid(id);
+		f->link = map->hash[h];
+		map->hash[h] = f;
+		ov = nil;
+	}
+	wunlock(&map->rwlock);
+	return ov;	
+}
+
+int
+caninsertkey(Intmap *map, ulong id, void *v)
+{
+	Intlist *f;
+	int rv;
+	ulong h;
+
+	wlock(&map->rwlock);
+	if(*llookup(map, id))
+		rv = 0;
+	else{
+		f = emalloc9p(sizeof *f);
+		f->id = id;
+		f->aux = v;
+		h = hashid(id);
+		f->link = map->hash[h];
+		map->hash[h] = f;
+		rv = 1;
+	}
+	wunlock(&map->rwlock);
+	return rv;	
+}
+
+void*
+deletekey(Intmap *map, ulong id)
+{
+	Intlist **lf, *f;
+	void *ov;
+
+	wlock(&map->rwlock);
+	if(f = *(lf = llookup(map, id))){
+		ov = f->aux;
+		*lf = f->link;
+		free(f);
+	}else
+		ov = nil;
+	wunlock(&map->rwlock);
+	return ov;
+}
blob - /dev/null
blob + b4414951fe2d219a8d85fa1e97fae66d5a6f1aff (mode 644)
--- /dev/null
+++ src/lib9p/mem.c
@@ -0,0 +1,49 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include "9p.h"
+
+void*
+emalloc9p(ulong sz)
+{
+	void *v;
+
+	if((v = malloc(sz)) == nil) {
+		fprint(2, "out of memory allocating %lud\n", sz);
+		exits("mem");
+	}
+	memset(v, 0, sz);
+	setmalloctag(v, getcallerpc(&sz));
+	return v;
+}
+
+void*
+erealloc9p(void *v, ulong sz)
+{
+	void *nv;
+
+	if((nv = realloc(v, sz)) == nil) {
+		fprint(2, "out of memory allocating %lud\n", sz);
+		exits("mem");
+	}
+	if(v == nil)
+		setmalloctag(nv, getcallerpc(&v));
+	setrealloctag(nv, getcallerpc(&v));
+	return nv;
+}
+
+char*
+estrdup9p(char *s)
+{
+	char *t;
+
+	if((t = strdup(s)) == nil) {
+		fprint(2, "out of memory in strdup(%.10s)\n", s);
+		exits("mem");
+	}
+	setmalloctag(t, getcallerpc(&s));
+	return t;
+}
+
blob - /dev/null
blob + 17a10a3c18a03163b8542662ceb54214ee5da7b5 (mode 644)
--- /dev/null
+++ src/lib9p/mkfile
@@ -0,0 +1,20 @@
+PLAN9=../..
+<$PLAN9/src/mkhdr
+
+LIB=lib9p.a
+OFILES=\
+	_post.$O\
+	dirread.$O\
+	fid.$O\
+	file.$O\
+	intmap.$O\
+	mem.$O\
+	req.$O\
+	parse.$O\
+	post.$O\
+	srv.$O\
+	tpost.$O\
+	uid.$O\
+	util.$O\
+
+<$PLAN9/src/mksyslib
blob - /dev/null
blob + 753ae79db404da55f8ae9c85d4db067aa4522b9f (mode 644)
--- /dev/null
+++ src/lib9p/parse.c
@@ -0,0 +1,115 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+/*
+ * Generous estimate of number of fields, including terminal nil pointer
+ */
+static int
+ncmdfield(char *p, int n)
+{
+	int white, nwhite;
+	char *ep;
+	int nf;
+
+	if(p == nil)
+		return 1;
+
+	nf = 0;
+	ep = p+n;
+	white = 1;	/* first text will start field */
+	while(p < ep){
+		nwhite = (strchr(" \t\r\n", *p++ & 0xFF) != 0);	/* UTF is irrelevant */
+		if(white && !nwhite)	/* beginning of field */
+			nf++;
+		white = nwhite;
+	}
+	return nf+1;	/* +1 for nil */
+}
+
+/*
+ *  parse a command written to a device
+ */
+Cmdbuf*
+parsecmd(char *p, int n)
+{
+	Cmdbuf *cb;
+	int nf;
+	char *sp;
+
+	nf = ncmdfield(p, n);
+
+	/* allocate Cmdbuf plus string pointers plus copy of string including \0 */
+	sp = emalloc9p(sizeof(*cb) + nf * sizeof(char*) + n + 1);
+	cb = (Cmdbuf*)sp;
+	cb->f = (char**)(&cb[1]);
+	cb->buf = (char*)(&cb->f[nf]);
+
+	memmove(cb->buf, p, n);
+
+	/* dump new line and null terminate */
+	if(n > 0 && cb->buf[n-1] == '\n')
+		n--;
+	cb->buf[n] = '\0';
+
+	cb->nf = tokenize(cb->buf, cb->f, nf-1);
+	cb->f[cb->nf] = nil;
+
+	return cb;
+}
+
+/*
+ * Reconstruct original message, for error diagnostic
+ */
+void
+respondcmderror(Req *r, Cmdbuf *cb, char *fmt, ...)
+{
+	int i;
+	va_list arg;
+	char *p, *e;
+	char err[ERRMAX];
+	
+	e = err+ERRMAX-10;
+	va_start(arg, fmt);
+	p = vseprint(err, e, fmt, arg);
+	va_end(arg);
+	p = seprint(p, e, ": \"");
+	for(i=0; i<cb->nf; i++){
+		if(i > 0)
+			p = seprint(p, e, " ");
+		p = seprint(p, e, "%q", cb->f[i]);
+	}
+	strcpy(p, "\"");
+	respond(r, err);
+}
+
+/*
+ * Look up entry in table
+ */
+Cmdtab*
+lookupcmd(Cmdbuf *cb, Cmdtab *ctab, int nctab)
+{
+	int i;
+	Cmdtab *ct;
+
+	if(cb->nf == 0){
+		werrstr("empty control message");
+		return nil;
+	}
+
+	for(ct = ctab, i=0; i<nctab; i++, ct++){
+		if(strcmp(ct->cmd, "*") !=0)	/* wildcard always matches */
+		if(strcmp(ct->cmd, cb->f[0]) != 0)
+			continue;
+		if(ct->narg != 0 && ct->narg != cb->nf){
+			werrstr("bad # args to command");
+			return nil;
+		}
+		return ct;
+	}
+
+	werrstr("unknown control message");
+	return nil;
+}
blob - /dev/null
blob + 09296a8899338c6a412742afeb4e53e61e67ae49 (mode 644)
--- /dev/null
+++ src/lib9p/post.c
@@ -0,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "post.h"
+
+void
+postmountsrv(Srv *s, char *name, char *mtpt, int flag)
+{
+	Postcrud *p;
+
+	p = _post1(s, name, mtpt, flag);
+	switch(rfork(RFPROC|RFNOTEG|RFNAMEG|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		_post2(s);
+		exits(nil);
+	default:
+		_post3(p);
+	}
+}
+
blob - /dev/null
blob + 069a7ce7b9a0ec446e76ee293d47a02168ca7481 (mode 644)
--- /dev/null
+++ src/lib9p/post.h
@@ -0,0 +1,13 @@
+typedef struct Postcrud Postcrud;
+struct Postcrud
+{
+	int fd[2];
+	Srv *s;
+	char *name;
+	char *mtpt;
+	int flag;
+};
+
+Postcrud *_post1(Srv*, char*, char*, int);
+void _post2(void*);
+void _post3(Postcrud*);
blob - /dev/null
blob + a2f0b3d71824df0f665397fb4937820a47d44d25 (mode 644)
--- /dev/null
+++ src/lib9p/ramfs.c
@@ -0,0 +1,163 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+static char Ebad[] = "something bad happened";
+static char Enomem[] = "no memory";
+
+typedef struct Ramfile	Ramfile;
+struct Ramfile {
+	char *data;
+	int ndata;
+};
+
+void
+fsread(Req *r)
+{
+	Ramfile *rf;
+	vlong offset;
+	long count;
+
+	rf = r->fid->file->aux;
+	offset = r->ifcall.offset;
+	count = r->ifcall.count;
+
+//print("read %ld %lld\n", *count, offset);
+	if(offset >= rf->ndata){
+		r->ofcall.count = 0;
+		respond(r, nil);
+		return;
+	}
+
+	if(offset+count >= rf->ndata)
+		count = rf->ndata - offset;
+
+	memmove(r->ofcall.data, rf->data+offset, count);
+	r->ofcall.count = count;
+	respond(r, nil);
+}
+
+void
+fswrite(Req *r)
+{
+	void *v;
+	Ramfile *rf;
+	vlong offset;
+	long count;
+
+	rf = r->fid->file->aux;
+	offset = r->ifcall.offset;
+	count = r->ifcall.count;
+
+	if(offset+count >= rf->ndata){
+		v = realloc(rf->data, offset+count);
+		if(v == nil){
+			respond(r, Enomem);
+			return;
+		}
+		rf->data = v;
+		rf->ndata = offset+count;
+		r->fid->file->length = rf->ndata;
+	}
+	memmove(rf->data+offset, r->ifcall.data, count);
+	r->ofcall.count = count;
+	respond(r, nil);
+}
+
+void
+fscreate(Req *r)
+{
+	Ramfile *rf;
+	File *f;
+
+	if(f = createfile(r->fid->file, r->ifcall.name, r->fid->uid, r->ifcall.perm, nil)){
+		rf = emalloc9p(sizeof *rf);
+		f->aux = rf;
+		r->fid->file = f;
+		r->ofcall.qid = f->qid;
+		respond(r, nil);
+		return;
+	}
+	respond(r, Ebad);
+}
+
+void
+fsopen(Req *r)
+{
+	Ramfile *rf;
+
+	rf = r->fid->file->aux;
+
+	if(rf && (r->ifcall.mode&OTRUNC)){
+		rf->ndata = 0;
+		r->fid->file->length = 0;
+	}
+
+	respond(r, nil);
+}
+
+void
+fsdestroyfile(File *f)
+{
+	Ramfile *rf;
+
+//fprint(2, "clunk\n");
+	rf = f->aux;
+	if(rf){
+		free(rf->data);
+		free(rf);
+	}
+}
+
+Srv fs = {
+	.open=	fsopen,
+	.read=	fsread,
+	.write=	fswrite,
+	.create=	fscreate,
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: ramfs [-D] [-s srvname] [-m mtpt]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *srvname = nil;
+	char *mtpt = nil;
+	Qid q;
+
+	fs.tree = alloctree(nil, nil, DMDIR|0777, fsdestroyfile);
+	q = fs.tree->root->qid;
+
+	ARGBEGIN{
+	case 'D':
+		chatty9p++;
+		break;
+	case 's':
+		srvname = EARGF(usage());
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc)
+		usage();
+
+	if(chatty9p)
+		fprint(2, "ramsrv.nopipe %d srvname %s mtpt %s\n", fs.nopipe, srvname, mtpt);
+	if(srvname == nil && mtpt == nil)
+		sysfatal("you should at least specify a -s or -m option");
+
+	postmountsrv(&fs, srvname, mtpt, MREPL|MCREATE);
+	exits(0);
+}
blob - /dev/null
blob + 8e1aaab5480d6fab92a9d4c5c3687dd855386bb9 (mode 644)
--- /dev/null
+++ src/lib9p/req.c
@@ -0,0 +1,112 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+static void
+increqref(void *v)
+{
+	Req *r;
+
+	r = v;
+	if(r){
+if(chatty9p > 1)
+	fprint(2, "increfreq %p %ld\n", r, r->ref.ref);
+		incref(&r->ref);
+	}
+}
+
+Reqpool*
+allocreqpool(void (*destroy)(Req*))
+{
+	Reqpool *f;
+
+	f = emalloc9p(sizeof *f);
+	f->map = allocmap(increqref);
+	f->destroy = destroy;
+	return f;
+}
+
+void
+freereqpool(Reqpool *p)
+{
+	freemap(p->map, (void(*)(void*))p->destroy);
+	free(p);
+}	
+
+Req*
+allocreq(Reqpool *pool, ulong tag)
+{
+	Req *r;
+
+	r = emalloc9p(sizeof *r);
+	r->tag = tag;
+	r->pool = pool;
+
+	increqref(r);
+	increqref(r);
+	if(caninsertkey(pool->map, tag, r) == 0){
+		closereq(r);
+		return nil;
+	}
+
+	return r;
+}
+
+Req*
+lookupreq(Reqpool *pool, ulong tag)
+{
+if(chatty9p > 1)
+	fprint(2, "lookupreq %lud\n", tag);
+	return lookupkey(pool->map, tag);
+}
+
+void
+closereq(Req *r)
+{
+	int i;
+
+	if(r == nil)
+		return;
+
+if(chatty9p > 1)
+	fprint(2, "closereq %p %ld\n", r, r->ref.ref);
+
+	if(decref(&r->ref) == 0){
+		if(r->fid)
+			closefid(r->fid);
+		if(r->newfid)
+			closefid(r->newfid);
+		if(r->afid)
+			closefid(r->afid);
+		if(r->oldreq)
+			closereq(r->oldreq);
+		for(i=0; i<r->nflush; i++)
+			respond(r->flush[i], nil);
+		free(r->flush);
+		switch(r->ifcall.type){
+		case Tstat:
+			free(r->ofcall.stat);
+			free(r->d.name);
+			free(r->d.uid);
+			free(r->d.gid);
+			free(r->d.muid);
+			break;
+		}
+		if(r->pool->destroy)
+			r->pool->destroy(r);
+		free(r->buf);
+		free(r->rbuf);
+		free(r);
+	}
+}
+
+Req*
+removereq(Reqpool *pool, ulong tag)
+{
+if(chatty9p > 1)
+	fprint(2, "removereq %lud\n", tag);
+	return deletekey(pool->map, tag);
+}
blob - /dev/null
blob + c5262f4c79c5b52ea4264335741f8c434edb4aba (mode 644)
--- /dev/null
+++ src/lib9p/srv.c
@@ -0,0 +1,837 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+// static char Ebadattach[] = "unknown specifier in attach";
+static char Ebadoffset[] = "bad offset";
+// static char Ebadcount[] = "bad count";
+static char Ebotch[] = "9P protocol botch";
+static char Ecreatenondir[] = "create in non-directory";
+static char Edupfid[] = "duplicate fid";
+static char Eduptag[] = "duplicate tag";
+static char Eisdir[] = "is a directory";
+static char Enocreate[] = "create prohibited";
+// static char Enomem[] = "out of memory";
+static char Enoremove[] = "remove prohibited";
+static char Enostat[] = "stat prohibited";
+static char Enotfound[] = "file not found";
+// static char Enowrite[] = "write prohibited";
+static char Enowstat[] = "wstat prohibited";
+static char Eperm[] = "permission denied";
+static char Eunknownfid[] = "unknown fid";
+static char Ebaddir[] = "bad directory in wstat";
+static char Ewalknodir[] = "walk in non-directory";
+
+static void
+setfcallerror(Fcall *f, char *err)
+{
+	f->ename = err;
+	f->type = Rerror;
+}
+
+static void
+changemsize(Srv *srv, int msize)
+{
+	if(srv->rbuf && srv->wbuf && srv->msize == msize)
+		return;
+	qlock(&srv->rlock);
+	qlock(&srv->wlock);
+	srv->msize = msize;
+	free(srv->rbuf);
+	free(srv->wbuf);
+	srv->rbuf = emalloc9p(msize);
+	srv->wbuf = emalloc9p(msize);
+	qunlock(&srv->rlock);
+	qunlock(&srv->wlock);
+}
+
+static Req*
+getreq(Srv *s)
+{
+	long n;
+	uchar *buf;
+	Fcall f;
+	Req *r;
+
+	qlock(&s->rlock);
+	if((n = read9pmsg(s->infd, s->rbuf, s->msize)) <= 0){
+		qunlock(&s->rlock);
+		return nil;
+	}
+
+	buf = emalloc9p(n);
+	memmove(buf, s->rbuf, n);
+	qunlock(&s->rlock);
+
+	if(convM2S(buf, n, &f) != n){
+		free(buf);
+		return nil;
+	}
+
+	if((r=allocreq(s->rpool, f.tag)) == nil){	/* duplicate tag: cons up a fake Req */
+		r = emalloc9p(sizeof *r);
+		incref(&r->ref);
+		r->tag = f.tag;
+		r->ifcall = f;
+		r->error = Eduptag;
+		r->buf = buf;
+		r->responded = 0;
+		r->type = 0;
+		r->srv = s;
+		r->pool = nil;
+if(chatty9p)
+	fprint(2, "<-%d- %F: dup tag\n", s->infd, &f);
+		return r;
+	}
+
+	r->srv = s;
+	r->responded = 0;
+	r->buf = buf;
+	r->ifcall = f;
+	memset(&r->ofcall, 0, sizeof r->ofcall);
+	r->type = r->ifcall.type;
+
+if(chatty9p)
+	if(r->error)
+		fprint(2, "<-%d- %F: %s\n", s->infd, &r->ifcall, r->error);
+	else	
+		fprint(2, "<-%d- %F\n", s->infd, &r->ifcall);
+
+	return r;
+}
+
+static void
+filewalk(Req *r)
+{
+	int i;
+	File *f;
+
+	f = r->fid->file;
+	assert(f != nil);
+
+	incref(&f->ref);
+	for(i=0; i<r->ifcall.nwname; i++)
+		if(f = walkfile(f, r->ifcall.wname[i]))
+			r->ofcall.wqid[i] = f->dir.qid;
+		else
+			break;
+
+	r->ofcall.nwqid = i;
+	if(f){
+		r->newfid->file = f;
+		r->newfid->qid = r->newfid->file->dir.qid;
+	}
+	respond(r, nil);
+}
+
+void
+walkandclone(Req *r, char *(*walk1)(Fid*, char*, void*), char *(*clone)(Fid*, Fid*, void*), void *arg)
+{
+	int i;
+	char *e;
+
+	if(r->fid == r->newfid && r->ifcall.nwname > 1){
+		respond(r, "lib9p: unused documented feature not implemented");
+		return;
+	}
+
+	if(r->fid != r->newfid){
+		r->newfid->qid = r->fid->qid;
+		if(clone && (e = clone(r->fid, r->newfid, arg))){
+			respond(r, e);
+			return;
+		}
+	}
+
+	e = nil;
+	for(i=0; i<r->ifcall.nwname; i++){
+		if(e = walk1(r->newfid, r->ifcall.wname[i], arg))
+			break;
+		r->ofcall.wqid[i] = r->newfid->qid;
+	}
+
+	r->ofcall.nwqid = i;
+	if(e && i==0)
+		respond(r, e);
+	else
+		respond(r, nil);
+}
+
+static void
+sversion(Srv *srv, Req *r)
+{
+	USED(srv);
+
+	if(strncmp(r->ifcall.version, "9P", 2) != 0){
+		r->ofcall.version = "unknown";
+		respond(r, nil);
+		return;
+	}
+
+	r->ofcall.version = "9P2000";
+	r->ofcall.msize = r->ifcall.msize;
+	respond(r, nil);
+}
+static void
+rversion(Req *r, char *error)
+{
+	assert(error == nil);
+	changemsize(r->srv, r->ofcall.msize);
+}
+
+static void
+sauth(Srv *srv, Req *r)
+{
+	char e[ERRMAX];
+
+	if((r->afid = allocfid(srv->fpool, r->ifcall.afid)) == nil){
+		respond(r, Edupfid);
+		return;
+	}
+	if(srv->auth)
+		srv->auth(r);
+	else{
+		snprint(e, sizeof e, "%s: authentication not required", argv0);
+		respond(r, e);
+	}
+}
+static void
+rauth(Req *r, char *error)
+{
+	if(error && r->afid)
+		closefid(removefid(r->srv->fpool, r->afid->fid));
+}
+
+static void
+sattach(Srv *srv, Req *r)
+{
+	if((r->fid = allocfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Edupfid);
+		return;
+	}
+	r->afid = nil;
+	if(r->ifcall.afid != NOFID && (r->afid = lookupfid(srv->fpool, r->ifcall.afid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	r->fid->uid = estrdup9p(r->ifcall.uname);
+	if(srv->tree){
+		r->fid->file = srv->tree->root;
+		/* BUG? incref(r->fid->file) ??? */
+		r->ofcall.qid = r->fid->file->dir.qid;
+		r->fid->qid = r->ofcall.qid;
+	}
+	if(srv->attach)
+		srv->attach(r);
+	else
+		respond(r, nil);
+	return;
+}
+static void
+rattach(Req *r, char *error)
+{
+	if(error && r->fid)
+		closefid(removefid(r->srv->fpool, r->fid->fid));
+}
+
+static void
+sflush(Srv *srv, Req *r)
+{
+	r->oldreq = lookupreq(srv->rpool, r->ifcall.oldtag);
+	if(r->oldreq == nil || r->oldreq == r)
+		respond(r, nil);
+	else if(srv->flush)
+		srv->flush(r);
+	else
+		respond(r, nil);
+}
+static int
+rflush(Req *r, char *error)
+{
+	Req *or;
+
+	assert(error == nil);
+	or = r->oldreq;
+	if(or){
+		qlock(&or->lk);
+		if(or->responded == 0){
+			or->flush = erealloc9p(or->flush, (or->nflush+1)*sizeof(or->flush[0]));
+			or->flush[or->nflush++] = r;
+			qunlock(&or->lk);
+			return -1;		/* delay response until or is responded */
+		}
+		qunlock(&or->lk);
+		closereq(or);
+	}
+	r->oldreq = nil;
+	return 0;
+}
+
+static char*
+oldwalk1(Fid *fid, char *name, void *arg)
+{
+	char *e;
+	Qid qid;
+	Srv *srv;
+
+	srv = arg;
+	e = srv->walk1(fid, name, &qid);
+	if(e)
+		return e;
+	fid->qid = qid;
+	return nil;
+}
+
+static char*
+oldclone(Fid *fid, Fid *newfid, void *arg)
+{
+	Srv *srv;
+
+	srv = arg;
+	if(srv->clone == nil)
+		return nil;
+	return srv->clone(fid, newfid);
+}
+
+static void
+swalk(Srv *srv, Req *r)
+{
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->fid->omode != -1){
+		respond(r, "cannot clone open fid");
+		return;
+	}
+	if(r->ifcall.nwname && !(r->fid->qid.type&QTDIR)){
+		respond(r, Ewalknodir);
+		return;
+	}
+	if(r->ifcall.fid != r->ifcall.newfid){
+		if((r->newfid = allocfid(srv->fpool, r->ifcall.newfid)) == nil){
+			respond(r, Edupfid);
+			return;
+		}
+		r->newfid->uid = estrdup9p(r->fid->uid);
+	}else{
+		incref(&r->fid->ref);
+		r->newfid = r->fid;
+	}
+	if(r->fid->file){
+		filewalk(r);
+	}else if(srv->walk1)
+		walkandclone(r, oldwalk1, oldclone, srv);
+	else if(srv->walk)
+		srv->walk(r);
+	else
+		sysfatal("no walk function, no file trees");
+}
+static void
+rwalk(Req *r, char *error)
+{
+	if(error || r->ofcall.nwqid < r->ifcall.nwname){
+		if(r->ifcall.fid != r->ifcall.newfid && r->newfid)
+			closefid(removefid(r->srv->fpool, r->newfid->fid));
+		if (r->ofcall.nwqid==0){
+			if(error==nil && r->ifcall.nwname!=0)
+				r->error = Enotfound;
+		}else
+			r->error = nil;	// No error on partial walks
+	}else{
+		if(r->ofcall.nwqid == 0){
+			/* Just a clone */
+			r->newfid->qid = r->fid->qid;
+		}else{
+			/* if file trees are in use, filewalk took care of the rest */
+			r->newfid->qid = r->ofcall.wqid[r->ofcall.nwqid-1];
+		}
+	}
+}
+
+static void
+sopen(Srv *srv, Req *r)
+{
+	int p;
+
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->fid->omode != -1){
+		respond(r, Ebotch);
+		return;
+	}
+	if((r->fid->qid.type&QTDIR) && (r->ifcall.mode&~ORCLOSE) != OREAD){
+		respond(r, Eisdir);
+		return;
+	}
+	r->ofcall.qid = r->fid->qid;
+	switch(r->ifcall.mode&3){
+	default:
+		assert(0);
+	case OREAD:
+		p = AREAD;	
+		break;
+	case OWRITE:
+		p = AWRITE;
+		break;
+	case ORDWR:
+		p = AREAD|AWRITE;
+		break;
+	case OEXEC:
+		p = AEXEC;	
+		break;
+	}
+	if(r->ifcall.mode&OTRUNC)
+		p |= AWRITE;
+	if((r->fid->qid.type&QTDIR) && p!=AREAD){
+		respond(r, Eperm);
+		return;
+	}
+	if(r->fid->file){
+		if(!hasperm(r->fid->file, r->fid->uid, p)){
+			respond(r, Eperm);
+			return;
+		}
+	/* BUG RACE */
+		if((r->ifcall.mode&ORCLOSE)
+		&& !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){
+			respond(r, Eperm);
+			return;
+		}
+		r->ofcall.qid = r->fid->file->dir.qid;
+		if((r->ofcall.qid.type&QTDIR)
+		&& (r->fid->rdir = opendirfile(r->fid->file)) == nil){
+			respond(r, "opendirfile failed");
+			return;
+		}
+	}
+	if(srv->open)
+		srv->open(r);
+	else
+		respond(r, nil);
+}
+static void
+ropen(Req *r, char *error)
+{
+	char errbuf[ERRMAX];
+	if(error)
+		return;
+	if(chatty9p){
+		snprint(errbuf, sizeof errbuf, "fid mode is 0x%ux\n", r->ifcall.mode);
+		write(2, errbuf, strlen(errbuf));
+	}
+	r->fid->omode = r->ifcall.mode;
+	r->fid->qid = r->ofcall.qid;
+	if(r->ofcall.qid.type&QTDIR)
+		r->fid->diroffset = 0;
+}
+
+static void
+screate(Srv *srv, Req *r)
+{
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil)
+		respond(r, Eunknownfid);
+	else if(r->fid->omode != -1)
+		respond(r, Ebotch);
+	else if(!(r->fid->qid.type&QTDIR))
+		respond(r, Ecreatenondir);
+	else if(r->fid->file && !hasperm(r->fid->file, r->fid->uid, AWRITE))
+		respond(r, Eperm);
+	else if(srv->create)
+		srv->create(r);
+	else
+		respond(r, Enocreate);
+}
+static void
+rcreate(Req *r, char *error)
+{
+	if(error)
+		return;
+	r->fid->omode = r->ifcall.mode;
+	r->fid->qid = r->ofcall.qid;
+}
+
+static void
+sread(Srv *srv, Req *r)
+{
+	int o;
+
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->ifcall.count < 0){
+		respond(r, Ebotch);
+		return;
+	}
+	if(r->ifcall.offset < 0
+	|| ((r->fid->qid.type&QTDIR) && r->ifcall.offset != 0 && r->ifcall.offset != r->fid->diroffset)){
+		respond(r, Ebadoffset);
+		return;
+	}
+
+	if(r->ifcall.count > srv->msize - IOHDRSZ)
+		r->ifcall.count = srv->msize - IOHDRSZ;
+	r->rbuf = emalloc9p(r->ifcall.count);
+	r->ofcall.data = r->rbuf;
+	o = r->fid->omode & 3;
+	if(o != OREAD && o != ORDWR && o != OEXEC){
+		respond(r, Ebotch);
+		return;
+	}
+	if((r->fid->qid.type&QTDIR) && r->fid->file){
+		r->ofcall.count = readdirfile(r->fid->rdir, r->rbuf, r->ifcall.count);
+		respond(r, nil);
+		return;
+	}
+	if(srv->read)
+		srv->read(r);
+	else
+		respond(r, "no srv->read");
+}
+static void
+rread(Req *r, char *error)
+{
+	if(error==nil && (r->fid->qid.type&QTDIR))
+		r->fid->diroffset += r->ofcall.count;
+}
+
+static void
+swrite(Srv *srv, Req *r)
+{
+	int o;
+	char e[ERRMAX];
+
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->ifcall.count < 0){
+		respond(r, Ebotch);
+		return;
+	}
+	if(r->ifcall.offset < 0){
+		respond(r, Ebotch);
+		return;
+	}
+	if(r->ifcall.count > srv->msize - IOHDRSZ)
+		r->ifcall.count = srv->msize - IOHDRSZ;
+	o = r->fid->omode & 3;
+	if(o != OWRITE && o != ORDWR){
+		snprint(e, sizeof e, "write on fid with open mode 0x%ux", r->fid->omode);
+		respond(r, e);
+		return;
+	}
+	if(srv->write)
+		srv->write(r);
+	else
+		respond(r, "no srv->write");
+}
+static void
+rwrite(Req *r, char *error)
+{
+	if(error)
+		return;
+	if(r->fid->file)
+		r->fid->file->dir.qid.vers++;
+}
+
+static void
+sclunk(Srv *srv, Req *r)
+{
+	if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil)
+		respond(r, Eunknownfid);
+	else
+		respond(r, nil);
+}
+static void
+rclunk(Req *r, char *msg)
+{
+	USED(r);
+	USED(msg);
+}
+
+static void
+sremove(Srv *srv, Req *r)
+{
+	if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	/* BUG RACE */
+	if(r->fid->file && !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){
+		respond(r, Eperm);
+		return;
+	}
+	if(srv->remove)
+		srv->remove(r);
+	else
+		respond(r, r->fid->file ? nil : Enoremove);
+}
+static void
+rremove(Req *r, char *error, char *errbuf)
+{
+	if(error)
+		return;
+	if(r->fid->file){
+		if(removefile(r->fid->file) < 0){
+			snprint(errbuf, ERRMAX, "remove %s: %r", 
+				r->fid->file->dir.name);
+			r->error = errbuf;
+		}
+		r->fid->file = nil;
+	}
+}
+
+static void
+sstat(Srv *srv, Req *r)
+{
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(r->fid->file){
+		r->d = r->fid->file->dir;
+		if(r->d.name)
+			r->d.name = estrdup9p(r->d.name);
+		if(r->d.uid)
+			r->d.uid = estrdup9p(r->d.uid);
+		if(r->d.gid)
+			r->d.gid = estrdup9p(r->d.gid);
+		if(r->d.muid)
+			r->d.muid = estrdup9p(r->d.muid);
+	}
+	if(srv->stat)	
+		srv->stat(r);	
+	else if(r->fid->file)
+		respond(r, nil);
+	else
+		respond(r, Enostat);
+}
+static void
+rstat(Req *r, char *error)
+{
+	int n;
+	uchar *statbuf;
+	uchar tmp[BIT16SZ];
+
+	if(error)
+		return;
+	if(convD2M(&r->d, tmp, BIT16SZ) != BIT16SZ){
+		r->error = "convD2M(_,_,BIT16SZ) did not return BIT16SZ";
+		return;
+	}
+	n = GBIT16(tmp)+BIT16SZ;
+	statbuf = emalloc9p(n);
+	if(statbuf == nil){
+		r->error = "out of memory";
+		return;
+	}
+	r->ofcall.nstat = convD2M(&r->d, statbuf, n);
+	r->ofcall.stat = statbuf;	/* freed in closereq */
+	if(r->ofcall.nstat <= BIT16SZ){
+		r->error = "convD2M fails";
+		free(statbuf);
+		return;
+	}
+}
+
+static void
+swstat(Srv *srv, Req *r)
+{
+	if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
+		respond(r, Eunknownfid);
+		return;
+	}
+	if(srv->wstat == nil){
+		respond(r, Enowstat);
+		return;
+	}
+	if(convM2D(r->ifcall.stat, r->ifcall.nstat, &r->d, (char*)r->ifcall.stat) != r->ifcall.nstat){
+		respond(r, Ebaddir);
+		return;
+	}
+	if((ushort)~r->d.type){
+		respond(r, "wstat -- attempt to change type");
+		return;
+	}
+	if((uint)~r->d.dev){
+		respond(r, "wstat -- attempt to change dev");
+		return;
+	}
+	if((uchar)~r->d.qid.type || (ulong)~r->d.qid.vers || (uvlong)~r->d.qid.path){
+		respond(r, "wstat -- attempt to change qid");
+		return;
+	}
+	if(r->d.muid && r->d.muid[0]){
+		respond(r, "wstat -- attempt to change muid");
+		return;
+	}
+	if((ulong)~r->d.mode && ((r->d.mode&DMDIR)>>24) != (r->fid->qid.type&QTDIR)){
+		respond(r, "wstat -- attempt to change DMDIR bit");
+		return;
+	}
+	srv->wstat(r);
+}
+static void
+rwstat(Req *r, char *msg)
+{
+	USED(r);
+	USED(msg);
+}
+
+void
+srv(Srv *srv)
+{
+	Req *r;
+
+	fmtinstall('D', dirfmt);
+	fmtinstall('F', fcallfmt);
+
+	if(srv->fpool == nil)
+		srv->fpool = allocfidpool(srv->destroyfid);
+	if(srv->rpool == nil)
+		srv->rpool = allocreqpool(srv->destroyreq);
+	if(srv->msize == 0)
+		srv->msize = 8192+IOHDRSZ;
+
+	changemsize(srv, srv->msize);
+
+	srv->fpool->srv = srv;
+	srv->rpool->srv = srv;
+
+	while(r = getreq(srv)){
+		if(r->error){
+			respond(r, r->error);
+			continue;	
+		}
+		switch(r->ifcall.type){
+		default:
+			respond(r, "unknown message");
+			break;
+		case Tversion:	sversion(srv, r);	break;
+		case Tauth:	sauth(srv, r);	break;
+		case Tattach:	sattach(srv, r);	break;
+		case Tflush:	sflush(srv, r);	break;
+		case Twalk:	swalk(srv, r);	break;
+		case Topen:	sopen(srv, r);	break;
+		case Tcreate:	screate(srv, r);	break;
+		case Tread:	sread(srv, r);	break;
+		case Twrite:	swrite(srv, r);	break;
+		case Tclunk:	sclunk(srv, r);	break;
+		case Tremove:	sremove(srv, r);	break;
+		case Tstat:	sstat(srv, r);	break;
+		case Twstat:	swstat(srv, r);	break;
+		}
+	}
+
+	if(srv->end)
+		srv->end(srv);
+}
+
+void
+respond(Req *r, char *error)
+{
+	int i, m, n;
+	char errbuf[ERRMAX];
+	Srv *srv;
+
+	srv = r->srv;
+	assert(srv != nil);
+
+	assert(r->responded == 0);
+	r->error = error;
+
+	switch(r->ifcall.type){
+	default:
+		assert(0);
+	/*
+	 * Flush is special.  If the handler says so, we return
+	 * without further processing.  Respond will be called
+	 * again once it is safe.
+	 */
+	case Tflush:
+		if(rflush(r, error)<0)
+			return;
+		break;
+	case Tversion:	rversion(r, error);	break;
+	case Tauth:	rauth(r, error);	break;
+	case Tattach:	rattach(r, error);	break;
+	case Twalk:	rwalk(r, error);	break;
+	case Topen:	ropen(r, error);	break;
+	case Tcreate:	rcreate(r, error);	break;
+	case Tread:	rread(r, error);	break;
+	case Twrite:	rwrite(r, error);	break;
+	case Tclunk:	rclunk(r, error);	break;
+	case Tremove:	rremove(r, error, errbuf);	break;
+	case Tstat:	rstat(r, error);	break;
+	case Twstat:	rwstat(r, error);	break;
+	}
+
+	r->ofcall.tag = r->ifcall.tag;
+	r->ofcall.type = r->ifcall.type+1;
+	if(r->error)
+		setfcallerror(&r->ofcall, r->error);
+
+if(chatty9p)
+	fprint(2, "-%d-> %F\n", srv->outfd, &r->ofcall);
+
+	qlock(&srv->wlock);
+	n = convS2M(&r->ofcall, srv->wbuf, srv->msize);
+	if(n <= 0){
+		fprint(2, "n = %d %F\n", n, &r->ofcall);
+		abort();
+	}
+	assert(n > 2);
+	if(r->pool)	/* not a fake */
+		closereq(removereq(r->pool, r->ifcall.tag));
+	m = write(srv->outfd, srv->wbuf, n);
+	if(m != n)
+		sysfatal("lib9p srv: write %d returned %d on fd %d: %r", n, m, srv->outfd);
+	qunlock(&srv->wlock);
+
+	qlock(&r->lk);	/* no one will add flushes now */
+	r->responded = 1;
+	qunlock(&r->lk);
+
+	for(i=0; i<r->nflush; i++)
+		respond(r->flush[i], nil);
+	free(r->flush);
+
+	if(r->pool)
+		closereq(r);
+	else
+		free(r);
+}
+
+int
+postfd(char *name, int pfd)
+{
+	int fd;
+	char buf[80];
+
+	snprint(buf, sizeof buf, "/srv/%s", name);
+	if(chatty9p)
+		fprint(2, "postfd %s\n", buf);
+	fd = create(buf, OWRITE|ORCLOSE|OCEXEC, 0600);
+	if(fd < 0){
+		if(chatty9p)
+			fprint(2, "create fails: %r\n");
+		return -1;
+	}
+	if(fprint(fd, "%d", pfd) < 0){
+		if(chatty9p)
+			fprint(2, "write fails: %r\n");
+		close(fd);
+		return -1;
+	}
+	if(chatty9p)
+		fprint(2, "postfd successful\n");
+	return 0;
+}
+
blob - /dev/null
blob + 68da19a74bc025289f251a87a8b91d086925562f (mode 644)
--- /dev/null
+++ src/lib9p/tpost.c
@@ -0,0 +1,18 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "post.h"
+
+void
+threadpostmountsrv(Srv *s, char *name, char *mtpt, int flag)
+{
+	Postcrud *p;
+
+	p = _post1(s, name, mtpt, flag);
+	if(procrfork(_post2, s, 32*1024, RFNAMEG|RFNOTEG) < 0)
+		sysfatal("procrfork: %r");
+	_post3(p);
+}
+
blob - /dev/null
blob + 84b98bde13dd96efc542b481eafe3b6309b6b6c8 (mode 644)
--- /dev/null
+++ src/lib9p/uid.c
@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+/*
+ * simplistic permission checking.  assume that
+ * each user is the leader of her own group.
+ */
+int
+hasperm(File *f, char *uid, int p)
+{
+	int m;
+
+	m = f->dir.mode & 7;	/* other */
+	if((p & m) == p)
+		return 1;
+
+	if(strcmp(f->dir.uid, uid) == 0) {
+		m |= (f->dir.mode>>6) & 7;
+		if((p & m) == p)
+			return 1;
+	}
+
+	if(strcmp(f->dir.gid, uid) == 0) {
+		m |= (f->dir.mode>>3) & 7;
+		if((p & m) == p)
+			return 1;
+	}
+
+	return 0;
+}
blob - /dev/null
blob + 17588a95e8c6ce231d86b7adf10180eff145da64 (mode 644)
--- /dev/null
+++ src/lib9p/util.c
@@ -0,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include "9p.h"
+
+void
+readbuf(Req *r, void *s, long n)
+{
+	r->ofcall.count = r->ifcall.count;
+	if(r->ifcall.offset >= n){
+		r->ofcall.count = 0;
+		return;
+	}
+	if(r->ifcall.offset+r->ofcall.count > n)
+		r->ofcall.count = n - r->ifcall.offset;
+	memmove(r->ofcall.data, (char*)s+r->ifcall.offset, r->ofcall.count);
+}
+
+void
+readstr(Req *r, char *s)
+{
+	readbuf(r, s, strlen(s));
+}
blob - d912391f8a0ac0ab12a6858aea06977071f5b3d4
blob + 3151525080c0ef7feaeec1cbfd41428e48a318bb
--- src/libfs/fs.c
+++ src/libfs/fs.c
@@ -259,7 +259,7 @@ _fssend(Mux *mux, void *pkt)
 	Fsys *fs;
 
 	fs = mux->aux;
-	return write(fs->fd, pkt, GBIT32((uchar*)pkt));
+	return threadwrite(fs->fd, pkt, GBIT32((uchar*)pkt));
 }
 
 static void*
blob - c05d40d106e4c8b22f470575957efd5f821fab59
blob + f868e1237c37ab1ae7eb2ec3c45e7a88fcd00d4e
--- src/libfs/read.c
+++ src/libfs/read.c
@@ -52,3 +52,21 @@ fsread(Fid *fid, void *buf, long n)
 {
 	return fspread(fid, buf, n, -1);
 }
+
+long
+fsreadn(Fid *fid, void *buf, long n)
+{
+	long tot, nn;
+
+	for(tot=0; tot<n; tot+=nn){
+		nn = fsread(fid, (char*)buf+tot, n-tot);
+		if(nn <= 0){
+			if(tot == 0)
+				return nn;
+			break;
+		}
+	}
+	return tot;
+}
+			
+
blob - 91abc7c9bc3a778d30675dcf3f90fd33594046a4
blob + 5983345e39b1b5fb8e3fe8d2677b485f502df60d
--- src/libhttpd/gethead.c
+++ src/libhttpd/gethead.c
@@ -15,11 +15,15 @@ hgethead(HConnect *c, int many)
 	int n;
 
 	hin = &c->hin;
+fprint(2, "hgethead top %p - %p\n", hin->pos, hin->stop);
 	for(;;){
 		s = (char*)hin->pos;
 		pp = s;
+fprint(2, "hgethead %p - %p\n", pp, hin->stop);
 		while(p = memchr(pp, '\n', (char*)hin->stop - pp)){
-			if(!many || p == pp || p == pp + 1 && *pp == '\r'){
+fprint(2, "hgethead %p - %p newline at %p %d\n", pp, hin->stop, p, *pp);
+			if(!many || p == pp || (p == pp + 1 && *pp == '\r')){
+fprint(2, "breaking\n");
 				pp = p + 1;
 				break;
 			}
@@ -32,6 +36,7 @@ hgethead(HConnect *c, int many)
 		memmove(c->hstop, s, n);
 		c->hstop += n;
 		*c->hstop = '\0';
+fprint(2, "p %p\n", p);
 		if(p != nil)
 			return 1;
 		if(hreadbuf(hin, hin->pos) == nil || hin->state == Hend)
blob - 7cfb555721bd2701c4224a02238d8482492422e5
blob + 34d3a3a2c5f5c957d6b75e7dfc74c9459da34f20
--- src/libhttpd/hio.c
+++ src/libhttpd/hio.c
@@ -157,10 +157,15 @@ hreadbuf(Hio *h, void *vsave)
 			memmove(h->start + cpy, hh->pos, in);
 			hh->pos += in;
 		}
-	}else if(in && (in = read(h->fd, h->start + cpy, in)) < 0){
-		h->state = Herr;
-		h->pos = h->stop;
-		return nil;
+	}else if(in){
+fprint(2, "read %d from %d\n", in, h->fd);
+		if((in = read(h->fd, h->start + cpy, in)) < 0){
+fprint(2, "got error: %r\n");
+			h->state = Herr;
+			h->pos = h->stop;
+			return nil;
+		}
+fprint(2, "got %d\n", in);
 	}
 	if(in == 0)
 		h->state = Hend;
blob - b8330d85220057b5fd0e693faaee698a5bffbed4
blob + b08999a621dfa081728a0615c13e48f291e2bbdd
--- src/libip/mkfile
+++ src/libip/mkfile
@@ -3,16 +3,17 @@ PLAN9=../..
 
 LIB=libip.a
 OFILES=\
+	bo.$O\
+	classmask.$O\
 	eipfmt.$O\
-	parseip.$O\
-	parseether.$O\
+	ipaux.$O\
 	myetheraddr.$O\
 	myipaddr.$O\
-	classmask.$O\
-	bo.$O\
-	readipifc.$O\
-	ipaux.$O\
+	parseether.$O\
+	parseip.$O\
 	ptclbsum.$O\
+	readipifc.$O\
+	udp.$O\
 
 HFILES=\
 	ip.h
blob - fd95810b3e06ebdb605a9201b7fd67bae91e966d
blob + c4094fa485c45e39500cc4a3baabf197000bf5f9
--- src/libplumb/mesg.c
+++ src/libplumb/mesg.c
@@ -13,8 +13,6 @@ static Fid *pfid;
 int
 plumbopen(char *name, int omode)
 {
-	int fd;
-
 	if(fsplumb == nil)
 		fsplumb = nsmount("plumb", "");
 	if(fsplumb == nil)
@@ -47,26 +45,42 @@ plumbopen(char *name, int omode)
 		return pfd;
 	}
 
-	fd = fsopenfd(fsplumb, name, omode);
-	return fd;
+	return fsopenfd(fsplumb, name, omode);
 }
 
+Fid*
+plumbopenfid(char *name, int mode)
+{
+	if(fsplumb == nil)
+		fsplumb = nsmount("plumb", "");
+	if(fsplumb == nil)
+		return nil;
+	return fsopen(fsplumb, name, mode);
+}
+
 int
-plumbsend(int fd, Plumbmsg *m)
+plumbsendtofid(Fid *fid, Plumbmsg *m)
 {
 	char *buf;
 	int n;
 
-	if(fd != pfd){
-		werrstr("fd is not the plumber");
-		return -1;
-	}
 	buf = plumbpack(m, &n);
 	if(buf == nil)
 		return -1;
-	n = fswrite(pfid, buf, n);
+	n = fswrite(fid, buf, n);
+fprint(2, "fswrite %d\n", n);
 	free(buf);
 	return n;
+}
+
+int
+plumbsend(int fd, Plumbmsg *m)
+{
+	if(fd != pfd){
+		werrstr("fd is not the plumber");
+		return -1;
+	}
+	return plumbsendtofid(pfid, m);
 }
 
 static int
@@ -421,9 +435,37 @@ plumbrecv(int fd)
 			if(buf == nil)
 				return nil;
 			if(readn(fd, buf+n, more) == more)
+				m = plumbunpackpartial(buf, n+more, nil);
+		}
+	}
+	free(buf);
+	return m;
+}
+
+Plumbmsg*
+plumbrecvfid(Fid *fid)
+{
+	char *buf;
+	Plumbmsg *m;
+	int n, more;
+
+	buf = malloc(8192);
+	if(buf == nil)
+		return nil;
+	n = fsread(fid, buf, 8192);
+	m = nil;
+	if(n > 0){
+		m = plumbunpackpartial(buf, n, &more);
+		if(m==nil && more>0){
+			/* we now know how many more bytes to read for complete message */
+			buf = realloc(buf, n+more);
+			if(buf == nil)
+				return nil;
+			if(fsreadn(fid, buf+n, more) == more)
 				m = plumbunpackpartial(buf, n+more, nil);
 		}
 	}
 	free(buf);
 	return m;
 }
+
blob - c31fdfb1972c7a6e6a928c1791194ed29f5e1e9e
blob + 5b3752570b78f1344f4a72f9d21c618dc803c060
--- src/libplumb/thread.c
+++ src/libplumb/thread.c
@@ -31,3 +31,4 @@ threadplumbrecv(int fd)
 	free(buf);
 	return m;
 }
+
blob - fce96fd3ef3bd4bd9022824edbc10a56e001dfce
blob + dda9c8a52a95e96a1eee6824a84b4cb188b6cc91
--- src/libsec/port/aes.c
+++ src/libsec/port/aes.c
@@ -43,7 +43,7 @@ static const u32 Td3[256];
 static const u8  Te4[256];
 
 static int rijndaelKeySetupEnc(u32 rk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits);
-// static int rijndaelKeySetupDec(u32 rk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits);
+static int rijndaelKeySetupDec(u32 rk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits);
 static int rijndaelKeySetup(u32 erk[/*4*(Nr + 1)*/], u32 drk[/*4*(Nr + 1)*/], const u8 cipherKey[], int keyBits);
 static void	rijndaelEncrypt(const u32int rk[], int Nr, const uchar pt[16], uchar ct[16]);
 static void	rijndaelDecrypt(const u32int rk[], int Nr, const uchar ct[16], uchar pt[16]);
@@ -950,7 +950,6 @@ static int rijndaelKeySetupEnc(u32 rk[/*4*(Nr + 1)*/],
 	return 0;
 }
 
-#if 0
 /**
  * Expand the cipher key into the decryption key schedule.
  *
@@ -995,7 +994,6 @@ static int rijndaelKeySetupDec(u32 rk[/*4*(Nr + 1)*/],
 	}
 	return Nr;
 }
-#endif
 
 static void rijndaelEncrypt(const u32 rk[/*4*(Nr + 1)*/], int Nr, const u8 pt[16], u8 ct[16]) {
 	u32 s0, s1, s2, s3, t0, t1, t2, t3;
blob - 294111e26a5b7f1f70c9dff4b943a1d9dc163555
blob + 2cbaeb8c4ea0138597b4dd6f66c0a72882b6b13f
--- src/libsec/port/genrandom.c
+++ src/libsec/port/genrandom.c
@@ -1,4 +1,5 @@
 #include "os.h"
+#include <mp.h>
 #include <libsec.h>
 
 typedef struct State{
blob - 97aa29528b35bbffb49f439677ecd3b23235a793
blob + eb0a7e028c66fdfc1b36f96c96012aa247994f62
--- src/libsec/port/mkfile
+++ src/libsec/port/mkfile
@@ -5,14 +5,55 @@ LIB=libsec.a
 
 OFILES=\
 	aes.$O\
+	blowfish.$O\
+	decodepem.$O\
 	des.$O\
+	des3CBC.$O\
+	des3ECB.$O\
+	desCBC.$O\
+	desECB.$O\
 	desmodes.$O\
+	dsaalloc.$O\
+	dsagen.$O\
+	dsaprimes.$O\
+	dsaprivtopub.$O\
+	dsasign.$O\
+	dsaverify.$O\
+	egalloc.$O\
+	egdecrypt.$O\
+	egencrypt.$O\
+	eggen.$O\
+	egprivtopub.$O\
+	egsign.$O\
+	egverify.$O\
 	fastrand.$O\
+	genprime.$O\
 	genrandom.$O\
+	gensafeprime.$O\
+	genstrongprime.$O\
+	hmac.$O\
+	md4.$O\
 	md5.$O\
 	md5block.$O\
+	md5pickle.$O\
+	nfastrand.$O\
+	prng.$O\
+	probably_prime.$O\
+	rc4.$O\
+	readcert.$O\
+	rsaalloc.$O\
+	rsadecrypt.$O\
+	rsaencrypt.$O\
+	rsafill.$O\
+	rsagen.$O\
+	rsaprivtopub.$O\
 	sha1.$O\
 	sha1block.$O\
+	sha1pickle.$O\
+	smallprimetest.$O\
+	thumb.$O\
+	tlshand.$O\
+	x509.$O\
 
 HFILES=$PLAN9/include/libsec.h
 
blob - dabe75f0dc0c543c74f4050ee8af55d6dbba8a98
blob + 946f028389efc1793d4718a24aa9ebc418e23f7a
--- src/libsec/port/sha1.c
+++ src/libsec/port/sha1.c
@@ -1,5 +1,4 @@
-#include <u.h>
-#include <libc.h>
+#include "os.h"
 #include <libsec.h>
 
 static void encode(uchar*, u32int*, ulong);
@@ -11,8 +10,6 @@ extern void _sha1block(uchar*, ulong, u32int*);
  *  the last call.  There must be room in the input buffer
  *  to pad.
  */
-ulong lastlen;
-
 SHA1state*
 sha1(uchar *p, ulong len, uchar *digest, SHA1state *s)
 {
@@ -21,15 +18,12 @@ sha1(uchar *p, ulong len, uchar *digest, SHA1state *s)
 	int i;
 	uchar *e;
 
-lastlen = len;
 	if(s == nil){
 		s = malloc(sizeof(*s));
 		if(s == nil)
 			return nil;
 		memset(s, 0, sizeof(*s));
 		s->malloced = 1;
-		assert(!s->seeded);
-		assert(!s->blen);
 	}
 
 	if(s->seeded == 0){
@@ -42,11 +36,8 @@ lastlen = len;
 		s->seeded = 1;
 	}
 
-assert(len < 100000);
-
 	/* fill out the partial 64 byte block from previous calls */
 	if(s->blen){
-assert(s);
 		i = 64 - s->blen;
 		if(len < i)
 			i = len;
@@ -61,11 +52,9 @@ assert(s);
 		}
 	}
 
-assert(len < 1000000);
 	/* do 64 byte blocks */
 	i = len & ~0x3f;
 	if(i){
-assert(i < 1000000);
 		_sha1block(p, i, s->state);
 		s->len += i;
 		len -= i;
blob - 0ae920e365350352d1bf30a5c1cd2de4ba552d37
blob + 82566f21029c551fb9cd42bb09c2c86b31959906
--- src/libsec/port/sha1block.c
+++ src/libsec/port/sha1block.c
@@ -1,11 +1,5 @@
-#include <u.h>
-#include <libc.h>
+#include "os.h"
 
-uchar* lastsbp;
-ulong lastsblen;
-u32int* lastsbs;
-Lock slock;
-
 void
 _sha1block(uchar *p, ulong len, u32int *s)
 {
@@ -14,11 +8,6 @@ _sha1block(uchar *p, ulong len, u32int *s)
 	u32int *wp, *wend;
 	u32int w[80];
 
-lock(&slock);
-lastsbp=p;
-lastsblen=len;
-lastsbs=s;
-
 	/* at this point, we have a multiple of 64 bytes */
 	for(end = p+len; p < end;){
 		a = s[0];
@@ -195,5 +184,4 @@ lastsbs=s;
 		s[3] += d;
 		s[4] += e;
 	}
-unlock(&slock);
 }
blob - cbecad53d741034bec9c1f7d1058ea300623e9f9
blob + bcab77c76afa833e411dd0bd509bc9d45f3ec90e
--- src/libthread/main.c
+++ src/libthread/main.c
@@ -45,6 +45,7 @@ main(int argc, char **argv)
 
 	signal(SIGTERM, _threaddie);
 	signal(SIGCHLD, _nop);
+	signal(SIGALRM, _nop);
 //	signal(SIGINFO, _threadstatus);
 //	rfork(RFREND);
 
blob - 60d253df82fa80b2598ce31affc61a475b579b08
blob + 7ecaf7c355f0a030f4bc5211bff3e59a38f8c00d
--- src/libventi/server.c
+++ src/libventi/server.c
@@ -55,11 +55,13 @@ listenproc(void *v)
 
 	srv = v;
 	for(;;){
+fprint(2, "listen for venti\n");
 		ctl = listen(srv->adir, dir);
 		if(ctl < 0){
 			srv->dead = 1;
 			break;
 		}
+fprint(2, "got one\n");
 		sc = vtmallocz(sizeof(VtSconn));
 		sc->ctl = ctl;
 		sc->srv = srv;
blob - 0465041dcfa1f10e9f0c38859bcdb6027b7e772a
blob + 45683608e52ebb5bcf763e113846686d4af82af0
--- src/mkfile
+++ src/mkfile
@@ -1,6 +1,6 @@
 <mkhdr
 
-BUGGERED="String|html|httpd|ip|venti"
+BUGGERED='9p|html|httpd|ip|venti'
 LIBDIRS=`ls -ld lib* | sed -n 's/^d.* //p' |egrep -v "^lib($BUGGERED)$"`
 
 DIRS=\