summaryrefslogtreecommitdiff
path: root/sys/src/cmd/auth
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/auth
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/auth')
-rwxr-xr-xsys/src/cmd/auth/as.c181
-rwxr-xr-xsys/src/cmd/auth/asn12dsa.c70
-rwxr-xr-xsys/src/cmd/auth/asn12rsa.c71
-rwxr-xr-xsys/src/cmd/auth/authcmdlib.h68
-rwxr-xr-xsys/src/cmd/auth/authsrv.c914
-rwxr-xr-xsys/src/cmd/auth/challenge.c59
-rwxr-xr-xsys/src/cmd/auth/changeuser.c144
-rwxr-xr-xsys/src/cmd/auth/convbio.c133
-rwxr-xr-xsys/src/cmd/auth/convkeys.c162
-rwxr-xr-xsys/src/cmd/auth/convkeys2.c132
-rwxr-xr-xsys/src/cmd/auth/cron.c744
-rwxr-xr-xsys/src/cmd/auth/debug.c344
-rwxr-xr-xsys/src/cmd/auth/disable5
-rwxr-xr-xsys/src/cmd/auth/dsa2pub.c44
-rwxr-xr-xsys/src/cmd/auth/dsa2ssh.c48
-rwxr-xr-xsys/src/cmd/auth/dsagen.c46
-rwxr-xr-xsys/src/cmd/auth/enable5
-rwxr-xr-xsys/src/cmd/auth/factotum/apop.c324
-rwxr-xr-xsys/src/cmd/auth/factotum/chap.c452
-rwxr-xr-xsys/src/cmd/auth/factotum/confirm.c204
-rwxr-xr-xsys/src/cmd/auth/factotum/dat.h238
-rwxr-xr-xsys/src/cmd/auth/factotum/fgui.c780
-rwxr-xr-xsys/src/cmd/auth/factotum/fs.c601
-rwxr-xr-xsys/src/cmd/auth/factotum/httpdigest.c184
-rwxr-xr-xsys/src/cmd/auth/factotum/log.c110
-rwxr-xr-xsys/src/cmd/auth/factotum/mkfile44
-rwxr-xr-xsys/src/cmd/auth/factotum/p9any.c424
-rwxr-xr-xsys/src/cmd/auth/factotum/p9cr.c363
-rwxr-xr-xsys/src/cmd/auth/factotum/p9sk1.c486
-rwxr-xr-xsys/src/cmd/auth/factotum/pass.c99
-rwxr-xr-xsys/src/cmd/auth/factotum/rpc.c508
-rwxr-xr-xsys/src/cmd/auth/factotum/rsa.c187
-rwxr-xr-xsys/src/cmd/auth/factotum/secstore.c628
-rwxr-xr-xsys/src/cmd/auth/factotum/sshrsa.c180
-rwxr-xr-xsys/src/cmd/auth/factotum/util.c1002
-rwxr-xr-xsys/src/cmd/auth/factotum/wep.c128
-rwxr-xr-xsys/src/cmd/auth/guard.srv.c156
-rwxr-xr-xsys/src/cmd/auth/keyfs.c1113
-rwxr-xr-xsys/src/cmd/auth/lib/error.c20
-rwxr-xr-xsys/src/cmd/auth/lib/fs.c10
-rwxr-xr-xsys/src/cmd/auth/lib/getauthkey.c27
-rwxr-xr-xsys/src/cmd/auth/lib/getexpiration.c77
-rwxr-xr-xsys/src/cmd/auth/lib/keyfmt.c29
-rwxr-xr-xsys/src/cmd/auth/lib/log.c45
-rwxr-xr-xsys/src/cmd/auth/lib/mkfile30
-rwxr-xr-xsys/src/cmd/auth/lib/netcheck.c111
-rwxr-xr-xsys/src/cmd/auth/lib/okpasswd.c42
-rwxr-xr-xsys/src/cmd/auth/lib/querybio.c66
-rwxr-xr-xsys/src/cmd/auth/lib/rdbio.c58
-rwxr-xr-xsys/src/cmd/auth/lib/readarg.c23
-rwxr-xr-xsys/src/cmd/auth/lib/readln.c115
-rwxr-xr-xsys/src/cmd/auth/lib/readn.c20
-rwxr-xr-xsys/src/cmd/auth/lib/readwrite.c90
-rwxr-xr-xsys/src/cmd/auth/lib/wrbio.c40
-rwxr-xr-xsys/src/cmd/auth/login.c216
-rwxr-xr-xsys/src/cmd/auth/mkfile139
-rwxr-xr-xsys/src/cmd/auth/netkey.c49
-rwxr-xr-xsys/src/cmd/auth/newns.c62
-rwxr-xr-xsys/src/cmd/auth/none.c55
-rwxr-xr-xsys/src/cmd/auth/passwd.c139
-rwxr-xr-xsys/src/cmd/auth/pemdecode.c59
-rwxr-xr-xsys/src/cmd/auth/pemencode.c64
-rwxr-xr-xsys/src/cmd/auth/printnetkey.c44
-rwxr-xr-xsys/src/cmd/auth/readnvram.c36
-rwxr-xr-xsys/src/cmd/auth/respond.c34
-rwxr-xr-xsys/src/cmd/auth/rsa2any.c301
-rwxr-xr-xsys/src/cmd/auth/rsa2any.h6
-rwxr-xr-xsys/src/cmd/auth/rsa2csr.c43
-rwxr-xr-xsys/src/cmd/auth/rsa2pub.c44
-rwxr-xr-xsys/src/cmd/auth/rsa2ssh.c35
-rwxr-xr-xsys/src/cmd/auth/rsa2x509.c50
-rwxr-xr-xsys/src/cmd/auth/rsafill.c46
-rwxr-xr-xsys/src/cmd/auth/rsagen.c60
-rwxr-xr-xsys/src/cmd/auth/secstore/SConn.c212
-rwxr-xr-xsys/src/cmd/auth/secstore/SConn.h35
-rwxr-xr-xsys/src/cmd/auth/secstore/aescbc.c150
-rwxr-xr-xsys/src/cmd/auth/secstore/dirls.c87
-rwxr-xr-xsys/src/cmd/auth/secstore/mkfile51
-rwxr-xr-xsys/src/cmd/auth/secstore/pak.c344
-rwxr-xr-xsys/src/cmd/auth/secstore/password.c146
-rwxr-xr-xsys/src/cmd/auth/secstore/secchk.c31
-rwxr-xr-xsys/src/cmd/auth/secstore/secstore.c570
-rwxr-xr-xsys/src/cmd/auth/secstore/secstore.h36
-rwxr-xr-xsys/src/cmd/auth/secstore/secstored.c396
-rwxr-xr-xsys/src/cmd/auth/secstore/secuser.c227
-rwxr-xr-xsys/src/cmd/auth/secstore/util.c110
-rwxr-xr-xsys/src/cmd/auth/secureidcheck.c466
-rwxr-xr-xsys/src/cmd/auth/status36
-rwxr-xr-xsys/src/cmd/auth/uniq.c92
-rwxr-xr-xsys/src/cmd/auth/userpasswd.c34
-rwxr-xr-xsys/src/cmd/auth/warning.c322
-rwxr-xr-xsys/src/cmd/auth/wrkey.c13
92 files changed, 17024 insertions, 0 deletions
diff --git a/sys/src/cmd/auth/as.c b/sys/src/cmd/auth/as.c
new file mode 100755
index 000000000..e4f03852a
--- /dev/null
+++ b/sys/src/cmd/auth/as.c
@@ -0,0 +1,181 @@
+/*
+ * as user cmd [arg...] - run cmd with args as user on this cpu server.
+ * must be hostowner for this to work.
+ * needs #¤/caphash and #¤/capuse.
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <auth.h>
+#include "authcmdlib.h"
+
+int debug;
+
+int becomeuser(char*);
+void createuser(void);
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+void initcap(void);
+int mkcmd(char*, char*, int);
+int myauth(int, char*);
+int qidcmp(Qid, Qid);
+void runas(char *, char *);
+void usage(void);
+
+#pragma varargck argpos clog 1
+#pragma varargck argpos fatal 1
+
+static void
+fatal(char *fmt, ...)
+{
+ char msg[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(msg, msg + sizeof msg, fmt, arg);
+ va_end(arg);
+ error("%s", msg);
+}
+
+void
+main(int argc, char *argv[])
+{
+ debug = 0;
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ initcap();
+ srand(getpid()*time(0));
+ runas(argv[0], argv[1]);
+}
+
+void
+runas(char *user, char *cmd)
+{
+ if(becomeuser(user) < 0)
+ sysfatal("can't change uid for %s: %r", user);
+ putenv("service", "rx");
+ execl("/bin/rc", "rc", "-lc", cmd, nil);
+ sysfatal("exec /bin/rc: %r");
+}
+
+void *
+emalloc(ulong n)
+{
+ void *p;
+
+ if(p = mallocz(n, 1))
+ return p;
+ fatal("out of memory");
+ return 0;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+ if(p = realloc(p, n))
+ return p;
+ fatal("out of memory");
+ return 0;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-c]\n", argv0);
+ exits("usage");
+}
+
+void
+memrandom(void *p, int n)
+{
+ uchar *cp;
+
+ for(cp = (uchar*)p; n > 0; n--)
+ *cp++ = fastrand();
+}
+
+/*
+ * keep caphash fd open since opens of it could be disabled
+ */
+static int caphashfd;
+
+void
+initcap(void)
+{
+ caphashfd = open("#¤/caphash", OCEXEC|OWRITE);
+ if(caphashfd < 0)
+ fprint(2, "%s: opening #¤/caphash: %r\n", argv0);
+}
+
+/*
+ * create a change uid capability
+ */
+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;
+}
+
+int
+usecap(char *cap)
+{
+ int fd, rv;
+
+ fd = open("#¤/capuse", OWRITE);
+ if(fd < 0)
+ return -1;
+ rv = write(fd, cap, strlen(cap));
+ close(fd);
+ return rv;
+}
+
+int
+becomeuser(char *new)
+{
+ char *cap;
+ int rv;
+
+ cap = mkcap(getuser(), new);
+ if(cap == nil)
+ return -1;
+ rv = usecap(cap);
+ free(cap);
+
+ newns(new, nil);
+ return rv;
+}
diff --git a/sys/src/cmd/auth/asn12dsa.c b/sys/src/cmd/auth/asn12dsa.c
new file mode 100755
index 000000000..7df5dd781
--- /dev/null
+++ b/sys/src/cmd/auth/asn12dsa.c
@@ -0,0 +1,70 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
+
+void
+usage(void)
+{
+ fprint(2, "auth/asn12dsa [-t tag] [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *s;
+ uchar *buf;
+ int fd;
+ long n, tot;
+ char *tag, *file;
+ DSApriv *key;
+
+ fmtinstall('B', mpfmt);
+
+ tag = nil;
+ ARGBEGIN{
+ case 't':
+ tag = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0 && argc != 1)
+ usage();
+
+ if(argc == 1)
+ file = argv[0];
+ else
+ file = "/dev/stdin";
+
+ if((fd = open(file, OREAD)) < 0)
+ sysfatal("open %s: %r", file);
+ buf = nil;
+ tot = 0;
+ for(;;){
+ buf = realloc(buf, tot+8192);
+ if(buf == nil)
+ sysfatal("realloc: %r");
+ if((n = read(fd, buf+tot, 8192)) < 0)
+ sysfatal("read: %r");
+ if(n == 0)
+ break;
+ tot += n;
+ }
+
+ key = asn1toDSApriv(buf, tot);
+ if(key == nil)
+ sysfatal("couldn't parse asn1 key");
+
+ s = smprint("key proto=dsa %s%sp=%B q=%B alpha=%B key=%B !secret=%B\n",
+ tag ? tag : "", tag ? " " : "",
+ key->pub.p, key->pub.q, key->pub.alpha, key->pub.key,
+ key->secret);
+ if(s == nil)
+ sysfatal("smprint: %r");
+ write(1, s, strlen(s));
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/asn12rsa.c b/sys/src/cmd/auth/asn12rsa.c
new file mode 100755
index 000000000..fa1682c9c
--- /dev/null
+++ b/sys/src/cmd/auth/asn12rsa.c
@@ -0,0 +1,71 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
+
+void
+usage(void)
+{
+ fprint(2, "auth/asn12rsa [-t tag] [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *s;
+ uchar *buf;
+ int fd;
+ long n, tot;
+ char *tag, *file;
+ RSApriv *key;
+
+ fmtinstall('B', mpfmt);
+
+ tag = nil;
+ ARGBEGIN{
+ case 't':
+ tag = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0 && argc != 1)
+ usage();
+
+ if(argc == 1)
+ file = argv[0];
+ else
+ file = "#d/0";
+
+ if((fd = open(file, OREAD)) < 0)
+ sysfatal("open %s: %r", file);
+ buf = nil;
+ tot = 0;
+ for(;;){
+ buf = realloc(buf, tot+8192);
+ if(buf == nil)
+ sysfatal("realloc: %r");
+ if((n = read(fd, buf+tot, 8192)) < 0)
+ sysfatal("read: %r");
+ if(n == 0)
+ break;
+ tot += n;
+ }
+
+ key = asn1toRSApriv(buf, tot);
+ if(key == nil)
+ sysfatal("couldn't parse asn1 key");
+
+ s = smprint("key proto=rsa %s%ssize=%d ek=%B !dk=%B n=%B !p=%B !q=%B !kp=%B !kq=%B !c2=%B\n",
+ tag ? tag : "", tag ? " " : "",
+ mpsignif(key->pub.n), key->pub.ek,
+ key->dk, key->pub.n, key->p, key->q,
+ key->kp, key->kq, key->c2);
+ if(s == nil)
+ sysfatal("smprint: %r");
+ write(1, s, strlen(s));
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/authcmdlib.h b/sys/src/cmd/auth/authcmdlib.h
new file mode 100755
index 000000000..78a3a7078
--- /dev/null
+++ b/sys/src/cmd/auth/authcmdlib.h
@@ -0,0 +1,68 @@
+#pragma lib "./lib.$O.a"
+
+enum{
+ PASSLEN = 10,
+ MAXNETCHAL = 100000, /* max securenet challenge */
+ Maxpath = 256,
+};
+
+#define KEYDB "/mnt/keys"
+#define NETKEYDB "/mnt/netkeys"
+#define KEYDBBUF (sizeof NETKEYDB) /* enough for any keydb prefix */
+#define AUTHLOG "auth"
+
+enum
+{
+ Nemail = 10,
+ Plan9 = 1,
+ Securenet = 2,
+};
+
+typedef struct
+{
+ char *user;
+ char *postid;
+ char *name;
+ char *dept;
+ char *email[Nemail];
+} Acctbio;
+
+typedef struct {
+ char *keys;
+ char *msg;
+ char *who;
+ Biobuf *b;
+} Fs;
+
+extern Fs fs[3];
+
+void checksum(char*, char*);
+void error(char*, ...);
+void fail(char*);
+char* findkey(char*, char*, char*);
+char* findsecret(char*, char*, char*);
+int getauthkey(char*);
+long getexpiration(char *db, char *u);
+void getpass(char*, char*, int, int);
+int getsecret(int, char*);
+int keyfmt(Fmt*);
+void logfail(char*);
+int netcheck(void*, long, char*);
+char* netdecimal(char*);
+char* netresp(char*, long, char*);
+char* okpasswd(char*);
+int querybio(char*, char*, Acctbio*);
+void rdbio(char*, char*, Acctbio*);
+int readarg(int, char*, int);
+int readfile(char*, char*, int);
+void readln(char*, char*, int, int);
+long readn(int, void*, long);
+char* secureidcheck(char*, char*);
+char* setkey(char*, char*, char*);
+char* setsecret(char*, char*, char*);
+int smartcheck(void*, long, char*);
+void succeed(char*);
+void wrbio(char*, Acctbio*);
+int writefile(char*, char*, int);
+
+#pragma varargck type "K" char*
diff --git a/sys/src/cmd/auth/authsrv.c b/sys/src/cmd/auth/authsrv.c
new file mode 100755
index 000000000..491eec42c
--- /dev/null
+++ b/sys/src/cmd/auth/authsrv.c
@@ -0,0 +1,914 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <regexp.h>
+#include <mp.h>
+#include <libsec.h>
+#include <authsrv.h>
+#include "authcmdlib.h"
+
+int debug;
+Ndb *db;
+char raddr[128];
+
+/* Microsoft auth constants */
+enum {
+ MShashlen = 16,
+ MSchallen = 8,
+ MSresplen = 24,
+};
+
+int ticketrequest(Ticketreq*);
+void challengebox(Ticketreq*);
+void changepasswd(Ticketreq*);
+void apop(Ticketreq*, int);
+void chap(Ticketreq*);
+void mschap(Ticketreq*);
+void http(Ticketreq*);
+void vnc(Ticketreq*);
+int speaksfor(char*, char*);
+void replyerror(char*, ...);
+void getraddr(char*);
+void mkkey(char*);
+void randombytes(uchar*, int);
+void nthash(uchar hash[MShashlen], char *passwd);
+void lmhash(uchar hash[MShashlen], char *passwd);
+void mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen]);
+void desencrypt(uchar data[8], uchar key[7]);
+int tickauthreply(Ticketreq*, char*);
+void safecpy(char*, char*, int);
+
+
+void
+main(int argc, char *argv[])
+{
+ char buf[TICKREQLEN];
+ Ticketreq tr;
+
+ ARGBEGIN{
+ case 'd':
+ debug++;
+ }ARGEND
+
+ strcpy(raddr, "unknown");
+ if(argc >= 1)
+ getraddr(argv[argc-1]);
+
+ alarm(10*60*1000); /* kill a connection after 10 minutes */
+
+ db = ndbopen("/lib/ndb/auth");
+ if(db == 0)
+ syslog(0, AUTHLOG, "no /lib/ndb/auth");
+
+ srand(time(0)*getpid());
+ for(;;){
+ if(readn(0, buf, TICKREQLEN) <= 0)
+ exits(0);
+
+ convM2TR(buf, &tr);
+ switch(buf[0]){
+ case AuthTreq:
+ ticketrequest(&tr);
+ break;
+ case AuthChal:
+ challengebox(&tr);
+ break;
+ case AuthPass:
+ changepasswd(&tr);
+ break;
+ case AuthApop:
+ apop(&tr, AuthApop);
+ break;
+ case AuthChap:
+ chap(&tr);
+ break;
+ case AuthMSchap:
+ mschap(&tr);
+ break;
+ case AuthCram:
+ apop(&tr, AuthCram);
+ break;
+ case AuthHttp:
+ http(&tr);
+ break;
+ case AuthVNC:
+ vnc(&tr);
+ break;
+ default:
+ syslog(0, AUTHLOG, "unknown ticket request type: %d", buf[0]);
+ exits(0);
+ }
+ }
+ /* not reached */
+}
+
+int
+ticketrequest(Ticketreq *tr)
+{
+ char akey[DESKEYLEN];
+ char hkey[DESKEYLEN];
+ Ticket t;
+ char tbuf[2*TICKETLEN+1];
+
+ if(findkey(KEYDB, tr->authid, akey) == 0){
+ /* make one up so caller doesn't know it was wrong */
+ mkkey(akey);
+ if(debug)
+ syslog(0, AUTHLOG, "tr-fail authid %s", raddr);
+ }
+ if(findkey(KEYDB, tr->hostid, hkey) == 0){
+ /* make one up so caller doesn't know it was wrong */
+ mkkey(hkey);
+ if(debug)
+ syslog(0, AUTHLOG, "tr-fail hostid %s(%s)", tr->hostid, raddr);
+ }
+
+ memset(&t, 0, sizeof(t));
+ memmove(t.chal, tr->chal, CHALLEN);
+ strcpy(t.cuid, tr->uid);
+ if(speaksfor(tr->hostid, tr->uid))
+ strcpy(t.suid, tr->uid);
+ else {
+ mkkey(akey);
+ mkkey(hkey);
+ if(debug)
+ syslog(0, AUTHLOG, "tr-fail %s@%s(%s) -> %s@%s no speaks for",
+ tr->uid, tr->hostid, raddr, tr->uid, tr->authid);
+ }
+
+ mkkey(t.key);
+
+ tbuf[0] = AuthOK;
+ t.num = AuthTc;
+ convT2M(&t, tbuf+1, hkey);
+ t.num = AuthTs;
+ convT2M(&t, tbuf+1+TICKETLEN, akey);
+ if(write(1, tbuf, 2*TICKETLEN+1) < 0){
+ if(debug)
+ syslog(0, AUTHLOG, "tr-fail %s@%s(%s): hangup",
+ tr->uid, tr->hostid, raddr);
+ exits(0);
+ }
+ if(debug)
+ syslog(0, AUTHLOG, "tr-ok %s@%s(%s) -> %s@%s",
+ tr->uid, tr->hostid, raddr, tr->uid, tr->authid);
+
+ return 0;
+}
+
+void
+challengebox(Ticketreq *tr)
+{
+ long chal;
+ char *key, *netkey;
+ char kbuf[DESKEYLEN], nkbuf[DESKEYLEN], hkey[DESKEYLEN];
+ char buf[NETCHLEN+1];
+ char *err;
+
+ key = findkey(KEYDB, tr->uid, kbuf);
+ netkey = findkey(NETKEYDB, tr->uid, nkbuf);
+ if(key == 0 && netkey == 0){
+ /* make one up so caller doesn't know it was wrong */
+ mkkey(nkbuf);
+ netkey = nkbuf;
+ if(debug)
+ syslog(0, AUTHLOG, "cr-fail uid %s@%s", tr->uid, raddr);
+ }
+ if(findkey(KEYDB, tr->hostid, hkey) == 0){
+ /* make one up so caller doesn't know it was wrong */
+ mkkey(hkey);
+ if(debug)
+ syslog(0, AUTHLOG, "cr-fail hostid %s %s@%s", tr->hostid,
+ tr->uid, raddr);
+ }
+
+ /*
+ * challenge-response
+ */
+ memset(buf, 0, sizeof(buf));
+ buf[0] = AuthOK;
+ chal = lnrand(MAXNETCHAL);
+ sprint(buf+1, "%lud", chal);
+ if(write(1, buf, NETCHLEN+1) < 0)
+ exits(0);
+ if(readn(0, buf, NETCHLEN) < 0)
+ exits(0);
+ if(!(key && netcheck(key, chal, buf))
+ && !(netkey && netcheck(netkey, chal, buf))
+ && (err = secureidcheck(tr->uid, buf)) != nil){
+ replyerror("cr-fail %s %s %s", err, tr->uid, raddr);
+ logfail(tr->uid);
+ if(debug)
+ syslog(0, AUTHLOG, "cr-fail %s@%s(%s): bad resp",
+ tr->uid, tr->hostid, raddr);
+ return;
+ }
+ succeed(tr->uid);
+
+ /*
+ * reply with ticket & authenticator
+ */
+ if(tickauthreply(tr, hkey) < 0){
+ if(debug)
+ syslog(0, AUTHLOG, "cr-fail %s@%s(%s): hangup",
+ tr->uid, tr->hostid, raddr);
+ exits(0);
+ }
+
+ if(debug)
+ syslog(0, AUTHLOG, "cr-ok %s@%s(%s)",
+ tr->uid, tr->hostid, raddr);
+}
+
+void
+changepasswd(Ticketreq *tr)
+{
+ Ticket t;
+ char tbuf[TICKETLEN+1];
+ char prbuf[PASSREQLEN];
+ Passwordreq pr;
+ char okey[DESKEYLEN], nkey[DESKEYLEN];
+ char *err;
+
+ if(findkey(KEYDB, tr->uid, okey) == 0){
+ /* make one up so caller doesn't know it was wrong */
+ mkkey(okey);
+ syslog(0, AUTHLOG, "cp-fail uid %s", raddr);
+ }
+
+ /* send back a ticket with a new key */
+ memmove(t.chal, tr->chal, CHALLEN);
+ mkkey(t.key);
+ tbuf[0] = AuthOK;
+ t.num = AuthTp;
+ safecpy(t.cuid, tr->uid, sizeof(t.cuid));
+ safecpy(t.suid, tr->uid, sizeof(t.suid));
+ convT2M(&t, tbuf+1, okey);
+ write(1, tbuf, sizeof(tbuf));
+
+ /* loop trying passwords out */
+ for(;;){
+ if(readn(0, prbuf, PASSREQLEN) < 0)
+ exits(0);
+ convM2PR(prbuf, &pr, t.key);
+ if(pr.num != AuthPass){
+ replyerror("protocol botch1: %s", raddr);
+ exits(0);
+ }
+ passtokey(nkey, pr.old);
+ if(memcmp(nkey, okey, DESKEYLEN)){
+ replyerror("protocol botch2: %s", raddr);
+ continue;
+ }
+ if(*pr.new){
+ err = okpasswd(pr.new);
+ if(err){
+ replyerror("%s %s", err, raddr);
+ continue;
+ }
+ passtokey(nkey, pr.new);
+ }
+ if(pr.changesecret && setsecret(KEYDB, tr->uid, pr.secret) == 0){
+ replyerror("can't write secret %s", raddr);
+ continue;
+ }
+ if(*pr.new && setkey(KEYDB, tr->uid, nkey) == 0){
+ replyerror("can't write key %s", raddr);
+ continue;
+ }
+ break;
+ }
+
+ prbuf[0] = AuthOK;
+ write(1, prbuf, 1);
+ succeed(tr->uid);
+ return;
+}
+
+void
+http(Ticketreq *tr)
+{
+ Ticket t;
+ char tbuf[TICKETLEN+1];
+ char key[DESKEYLEN];
+ char *p;
+ Biobuf *b;
+ int n;
+
+ n = strlen(tr->uid);
+ b = Bopen("/sys/lib/httppasswords", OREAD);
+ if(b == nil){
+ replyerror("no password file", raddr);
+ return;
+ }
+
+ /* find key */
+ for(;;){
+ p = Brdline(b, '\n');
+ if(p == nil)
+ break;
+ p[Blinelen(b)-1] = 0;
+ if(strncmp(p, tr->uid, n) == 0)
+ if(p[n] == ' ' || p[n] == '\t'){
+ p += n;
+ break;
+ }
+ }
+ Bterm(b);
+ if(p == nil) {
+ randombytes((uchar*)key, DESKEYLEN);
+ } else {
+ while(*p == ' ' || *p == '\t')
+ p++;
+ passtokey(key, p);
+ }
+
+ /* send back a ticket encrypted with the key */
+ randombytes((uchar*)t.chal, CHALLEN);
+ mkkey(t.key);
+ tbuf[0] = AuthOK;
+ t.num = AuthHr;
+ safecpy(t.cuid, tr->uid, sizeof(t.cuid));
+ safecpy(t.suid, tr->uid, sizeof(t.suid));
+ convT2M(&t, tbuf+1, key);
+ write(1, tbuf, sizeof(tbuf));
+}
+
+static char*
+domainname(void)
+{
+ static char sysname[Maxpath];
+ static char *domain;
+ int n;
+
+ if(domain)
+ return domain;
+ if(*sysname)
+ return sysname;
+
+ domain = csgetvalue(0, "sys", sysname, "dom", nil);
+ if(domain)
+ return domain;
+
+ n = readfile("/dev/sysname", sysname, sizeof(sysname)-1);
+ if(n < 0){
+ strcpy(sysname, "kremvax");
+ return sysname;
+ }
+ sysname[n] = 0;
+
+ return sysname;
+}
+
+static int
+h2b(char c)
+{
+ if(c >= '0' && c <= '9')
+ return c - '0';
+ if(c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ if(c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ return 0;
+}
+
+void
+apop(Ticketreq *tr, int type)
+{
+ int challen, i, tries;
+ char *secret, *hkey, *p;
+ Ticketreq treq;
+ DigestState *s;
+ char sbuf[SECRETLEN], hbuf[DESKEYLEN];
+ char tbuf[TICKREQLEN];
+ char buf[MD5dlen*2];
+ uchar digest[MD5dlen], resp[MD5dlen];
+ ulong rb[4];
+ char chal[256];
+
+ USED(tr);
+
+ /*
+ * Create a challenge and send it.
+ */
+ randombytes((uchar*)rb, sizeof(rb));
+ p = chal;
+ p += snprint(p, sizeof(chal), "<%lux%lux.%lux%lux@%s>",
+ rb[0], rb[1], rb[2], rb[3], domainname());
+ challen = p - chal;
+ print("%c%-5d%s", AuthOKvar, challen, chal);
+
+ /* give user a few attempts */
+ for(tries = 0; ; tries++) {
+ /*
+ * get ticket request
+ */
+ if(readn(0, tbuf, TICKREQLEN) < 0)
+ exits(0);
+ convM2TR(tbuf, &treq);
+ tr = &treq;
+ if(tr->type != type)
+ exits(0);
+
+ /*
+ * read response
+ */
+ if(readn(0, buf, MD5dlen*2) < 0)
+ exits(0);
+ for(i = 0; i < MD5dlen; i++)
+ resp[i] = (h2b(buf[2*i])<<4)|h2b(buf[2*i+1]);
+
+ /*
+ * lookup
+ */
+ secret = findsecret(KEYDB, tr->uid, sbuf);
+ hkey = findkey(KEYDB, tr->hostid, hbuf);
+ if(hkey == 0 || secret == 0){
+ replyerror("apop-fail bad response %s", raddr);
+ logfail(tr->uid);
+ if(tries > 5)
+ return;
+ continue;
+ }
+
+ /*
+ * check for match
+ */
+ if(type == AuthCram){
+ hmac_md5((uchar*)chal, challen,
+ (uchar*)secret, strlen(secret),
+ digest, nil);
+ } else {
+ s = md5((uchar*)chal, challen, 0, 0);
+ md5((uchar*)secret, strlen(secret), digest, s);
+ }
+ if(memcmp(digest, resp, MD5dlen) != 0){
+ replyerror("apop-fail bad response %s", raddr);
+ logfail(tr->uid);
+ if(tries > 5)
+ return;
+ continue;
+ }
+ break;
+ }
+
+ succeed(tr->uid);
+
+ /*
+ * reply with ticket & authenticator
+ */
+ if(tickauthreply(tr, hkey) < 0)
+ exits(0);
+
+ if(debug){
+ if(type == AuthCram)
+ syslog(0, AUTHLOG, "cram-ok %s %s", tr->uid, raddr);
+ else
+ syslog(0, AUTHLOG, "apop-ok %s %s", tr->uid, raddr);
+ }
+}
+
+enum {
+ VNCchallen= 16,
+};
+
+/* VNC reverses the bits of each byte before using as a des key */
+uchar swizzletab[256] = {
+ 0x0, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x8, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x4, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0xc, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x2, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0xa, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x6, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0xe, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x1, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x9, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x5, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0xd, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x3, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0xb, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x7, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0xf, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+void
+vnc(Ticketreq *tr)
+{
+ uchar chal[VNCchallen+6];
+ uchar reply[VNCchallen];
+ char *secret, *hkey;
+ char sbuf[SECRETLEN], hbuf[DESKEYLEN];
+ DESstate s;
+ int i;
+
+ /*
+ * Create a challenge and send it.
+ */
+ randombytes(chal+6, VNCchallen);
+ chal[0] = AuthOKvar;
+ sprint((char*)chal+1, "%-5d", VNCchallen);
+ if(write(1, chal, sizeof(chal)) != sizeof(chal))
+ return;
+
+ /*
+ * lookup keys (and swizzle bits)
+ */
+ memset(sbuf, 0, sizeof(sbuf));
+ secret = findsecret(KEYDB, tr->uid, sbuf);
+ if(secret == 0){
+ randombytes((uchar*)sbuf, sizeof(sbuf));
+ secret = sbuf;
+ }
+ for(i = 0; i < 8; i++)
+ secret[i] = swizzletab[(uchar)secret[i]];
+
+ hkey = findkey(KEYDB, tr->hostid, hbuf);
+ if(hkey == 0){
+ randombytes((uchar*)hbuf, sizeof(hbuf));
+ hkey = hbuf;
+ }
+
+ /*
+ * get response
+ */
+ if(readn(0, reply, sizeof(reply)) != sizeof(reply))
+ return;
+
+ /*
+ * decrypt response and compare
+ */
+ setupDESstate(&s, (uchar*)secret, nil);
+ desECBdecrypt(reply, sizeof(reply), &s);
+ if(memcmp(reply, chal+6, VNCchallen) != 0){
+ replyerror("vnc-fail bad response %s", raddr);
+ logfail(tr->uid);
+ return;
+ }
+ succeed(tr->uid);
+
+ /*
+ * reply with ticket & authenticator
+ */
+ if(tickauthreply(tr, hkey) < 0)
+ exits(0);
+
+ if(debug)
+ syslog(0, AUTHLOG, "vnc-ok %s %s", tr->uid, raddr);
+}
+
+void
+chap(Ticketreq *tr)
+{
+ char *secret, *hkey;
+ DigestState *s;
+ char sbuf[SECRETLEN], hbuf[DESKEYLEN];
+ uchar digest[MD5dlen];
+ char chal[CHALLEN];
+ OChapreply reply;
+
+ /*
+ * Create a challenge and send it.
+ */
+ randombytes((uchar*)chal, sizeof(chal));
+ write(1, chal, sizeof(chal));
+
+ /*
+ * get chap reply
+ */
+ if(readn(0, &reply, sizeof(reply)) < 0)
+ exits(0);
+ safecpy(tr->uid, reply.uid, sizeof(tr->uid));
+
+ /*
+ * lookup
+ */
+ secret = findsecret(KEYDB, tr->uid, sbuf);
+ hkey = findkey(KEYDB, tr->hostid, hbuf);
+ if(hkey == 0 || secret == 0){
+ replyerror("chap-fail bad response %s", raddr);
+ logfail(tr->uid);
+ exits(0);
+ }
+
+ /*
+ * check for match
+ */
+ s = md5(&reply.id, 1, 0, 0);
+ md5((uchar*)secret, strlen(secret), 0, s);
+ md5((uchar*)chal, sizeof(chal), digest, s);
+
+ if(memcmp(digest, reply.resp, MD5dlen) != 0){
+ replyerror("chap-fail bad response %s", raddr);
+ logfail(tr->uid);
+ exits(0);
+ }
+
+ succeed(tr->uid);
+
+ /*
+ * reply with ticket & authenticator
+ */
+ if(tickauthreply(tr, hkey) < 0)
+ exits(0);
+
+ if(debug)
+ syslog(0, AUTHLOG, "chap-ok %s %s", tr->uid, raddr);
+}
+
+void
+printresp(uchar resp[MSresplen])
+{
+ char buf[200], *p;
+ int i;
+
+ p = buf;
+ for(i=0; i<MSresplen; i++)
+ p += sprint(p, "%.2ux ", resp[i]);
+ syslog(0, AUTHLOG, "resp = %s", buf);
+}
+
+
+void
+mschap(Ticketreq *tr)
+{
+
+ char *secret, *hkey;
+ char sbuf[SECRETLEN], hbuf[DESKEYLEN];
+ uchar chal[CHALLEN];
+ uchar hash[MShashlen];
+ uchar hash2[MShashlen];
+ uchar resp[MSresplen];
+ OMSchapreply reply;
+ int dupe, lmok, ntok;
+ DigestState *s;
+ uchar digest[SHA1dlen];
+
+ /*
+ * Create a challenge and send it.
+ */
+ randombytes((uchar*)chal, sizeof(chal));
+ write(1, chal, sizeof(chal));
+
+ /*
+ * get chap reply
+ */
+ if(readn(0, &reply, sizeof(reply)) < 0)
+ exits(0);
+
+ safecpy(tr->uid, reply.uid, sizeof(tr->uid));
+ /*
+ * lookup
+ */
+ secret = findsecret(KEYDB, tr->uid, sbuf);
+ hkey = findkey(KEYDB, tr->hostid, hbuf);
+ if(hkey == 0 || secret == 0){
+ replyerror("mschap-fail bad response %s/%s(%s)",
+ tr->uid, tr->hostid, raddr);
+ logfail(tr->uid);
+ exits(0);
+ }
+
+ lmhash(hash, secret);
+ mschalresp(resp, hash, chal);
+ lmok = memcmp(resp, reply.LMresp, MSresplen) == 0;
+ nthash(hash, secret);
+ mschalresp(resp, hash, chal);
+ ntok = memcmp(resp, reply.NTresp, MSresplen) == 0;
+ dupe = memcmp(reply.LMresp, reply.NTresp, MSresplen) == 0;
+
+ /*
+ * It is valid to send the same response in both the LM and NTLM
+ * fields provided one of them is correct, if neither matches,
+ * or the two fields are different and either fails to match,
+ * the whole sha-bang fails.
+ *
+ * This is an improvement in security as it allows clients who
+ * wish to do NTLM auth (which is insecure) not to send
+ * LM tokens (which is very insecure).
+ *
+ * Windows servers supports clients doing this also though
+ * windows clients don't seem to use the feature.
+ */
+ if((!ntok && !lmok) || ((!ntok || !lmok) && !dupe)){
+ replyerror("mschap-fail bad response %s/%s(%s) %d,%d,%d",
+ tr->uid, tr->hostid, raddr, dupe, lmok, ntok);
+ logfail(tr->uid);
+ exits(0);
+ }
+
+ succeed(tr->uid);
+
+ /*
+ * reply with ticket & authenticator
+ */
+ if(tickauthreply(tr, hkey) < 0)
+ exits(0);
+
+ if(debug)
+ replyerror("mschap-ok %s/%s(%s) %ux",
+ tr->uid, tr->hostid, raddr);
+
+ nthash(hash, secret);
+ md4(hash, 16, hash2, 0);
+ s = sha1(hash2, 16, 0, 0);
+ sha1(hash2, 16, 0, s);
+ sha1(chal, 8, digest, s);
+
+ if(write(1, digest, 16) < 0)
+ exits(0);
+}
+
+void
+nthash(uchar hash[MShashlen], char *passwd)
+{
+ uchar buf[512];
+ int i;
+
+ for (i = 0; *passwd && i + 1 < sizeof(buf);) {
+ Rune r;
+ passwd += chartorune(&r, passwd);
+ buf[i++] = r;
+ buf[i++] = r >> 8;
+ }
+
+ memset(hash, 0, 16);
+
+ md4(buf, i, hash, 0);
+}
+
+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);
+}
+
+void
+mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen])
+{
+ int i;
+ uchar buf[21];
+
+ memset(buf, 0, sizeof(buf));
+ memcpy(buf, hash, MShashlen);
+
+ for(i=0; i<3; i++) {
+ memmove(resp+i*MSchallen, chal, MSchallen);
+ desencrypt(resp+i*MSchallen, buf+i*7);
+ }
+}
+
+void
+desencrypt(uchar data[8], uchar key[7])
+{
+ ulong ekey[32];
+
+ key_setup(key, ekey);
+ block_cipher(ekey, data, 0);
+}
+
+/*
+ * return true of the speaker may speak for the user
+ *
+ * a speaker may always speak for himself/herself
+ */
+int
+speaksfor(char *speaker, char *user)
+{
+ Ndbtuple *tp, *ntp;
+ Ndbs s;
+ int ok;
+ char notuser[Maxpath];
+
+ if(strcmp(speaker, user) == 0)
+ return 1;
+
+ if(db == 0)
+ return 0;
+
+ tp = ndbsearch(db, &s, "hostid", speaker);
+ if(tp == 0)
+ return 0;
+
+ ok = 0;
+ snprint(notuser, sizeof notuser, "!%s", user);
+ for(ntp = tp; ntp; ntp = ntp->entry)
+ if(strcmp(ntp->attr, "uid") == 0){
+ if(strcmp(ntp->val, notuser) == 0){
+ ok = 0;
+ break;
+ }
+ if(*ntp->val == '*' || strcmp(ntp->val, user) == 0)
+ ok = 1;
+ }
+ ndbfree(tp);
+ return ok;
+}
+
+/*
+ * return an error reply
+ */
+void
+replyerror(char *fmt, ...)
+{
+ char buf[AERRLEN+1];
+ va_list arg;
+
+ memset(buf, 0, sizeof(buf));
+ va_start(arg, fmt);
+ vseprint(buf + 1, buf + sizeof(buf), fmt, arg);
+ va_end(arg);
+ buf[AERRLEN] = 0;
+ buf[0] = AuthErr;
+ write(1, buf, AERRLEN+1);
+ syslog(0, AUTHLOG, buf+1);
+}
+
+void
+getraddr(char *dir)
+{
+ int n;
+ char *cp;
+ char file[Maxpath];
+
+ raddr[0] = 0;
+ snprint(file, sizeof(file), "%s/remote", dir);
+ n = readfile(file, raddr, sizeof(raddr)-1);
+ if(n < 0)
+ return;
+ raddr[n] = 0;
+
+ cp = strchr(raddr, '\n');
+ if(cp)
+ *cp = 0;
+ cp = strchr(raddr, '!');
+ if(cp)
+ *cp = 0;
+}
+
+void
+mkkey(char *k)
+{
+ randombytes((uchar*)k, DESKEYLEN);
+}
+
+void
+randombytes(uchar *buf, int len)
+{
+ int i;
+
+ if(readfile("/dev/random", (char*)buf, len) >= 0)
+ return;
+
+ for(i = 0; i < len; i++)
+ buf[i] = rand();
+}
+
+/*
+ * reply with ticket and authenticator
+ */
+int
+tickauthreply(Ticketreq *tr, char *hkey)
+{
+ Ticket t;
+ Authenticator a;
+ char buf[TICKETLEN+AUTHENTLEN+1];
+
+ memset(&t, 0, sizeof(t));
+ memmove(t.chal, tr->chal, CHALLEN);
+ safecpy(t.cuid, tr->uid, sizeof t.cuid);
+ safecpy(t.suid, tr->uid, sizeof t.suid);
+ mkkey(t.key);
+ buf[0] = AuthOK;
+ t.num = AuthTs;
+ convT2M(&t, buf+1, hkey);
+ memmove(a.chal, t.chal, CHALLEN);
+ a.num = AuthAc;
+ a.id = 0;
+ convA2M(&a, buf+TICKETLEN+1, t.key);
+ if(write(1, buf, TICKETLEN+AUTHENTLEN+1) < 0)
+ return -1;
+ return 0;
+}
+
+void
+safecpy(char *to, char *from, int len)
+{
+ strncpy(to, from, len);
+ to[len-1] = 0;
+}
diff --git a/sys/src/cmd/auth/challenge.c b/sys/src/cmd/auth/challenge.c
new file mode 100755
index 000000000..c99027689
--- /dev/null
+++ b/sys/src/cmd/auth/challenge.c
@@ -0,0 +1,59 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/challenge 'params'\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char buf[128], bufu[128];
+ int afd, n;
+ AuthInfo *ai;
+ AuthRpc *rpc;
+ Chalstate *c;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0)
+ sysfatal("open /mnt/factotum/rpc: %r");
+
+ rpc = auth_allocrpc(afd);
+ if(rpc == nil)
+ sysfatal("auth_allocrpc: %r");
+
+ if((c = auth_challenge("%s", argv[0])) == nil)
+ sysfatal("auth_challenge: %r");
+
+ print("challenge: %s\n", c->chal);
+ print("user:");
+ n = read(0, bufu, sizeof bufu);
+ if(n > 0){
+ bufu[n-1] = '\0';
+ c->user = bufu;
+ }
+
+ print("response: ");
+ n = read(0, buf, sizeof buf);
+ if(n < 0)
+ sysfatal("read: %r");
+ if(n == 0)
+ exits(nil);
+ c->nresp = n-1;
+ c->resp = buf;
+ if((ai = auth_response(c)) == nil)
+ sysfatal("auth_response: %r");
+
+ print("%s %s\n", ai->cuid, ai->suid);
+}
diff --git a/sys/src/cmd/auth/changeuser.c b/sys/src/cmd/auth/changeuser.c
new file mode 100755
index 000000000..71a59fc03
--- /dev/null
+++ b/sys/src/cmd/auth/changeuser.c
@@ -0,0 +1,144 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <ctype.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+void install(char*, char*, char*, long, int);
+int exists (char*, char*);
+
+void
+usage(void)
+{
+ fprint(2, "usage: changeuser [-pn] user\n");
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ char *u, key[DESKEYLEN], answer[32], p9pass[32];
+ int which, i, newkey, newbio, dosecret;
+ long t;
+ Acctbio a;
+ Fs *f;
+
+ srand(getpid()*time(0));
+ fmtinstall('K', keyfmt);
+
+ which = 0;
+ ARGBEGIN{
+ case 'p':
+ which |= Plan9;
+ break;
+ case 'n':
+ which |= Securenet;
+ break;
+ default:
+ usage();
+ }ARGEND
+ argv0 = "changeuser";
+
+ if(argc != 1)
+ usage();
+ u = *argv;
+ if(memchr(u, '\0', ANAMELEN) == 0)
+ error("bad user name");
+
+ if(!which)
+ which = Plan9;
+
+ newbio = 0;
+ t = 0;
+ a.user = 0;
+ if(which & Plan9){
+ f = &fs[Plan9];
+ newkey = 1;
+ if(exists(f->keys, u)){
+ readln("assign new password? [y/n]: ", answer, sizeof answer, 0);
+ if(answer[0] != 'y' && answer[0] != 'Y')
+ newkey = 0;
+ }
+ if(newkey)
+ getpass(key, p9pass, 1, 1);
+ dosecret = getsecret(newkey, p9pass);
+ t = getexpiration(f->keys, u);
+ install(f->keys, u, key, t, newkey);
+ if(dosecret && setsecret(KEYDB, u, p9pass) == 0)
+ error("error writing Inferno/pop secret");
+ newbio = querybio(f->who, u, &a);
+ if(newbio)
+ wrbio(f->who, &a);
+ print("user %s installed for Plan 9\n", u);
+ syslog(0, AUTHLOG, "user %s installed for plan 9", u);
+ }
+ if(which & Securenet){
+ f = &fs[Securenet];
+ newkey = 1;
+ if(exists(f->keys, u)){
+ readln("assign new key? [y/n]: ", answer, sizeof answer, 0);
+ if(answer[0] != 'y' && answer[0] != 'Y')
+ newkey = 0;
+ }
+ if(newkey)
+ for(i=0; i<DESKEYLEN; i++)
+ key[i] = nrand(256);
+ if(a.user == 0){
+ t = getexpiration(f->keys, u);
+ newbio = querybio(f->who, u, &a);
+ }
+ install(f->keys, u, key, t, newkey);
+ if(newbio)
+ wrbio(f->who, &a);
+ findkey(f->keys, u, key);
+ print("user %s: SecureNet key: %K\n", u, key);
+ checksum(key, answer);
+ print("verify with checksum %s\n", answer);
+ print("user %s installed for SecureNet\n", u);
+ syslog(0, AUTHLOG, "user %s installed for securenet", u);
+ }
+ exits(0);
+}
+
+void
+install(char *db, char *u, char *key, long t, int newkey)
+{
+ char buf[KEYDBBUF+ANAMELEN+20];
+ int fd;
+
+ if(!exists(db, u)){
+ sprint(buf, "%s/%s", db, u);
+ fd = create(buf, OREAD, 0777|DMDIR);
+ if(fd < 0)
+ error("can't create user %s: %r", u);
+ close(fd);
+ }
+
+ if(newkey){
+ sprint(buf, "%s/%s/key", db, u);
+ fd = open(buf, OWRITE);
+ if(fd < 0 || write(fd, key, DESKEYLEN) != DESKEYLEN)
+ error("can't set key: %r");
+ close(fd);
+ }
+
+ if(t == -1)
+ return;
+ sprint(buf, "%s/%s/expire", db, u);
+ fd = open(buf, OWRITE);
+ if(fd < 0 || fprint(fd, "%ld", t) < 0)
+ error("can't write expiration time");
+ close(fd);
+}
+
+int
+exists(char *db, char *u)
+{
+ char buf[KEYDBBUF+ANAMELEN+6];
+
+ sprint(buf, "%s/%s/expire", db, u);
+ if(access(buf, 0) < 0)
+ return 0;
+ return 1;
+}
diff --git a/sys/src/cmd/auth/convbio.c b/sys/src/cmd/auth/convbio.c
new file mode 100755
index 000000000..afca2f589
--- /dev/null
+++ b/sys/src/cmd/auth/convbio.c
@@ -0,0 +1,133 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "authcmdlib.h"
+
+void
+clrbio(Acctbio *a)
+{
+ int i;
+
+ if(a->user)
+ free(a->user);
+ if(a->name)
+ free(a->name);
+ if(a->dept)
+ free(a->dept);
+ for(i = 0; i < Nemail; i++)
+ if(a->email[i])
+ free(a->email[i]);
+ memset(a, 0, sizeof(Acctbio));
+}
+
+int
+ordbio(Biobuf *b, Acctbio *a)
+{
+ char *p, *cp, *next;
+ int ne;
+
+ clrbio(a);
+ while(p = Brdline(b, '\n')){
+ if(*p == '\n')
+ continue;
+
+ p[Blinelen(b)-1] = 0;
+
+ /* get user */
+ for(cp = p; *cp && *cp != ' ' && *cp != '\t'; cp++)
+ ;
+ a->user = malloc(cp - p + 1);
+ strncpy(a->user, p, cp - p);
+ a->user[cp - p] = 0;
+ p = cp;
+
+ /* get name */
+ while(*p == ' ' || *p == '\t')
+ p++;
+ for(cp = p; *cp; cp++){
+ if(isdigit(*cp) || *cp == '<'){
+ while(cp > p && *(cp-1) != ' ' && *(cp-1) != '\t')
+ cp--;
+ break;
+ }
+ }
+ next = cp;
+ while(cp > p && (*(cp-1) == ' ' || *(cp-1) == '\t'))
+ cp--;
+ a->name = malloc(cp - p + 1);
+ strncpy(a->name, p, cp - p);
+ a->name[cp - p] = 0;
+ p = next;
+
+ /* get dept */
+ for(cp = p; *cp; cp++){
+ if(*cp == '<')
+ break;
+ }
+ next = cp;
+ while(cp > p && (*(cp-1) == ' ' || *(cp-1) == '\t'))
+ cp--;
+ a->dept = malloc(cp - p + 1);
+ strncpy(a->dept, p, cp - p);
+ a->dept[cp - p] = 0;
+ p = next;
+
+ /* get emails */
+ ne = 0;
+ for(cp = p; *cp && ne < Nemail;){
+ if(*cp != '<'){
+ cp++;
+ continue;
+ }
+ p = ++cp;
+ while(*cp && *cp != '>')
+ cp++;
+ if(cp == p)
+ break;
+ a->email[ne] = malloc(cp - p + 1);
+ strncpy(a->email[ne], p, cp - p);
+ a->email[ne][cp-p] = 0;
+ ne++;
+ }
+ return 0;
+ }
+ return -1;
+}
+
+void
+nwrbio(Biobuf *b, Acctbio *a)
+{
+ int i;
+
+ if(a->postid == 0)
+ a->postid = "";
+ if(a->name == 0)
+ a->name = "";
+ if(a->dept == 0)
+ a->dept = "";
+ if(a->email[0] == 0)
+ a->email[0] = strdup(a->user);
+
+ Bprint(b, "%s|%s|%s|%s|%s", a->user, a->user, a->name, a->dept, a->email[0]);
+ for(i = 1; i < Nemail; i++){
+ if(a->email[i] == 0)
+ break;
+ Bprint(b, "|%s", a->email[i]);
+ }
+ Bprint(b, "\n");
+}
+
+void
+main(void)
+{
+ Biobuf in, out;
+ Acctbio a;
+
+ Binit(&in, 0, OREAD);
+ Binit(&out, 1, OWRITE);
+ while(ordbio(&in, &a) == 0)
+ nwrbio(&out, &a);
+ Bterm(&in);
+ Bterm(&out);
+}
diff --git a/sys/src/cmd/auth/convkeys.c b/sys/src/cmd/auth/convkeys.c
new file mode 100755
index 000000000..200c5ef21
--- /dev/null
+++ b/sys/src/cmd/auth/convkeys.c
@@ -0,0 +1,162 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <authsrv.h>
+#include <mp.h>
+#include <libsec.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+char authkey[DESKEYLEN];
+int verb;
+int usepass;
+
+int convert(char*, char*, int);
+int dofcrypt(int, char*, char*, int);
+void usage(void);
+
+void
+main(int argc, char *argv[])
+{
+ Dir *d;
+ char *p, *file, key[DESKEYLEN];
+ int fd, len;
+
+ ARGBEGIN{
+ case 'p':
+ usepass = 1;
+ break;
+ case 'v':
+ verb = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+ file = argv[0];
+
+ /* get original key */
+ if(usepass){
+ print("enter password file is encoded with\n");
+ getpass(authkey, nil, 0, 1);
+ } else
+ getauthkey(authkey);
+ if(!verb){
+ print("enter password to reencode with\n");
+ getpass(key, nil, 0, 1);
+ }
+
+ fd = open(file, ORDWR);
+ if(fd < 0)
+ error("can't open %s: %r\n", file);
+ d = dirfstat(fd);
+ if(d == nil)
+ error("can't stat %s: %r\n", file);
+ len = d->length;
+ p = malloc(len);
+ if(!p)
+ error("out of memory");
+ if(read(fd, p, len) != len)
+ error("can't read key file: %r\n");
+ len = convert(p, key, len);
+ if(verb)
+ exits(0);
+ if(pwrite(fd, p, len, 0) != len)
+ error("can't write key file: %r\n");
+ close(fd);
+ exits(0);
+}
+
+void
+randombytes(uchar *p, int len)
+{
+ int i, fd;
+
+ fd = open("/dev/random", OREAD);
+ if(fd < 0){
+ fprint(2, "convkeys: can't open /dev/random, using rand()\n");
+ srand(time(0));
+ for(i = 0; i < len; i++)
+ p[i] = rand();
+ return;
+ }
+ read(fd, p, len);
+ close(fd);
+}
+
+void
+oldCBCencrypt(char *key7, char *p, int len)
+{
+ uchar ivec[8];
+ uchar key[8];
+ DESstate s;
+
+ memset(ivec, 0, 8);
+ des56to64((uchar*)key7, key);
+ setupDESstate(&s, key, ivec);
+ desCBCencrypt((uchar*)p, len, &s);
+}
+
+void
+oldCBCdecrypt(char *key7, char *p, int len)
+{
+ uchar ivec[8];
+ uchar key[8];
+ DESstate s;
+
+ memset(ivec, 0, 8);
+ des56to64((uchar*)key7, key);
+ setupDESstate(&s, key, ivec);
+ desCBCdecrypt((uchar*)p, len, &s);
+
+}
+
+static int
+badname(char *s)
+{
+ int n;
+ Rune r;
+
+ for (; *s != '\0'; s += n) {
+ n = chartorune(&r, s);
+ if (n == 1 && r == Runeerror)
+ return 1;
+ }
+ return 0;
+}
+
+int
+convert(char *p, char *key, int len)
+{
+ int i;
+
+ len -= KEYDBOFF;
+ if(len % KEYDBLEN){
+ fprint(2, "convkeys: file odd length; not converting %d bytes\n",
+ len % KEYDBLEN);
+ len -= len % KEYDBLEN;
+ }
+ len += KEYDBOFF;
+ oldCBCdecrypt(authkey, p, len);
+ for(i = KEYDBOFF; i < len; i += KEYDBLEN)
+ if (badname(&p[i])) {
+ print("bad name %.30s... - aborting\n", &p[i]);
+ return 0;
+ }
+ if(verb)
+ for(i = KEYDBOFF; i < len; i += KEYDBLEN)
+ print("%s\n", &p[i]);
+
+ randombytes((uchar*)p, 8);
+ oldCBCencrypt(key, p, len);
+ return len;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: convkeys keyfile\n");
+ exits("usage");
+}
diff --git a/sys/src/cmd/auth/convkeys2.c b/sys/src/cmd/auth/convkeys2.c
new file mode 100755
index 000000000..f8e6feffe
--- /dev/null
+++ b/sys/src/cmd/auth/convkeys2.c
@@ -0,0 +1,132 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+char authkey[DESKEYLEN];
+int verb;
+int usepass;
+
+int convert(char*, char*, char*, int);
+int dofcrypt(int, char*, char*, int);
+void usage(void);
+void randombytes(uchar*, int);
+
+void
+main(int argc, char *argv[])
+{
+ Dir *d;
+ char *p, *np, *file, key[DESKEYLEN];
+ int fd, len;
+
+ ARGBEGIN{
+ case 'v':
+ verb = 1;
+ break;
+ case 'p':
+ usepass = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+ file = argv[0];
+
+ /* get original key */
+ if(usepass){
+ print("enter password file is encoded with\n");
+ getpass(authkey, nil, 0, 1);
+ } else
+ getauthkey(authkey);
+ print("enter password to reencode with\n");
+ getpass(key, nil, 0, 1);
+
+ fd = open(file, ORDWR);
+ if(fd < 0)
+ error("can't open %s: %r\n", file);
+ d = dirfstat(fd);
+ if(d == nil)
+ error("can't stat %s: %r\n", file);
+ len = d->length;
+ p = malloc(len);
+ if(!p)
+ error("out of memory");
+ np = malloc((len/OKEYDBLEN)*KEYDBLEN + KEYDBOFF);
+ if(!np)
+ error("out of memory");
+ if(read(fd, p, len) != len)
+ error("can't read key file: %r\n");
+ len = convert(p, np, key, len);
+ if(verb)
+ exits(0);
+ if(pwrite(fd, np, len, 0) != len)
+ error("can't write key file: %r\n");
+ close(fd);
+ exits(0);
+}
+
+void
+oldCBCencrypt(char *key7, char *p, int len)
+{
+ uchar ivec[8];
+ uchar key[8];
+ DESstate s;
+
+ memset(ivec, 0, 8);
+ des56to64((uchar*)key7, key);
+ setupDESstate(&s, key, ivec);
+ desCBCencrypt((uchar*)p, len, &s);
+}
+
+int
+convert(char *p, char *np, char *key, int len)
+{
+ int i, off, noff;
+
+ if(len % OKEYDBLEN)
+ fprint(2, "convkeys2: file odd length; not converting %d bytes\n",
+ len % KEYDBLEN);
+ len /= OKEYDBLEN;
+ for(i = 0; i < len; i ++){
+ off = i*OKEYDBLEN;
+ noff = KEYDBOFF+i*(KEYDBLEN);
+ decrypt(authkey, &p[off], OKEYDBLEN);
+ memmove(&np[noff], &p[off], OKEYDBLEN);
+ memset(&np[noff-SECRETLEN], 0, SECRETLEN);
+ if(verb)
+ print("%s\n", &p[off]);
+ }
+ randombytes((uchar*)np, KEYDBOFF);
+ len = (len*KEYDBLEN) + KEYDBOFF;
+ oldCBCencrypt(key, np, len);
+ return len;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: convkeys2 keyfile\n");
+ exits("usage");
+}
+
+void
+randombytes(uchar *p, int len)
+{
+ int i, fd;
+
+ fd = open("/dev/random", OREAD);
+ if(fd < 0){
+ fprint(2, "convkeys2: can't open /dev/random, using rand()\n");
+ srand(time(0));
+ for(i = 0; i < len; i++)
+ p[i] = rand();
+ return;
+ }
+ read(fd, p, len);
+ close(fd);
+}
diff --git a/sys/src/cmd/auth/cron.c b/sys/src/cmd/auth/cron.c
new file mode 100755
index 000000000..da62cdabc
--- /dev/null
+++ b/sys/src/cmd/auth/cron.c
@@ -0,0 +1,744 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <auth.h>
+#include "authcmdlib.h"
+
+char CRONLOG[] = "cron";
+
+enum {
+ Minute = 60,
+ Hour = 60 * Minute,
+ Day = 24 * Hour,
+};
+
+typedef struct Job Job;
+typedef struct Time Time;
+typedef struct User User;
+
+struct Time{ /* bit masks for each valid time */
+ uvlong min;
+ ulong hour;
+ ulong mday;
+ ulong wday;
+ ulong mon;
+};
+
+struct Job{
+ char *host; /* where ... */
+ Time time; /* when ... */
+ char *cmd; /* and what to execute */
+ Job *next;
+};
+
+struct User{
+ Qid lastqid; /* of last read /cron/user/cron */
+ char *name; /* who ... */
+ Job *jobs; /* wants to execute these jobs */
+};
+
+User *users;
+int nuser;
+int maxuser;
+char *savec;
+char *savetok;
+int tok;
+int debug;
+ulong lexval;
+
+void rexec(User*, Job*);
+void readalljobs(void);
+Job *readjobs(char*, User*);
+int getname(char**);
+uvlong gettime(int, int);
+int gettok(int, int);
+void initcap(void);
+void pushtok(void);
+void usage(void);
+void freejobs(Job*);
+User *newuser(char*);
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+int myauth(int, char*);
+void createuser(void);
+int mkcmd(char*, char*, int);
+void printjobs(void);
+int qidcmp(Qid, Qid);
+int becomeuser(char*);
+
+ulong
+minute(ulong tm)
+{
+ return tm - tm%Minute; /* round down to the minute */
+}
+
+int
+sleepuntil(ulong tm)
+{
+ ulong now = time(0);
+
+ if (now < tm)
+ return sleep((tm - now)*1000);
+ else
+ return 0;
+}
+
+#pragma varargck argpos clog 1
+#pragma varargck argpos fatal 1
+
+static void
+clog(char *fmt, ...)
+{
+ char msg[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(msg, msg + sizeof msg, fmt, arg);
+ va_end(arg);
+ syslog(0, CRONLOG, msg);
+}
+
+static void
+fatal(char *fmt, ...)
+{
+ char msg[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(msg, msg + sizeof msg, fmt, arg);
+ va_end(arg);
+ clog("%s", msg);
+ error("%s", msg);
+}
+
+static int
+openlock(char *file)
+{
+ return create(file, ORDWR, 0600);
+}
+
+static int
+mklock(char *file)
+{
+ int fd, try;
+ Dir *dir;
+
+ fd = openlock(file);
+ if (fd >= 0) {
+ /* make it a lock file if it wasn't */
+ dir = dirfstat(fd);
+ if (dir == nil)
+ error("%s vanished: %r", file);
+ dir->mode |= DMEXCL;
+ dir->qid.type |= QTEXCL;
+ dirfwstat(fd, dir);
+ free(dir);
+
+ /* reopen in case it wasn't a lock file at last open */
+ close(fd);
+ }
+ for (try = 0; try < 65 && (fd = openlock(file)) < 0; try++)
+ sleep(10*1000);
+ return fd;
+}
+
+void
+main(int argc, char *argv[])
+{
+ Job *j;
+ Tm tm;
+ Time t;
+ ulong now, last; /* in seconds */
+ int i, lock;
+
+ debug = 0;
+ ARGBEGIN{
+ case 'c':
+ createuser();
+ exits(0);
+ case 'd':
+ debug = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(debug){
+ readalljobs();
+ printjobs();
+ exits(0);
+ }
+
+ initcap(); /* do this early, before cpurc removes it */
+
+ switch(fork()){
+ case -1:
+ fatal("can't fork");
+ case 0:
+ break;
+ default:
+ exits(0);
+ }
+
+ /*
+ * it can take a few minutes before the file server notices that
+ * we've rebooted and gives up the lock.
+ */
+ lock = mklock("/cron/lock");
+ if (lock < 0)
+ fatal("cron already running: %r");
+
+ argv0 = "cron";
+ srand(getpid()*time(0));
+ last = time(0);
+ for(;;){
+ readalljobs();
+ /*
+ * the system's notion of time may have jumped forward or
+ * backward an arbitrary amount since the last call to time().
+ */
+ now = time(0);
+ /*
+ * if time has jumped backward, just note it and adapt.
+ * if time has jumped forward more than a day,
+ * just execute one day's jobs.
+ */
+ if (now < last) {
+ clog("time went backward");
+ last = now;
+ } else if (now - last > Day) {
+ clog("time advanced more than a day");
+ last = now - Day;
+ }
+ now = minute(now);
+ for(last = minute(last); last <= now; last += Minute){
+ tm = *localtime(last);
+ t.min = 1ULL << tm.min;
+ t.hour = 1 << tm.hour;
+ t.wday = 1 << tm.wday;
+ t.mday = 1 << tm.mday;
+ t.mon = 1 << (tm.mon + 1);
+ for(i = 0; i < nuser; i++)
+ for(j = users[i].jobs; j; j = j->next)
+ if(j->time.min & t.min
+ && j->time.hour & t.hour
+ && j->time.wday & t.wday
+ && j->time.mday & t.mday
+ && j->time.mon & t.mon)
+ rexec(&users[i], j);
+ }
+ seek(lock, 0, 0);
+ write(lock, "x", 1); /* keep the lock alive */
+ /*
+ * if we're not at next minute yet, sleep until a second past
+ * (to allow for sleep intervals being approximate),
+ * which synchronises with minute roll-over as a side-effect.
+ */
+ sleepuntil(now + Minute + 1);
+ }
+ /* not reached */
+}
+
+void
+createuser(void)
+{
+ Dir d;
+ char file[128], *user;
+ int fd;
+
+ user = getuser();
+ sprint(file, "/cron/%s", user);
+ fd = create(file, OREAD, 0755|DMDIR);
+ if(fd < 0)
+ sysfatal("couldn't create %s: %r", file);
+ nulldir(&d);
+ d.gid = user;
+ dirfwstat(fd, &d);
+ close(fd);
+ sprint(file, "/cron/%s/cron", user);
+ fd = create(file, OREAD, 0644);
+ if(fd < 0)
+ sysfatal("couldn't create %s: %r", file);
+ nulldir(&d);
+ d.gid = user;
+ dirfwstat(fd, &d);
+ close(fd);
+}
+
+void
+readalljobs(void)
+{
+ User *u;
+ Dir *d, *du;
+ char file[128];
+ int i, n, fd;
+
+ fd = open("/cron", OREAD);
+ if(fd < 0)
+ fatal("can't open /cron\n");
+ while((n = dirread(fd, &d)) > 0){
+ for(i = 0; i < n; i++){
+ if(strcmp(d[i].name, "log") == 0 ||
+ !(d[i].qid.type & QTDIR))
+ continue;
+ if(strcmp(d[i].name, d[i].uid) != 0){
+ syslog(1, CRONLOG, "cron for %s owned by %s",
+ d[i].name, d[i].uid);
+ continue;
+ }
+ u = newuser(d[i].name);
+ sprint(file, "/cron/%s/cron", d[i].name);
+ du = dirstat(file);
+ if(du == nil || qidcmp(u->lastqid, du->qid) != 0){
+ freejobs(u->jobs);
+ u->jobs = readjobs(file, u);
+ }
+ free(du);
+ }
+ free(d);
+ }
+ close(fd);
+}
+
+/*
+ * parse user's cron file
+ * other lines: minute hour monthday month weekday host command
+ */
+Job *
+readjobs(char *file, User *user)
+{
+ Biobuf *b;
+ Job *j, *jobs;
+ Dir *d;
+ int line;
+
+ d = dirstat(file);
+ if(!d)
+ return nil;
+ b = Bopen(file, OREAD);
+ if(!b){
+ free(d);
+ return nil;
+ }
+ jobs = nil;
+ user->lastqid = d->qid;
+ free(d);
+ for(line = 1; savec = Brdline(b, '\n'); line++){
+ savec[Blinelen(b) - 1] = '\0';
+ while(*savec == ' ' || *savec == '\t')
+ savec++;
+ if(*savec == '#' || *savec == '\0')
+ continue;
+ if(strlen(savec) > 1024){
+ clog("%s: line %d: line too long", user->name, line);
+ continue;
+ }
+ j = emalloc(sizeof *j);
+ j->time.min = gettime(0, 59);
+ if(j->time.min && (j->time.hour = gettime(0, 23))
+ && (j->time.mday = gettime(1, 31))
+ && (j->time.mon = gettime(1, 12))
+ && (j->time.wday = gettime(0, 6))
+ && getname(&j->host)){
+ j->cmd = emalloc(strlen(savec) + 1);
+ strcpy(j->cmd, savec);
+ j->next = jobs;
+ jobs = j;
+ }else{
+ clog("%s: line %d: syntax error", user->name, line);
+ free(j);
+ }
+ }
+ Bterm(b);
+ return jobs;
+}
+
+void
+printjobs(void)
+{
+ char buf[8*1024];
+ Job *j;
+ int i;
+
+ for(i = 0; i < nuser; i++){
+ print("user %s\n", users[i].name);
+ for(j = users[i].jobs; j; j = j->next)
+ if(!mkcmd(j->cmd, buf, sizeof buf))
+ print("\tbad job %s on host %s\n",
+ j->cmd, j->host);
+ else
+ print("\tjob %s on host %s\n", buf, j->host);
+ }
+}
+
+User *
+newuser(char *name)
+{
+ int i;
+
+ for(i = 0; i < nuser; i++)
+ if(strcmp(users[i].name, name) == 0)
+ return &users[i];
+ if(nuser == maxuser){
+ maxuser += 32;
+ users = erealloc(users, maxuser * sizeof *users);
+ }
+ memset(&users[nuser], 0, sizeof(users[nuser]));
+ users[nuser].name = strdup(name);
+ users[nuser].jobs = 0;
+ users[nuser].lastqid.type = QTFILE;
+ users[nuser].lastqid.path = ~0LL;
+ users[nuser].lastqid.vers = ~0L;
+ return &users[nuser++];
+}
+
+void
+freejobs(Job *j)
+{
+ Job *next;
+
+ for(; j; j = next){
+ next = j->next;
+ free(j->cmd);
+ free(j->host);
+ free(j);
+ }
+}
+
+int
+getname(char **namep)
+{
+ int c;
+ char buf[64], *p;
+
+ if(!savec)
+ return 0;
+ while(*savec == ' ' || *savec == '\t')
+ savec++;
+ for(p = buf; (c = *savec) && c != ' ' && c != '\t'; p++){
+ if(p >= buf+sizeof buf -1)
+ return 0;
+ *p = *savec++;
+ }
+ *p = '\0';
+ *namep = strdup(buf);
+ if(*namep == 0){
+ clog("internal error: strdup failure");
+ _exits(0);
+ }
+ while(*savec == ' ' || *savec == '\t')
+ savec++;
+ return p > buf;
+}
+
+/*
+ * return the next time range (as a bit vector) in the file:
+ * times: '*'
+ * | range
+ * range: number
+ * | number '-' number
+ * | range ',' range
+ * a return of zero means a syntax error was discovered
+ */
+uvlong
+gettime(int min, int max)
+{
+ uvlong n, m, e;
+
+ if(gettok(min, max) == '*')
+ return ~0ULL;
+ n = 0;
+ while(tok == '1'){
+ m = 1ULL << lexval;
+ n |= m;
+ if(gettok(0, 0) == '-'){
+ if(gettok(lexval, max) != '1')
+ return 0;
+ e = 1ULL << lexval;
+ for( ; m <= e; m <<= 1)
+ n |= m;
+ gettok(min, max);
+ }
+ if(tok != ',')
+ break;
+ if(gettok(min, max) != '1')
+ return 0;
+ }
+ pushtok();
+ return n;
+}
+
+void
+pushtok(void)
+{
+ savec = savetok;
+}
+
+int
+gettok(int min, int max)
+{
+ char c;
+
+ savetok = savec;
+ if(!savec)
+ return tok = 0;
+ while((c = *savec) == ' ' || c == '\t')
+ savec++;
+ switch(c){
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ lexval = strtoul(savec, &savec, 10);
+ if(lexval < min || lexval > max)
+ return tok = 0;
+ return tok = '1';
+ case '*': case '-': case ',':
+ savec++;
+ return tok = c;
+ default:
+ return tok = 0;
+ }
+}
+
+int
+call(char *host)
+{
+ char *na, *p;
+
+ na = netmkaddr(host, 0, "rexexec");
+ p = utfrune(na, L'!');
+ if(!p)
+ return -1;
+ p = utfrune(p+1, L'!');
+ if(!p)
+ return -1;
+ if(strcmp(p, "!rexexec") != 0)
+ return -2;
+ return dial(na, 0, 0, 0);
+}
+
+/*
+ * convert command to run properly on the remote machine
+ * need to escape the quotes so they don't get stripped
+ */
+int
+mkcmd(char *cmd, char *buf, int len)
+{
+ char *p;
+ int n, m;
+
+ n = sizeof "exec rc -c '" -1;
+ if(n >= len)
+ return 0;
+ strcpy(buf, "exec rc -c '");
+ while(p = utfrune(cmd, L'\'')){
+ p++;
+ m = p - cmd;
+ if(n + m + 1 >= len)
+ return 0;
+ strncpy(&buf[n], cmd, m);
+ n += m;
+ buf[n++] = '\'';
+ cmd = p;
+ }
+ m = strlen(cmd);
+ if(n + m + sizeof "'</dev/null>/dev/null>[2=1]" >= len)
+ return 0;
+ strcpy(&buf[n], cmd);
+ strcpy(&buf[n+m], "'</dev/null>/dev/null>[2=1]");
+ return 1;
+}
+
+void
+rexec(User *user, Job *j)
+{
+ char buf[8*1024];
+ int n, fd;
+ AuthInfo *ai;
+
+ switch(rfork(RFPROC|RFNOWAIT|RFNAMEG|RFENVG|RFFDG)){
+ case 0:
+ break;
+ case -1:
+ clog("can't fork a job for %s: %r\n", user->name);
+ default:
+ return;
+ }
+
+ if(!mkcmd(j->cmd, buf, sizeof buf)){
+ clog("internal error: cmd buffer overflow");
+ _exits(0);
+ }
+
+ /*
+ * local call, auth, cmd with no i/o
+ */
+ if(strcmp(j->host, "local") == 0){
+ if(becomeuser(user->name) < 0){
+ clog("%s: can't change uid for %s on %s: %r",
+ user->name, j->cmd, j->host);
+ _exits(0);
+ }
+ putenv("service", "rx");
+ clog("%s: ran '%s' on %s", user->name, j->cmd, j->host);
+ execl("/bin/rc", "rc", "-lc", buf, nil);
+ clog("%s: exec failed for %s on %s: %r",
+ user->name, j->cmd, j->host);
+ _exits(0);
+ }
+
+ /*
+ * remote call, auth, cmd with no i/o
+ * give it 2 min to complete
+ */
+ alarm(2*Minute*1000);
+ fd = call(j->host);
+ if(fd < 0){
+ if(fd == -2)
+ clog("%s: dangerous host %s", user->name, j->host);
+ clog("%s: can't call %s: %r", user->name, j->host);
+ _exits(0);
+ }
+ clog("%s: called %s on %s", user->name, j->cmd, j->host);
+ if(becomeuser(user->name) < 0){
+ clog("%s: can't change uid for %s on %s: %r",
+ user->name, j->cmd, j->host);
+ _exits(0);
+ }
+ ai = auth_proxy(fd, nil, "proto=p9any role=client");
+ if(ai == nil){
+ clog("%s: can't authenticate for %s on %s: %r",
+ user->name, j->cmd, j->host);
+ _exits(0);
+ }
+ clog("%s: authenticated %s on %s", user->name, j->cmd, j->host);
+ write(fd, buf, strlen(buf)+1);
+ write(fd, buf, 0);
+ while((n = read(fd, buf, sizeof(buf)-1)) > 0){
+ buf[n] = 0;
+ clog("%s: %s\n", j->cmd, buf);
+ }
+ _exits(0);
+}
+
+void *
+emalloc(ulong n)
+{
+ void *p;
+
+ if(p = mallocz(n, 1))
+ return p;
+ fatal("out of memory");
+ return 0;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+ if(p = realloc(p, n))
+ return p;
+ fatal("out of memory");
+ return 0;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: cron [-c]\n");
+ exits("usage");
+}
+
+int
+qidcmp(Qid a, Qid b)
+{
+ /* might be useful to know if a > b, but not for cron */
+ return(a.path != b.path || a.vers != b.vers);
+}
+
+void
+memrandom(void *p, int n)
+{
+ uchar *cp;
+
+ for(cp = (uchar*)p; n > 0; n--)
+ *cp++ = fastrand();
+}
+
+/*
+ * keep caphash fd open since opens of it could be disabled
+ */
+static int caphashfd;
+
+void
+initcap(void)
+{
+ caphashfd = open("#¤/caphash", OCEXEC|OWRITE);
+ if(caphashfd < 0)
+ fprint(2, "%s: opening #¤/caphash: %r\n", argv0);
+}
+
+/*
+ * create a change uid capability
+ */
+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;
+}
+
+int
+usecap(char *cap)
+{
+ int fd, rv;
+
+ fd = open("#¤/capuse", OWRITE);
+ if(fd < 0)
+ return -1;
+ rv = write(fd, cap, strlen(cap));
+ close(fd);
+ return rv;
+}
+
+int
+becomeuser(char *new)
+{
+ char *cap;
+ int rv;
+
+ cap = mkcap(getuser(), new);
+ if(cap == nil)
+ return -1;
+ rv = usecap(cap);
+ free(cap);
+
+ newns(new, nil);
+ return rv;
+}
diff --git a/sys/src/cmd/auth/debug.c b/sys/src/cmd/auth/debug.c
new file mode 100755
index 000000000..56b5202bf
--- /dev/null
+++ b/sys/src/cmd/auth/debug.c
@@ -0,0 +1,344 @@
+/*
+ * Test various aspects of the authentication setup.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <auth.h>
+#include <authsrv.h>
+
+/* private copy with added debugging */
+int
+authdial(char *netroot, char *dom)
+{
+ char *p;
+ int rv;
+
+ if(dom != nil){
+ /* look up an auth server in an authentication domain */
+ p = csgetvalue(netroot, "authdom", dom, "auth", nil);
+
+ /* if that didn't work, just try the IP domain */
+ if(p == nil)
+ p = csgetvalue(netroot, "dom", dom, "auth", nil);
+ if(p == nil){
+ werrstr("no auth server found for %s", dom);
+ return -1;
+ }
+ print("\tdialing auth server %s\n",
+ netmkaddr(p, netroot, "ticket"));
+ rv = dial(netmkaddr(p, netroot, "ticket"), 0, 0, 0);
+ free(p);
+ return rv;
+ } else
+ /* look for one relative to my machine */
+ return dial(netmkaddr("$auth", netroot, "ticket"), 0, 0, 0);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/debug\n");
+ exits("usage");
+}
+
+static char*
+readcons(char *prompt, char *def, int raw, char *buf, int nbuf)
+{
+ int fdin, fdout, ctl, n, m;
+ char line[10];
+
+ 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;
+
+ m = 0;
+ for(;;){
+ n = read(fdin, line, 1);
+ if(n == 0){
+ close(ctl);
+ werrstr("readcons: EOF");
+ return nil;
+ }
+ if(n < 0){
+ close(ctl);
+ werrstr("can't read cons");
+ return nil;
+ }
+ if(line[0] == 0x7f)
+ exits(0);
+ if(n == 0 || line[0] == '\n' || line[0] == '\r'){
+ if(raw){
+ write(ctl, "rawoff", 6);
+ write(fdout, "\n", 1);
+ close(ctl);
+ }
+ buf[m] = '\0';
+ if(buf[0]=='\0' && def)
+ strcpy(buf, def);
+ return buf;
+ }
+ if(line[0] == '\b'){
+ if(m > 0)
+ m--;
+ }else if(line[0] == 0x15){ /* ^U: line kill */
+ m = 0;
+ if(def != nil)
+ fprint(fdout, "%s[%s]: ", prompt, def);
+ else
+ fprint(fdout, "%s: ", prompt);
+ }else{
+ if(m >= nbuf-1){
+ fprint(fdout, "line too long\n");
+ m = 0;
+ if(def != nil)
+ fprint(fdout, "%s[%s]: ", prompt, def);
+ else
+ fprint(fdout, "%s: ", prompt);
+ }else
+ buf[m++] = line[0];
+ }
+ }
+}
+
+void authdialfutz(char*, char*);
+void authfutz(char*, char*);
+
+/* scan factotum for p9sk1 keys; check them */
+void
+debugfactotumkeys(void)
+{
+ char *s, *dom, *proto, *user;
+ int found;
+ Attr *a;
+ Biobuf *b;
+
+ b = Bopen("/mnt/factotum/ctl", OREAD);
+ if(b == nil){
+ fprint(2, "debug: cannot open /mnt/factotum/ctl\n");
+ return;
+ }
+ found = 0;
+ while((s = Brdstr(b, '\n', 1)) != nil){
+ if(strncmp(s, "key ", 4) != 0){
+ print("malformed ctl line: %s\n", s);
+ free(s);
+ continue;
+ }
+ a = _parseattr(s+4);
+ free(s);
+ proto = _strfindattr(a, "proto");
+ if(proto==nil || strcmp(proto, "p9sk1")!=0)
+ continue;
+ dom = _strfindattr(a, "dom");
+ if(dom == nil){
+ print("p9sk1 key with no dom: %A\n", a);
+ _freeattr(a);
+ continue;
+ }
+ user = _strfindattr(a, "user");
+ if(user == nil){
+ print("p9sk1 key with no user: %A\n", a);
+ _freeattr(a);
+ continue;
+ }
+ print("p9sk1 key: %A\n", a);
+ found = 1;
+ authdialfutz(dom, user);
+ _freeattr(a);
+ }
+ if(!found)
+ print("no p9sk1 keys found in factotum\n");
+}
+
+void
+authdialfutz(char *dom, char *user)
+{
+ int fd;
+ char *server;
+ char *addr;
+
+ fd = authdial(nil, dom);
+ if(fd >= 0){
+ print("\tsuccessfully dialed auth server\n");
+ close(fd);
+ authfutz(dom, user);
+ return;
+ }
+ print("\tcannot dial auth server: %r\n");
+ server = csgetvalue(nil, "authdom", dom, "auth", nil);
+ if(server){
+ print("\tcsquery authdom=%q auth=%s\n", dom, server);
+ free(server);
+ return;
+ }
+ print("\tcsquery authdom=%q auth=* failed\n", dom);
+ server = csgetvalue(nil, "dom", dom, "auth", nil);
+ if(server){
+ print("\tcsquery dom=%q auth=%q\n", dom, server);
+ free(server);
+ return;
+ }
+ print("\tcsquery dom=%q auth=*\n", dom);
+
+ fd = dial(addr=netmkaddr("$auth", nil, "ticket"), 0, 0, 0);
+ if(fd >= 0){
+ print("\tdial %s succeeded\n", addr);
+ close(fd);
+ return;
+ }
+ print("\tdial %s failed: %r\n", addr);
+}
+
+void
+authfutz(char *dom, char *user)
+{
+ int fd, nobootes;
+ char pw[128], prompt[128], key[DESKEYLEN], booteskey[DESKEYLEN], tbuf[2*TICKETLEN],
+ trbuf[TICKREQLEN];
+ Ticket t;
+ Ticketreq tr;
+
+ snprint(prompt, sizeof prompt, "\tpassword for %s@%s [hit enter to skip test]", user, dom);
+ readcons(prompt, nil, 1, pw, sizeof pw);
+ if(pw[0] == '\0')
+ return;
+ passtokey(key, pw);
+
+ fd = authdial(nil, dom);
+ if(fd < 0){
+ print("\tauthdial failed(!): %r\n");
+ return;
+ }
+
+ /* try ticket request using just user key */
+ tr.type = AuthTreq;
+ strecpy(tr.authid, tr.authid+sizeof tr.authid, user);
+ strecpy(tr.authdom, tr.authdom+sizeof tr.authdom, dom);
+ strecpy(tr.hostid, tr.hostid+sizeof tr.hostid, user);
+ strecpy(tr.uid, tr.uid+sizeof tr.uid, user);
+ memset(tr.chal, 0xAA, sizeof tr.chal);
+ convTR2M(&tr, trbuf);
+ if(_asgetticket(fd, trbuf, tbuf) < 0){
+ close(fd);
+ print("\t_asgetticket failed: %r\n");
+ return;
+ }
+ convM2T(tbuf, &t, key);
+ if(t.num != AuthTc){
+ print("\tcannot decrypt ticket1 from auth server (bad t.num=0x%.2ux)\n", t.num);
+ print("\tauth server and you do not agree on key for %s@%s\n", user, dom);
+ return;
+ }
+ if(memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
+ print("\tbad challenge1 from auth server got %.*H wanted %.*H\n",
+ sizeof t.chal, t.chal, sizeof tr.chal, tr.chal);
+ print("\tauth server is rogue\n");
+ return;
+ }
+
+ convM2T(tbuf+TICKETLEN, &t, key);
+ if(t.num != AuthTs){
+ print("\tcannot decrypt ticket2 from auth server (bad t.num=0x%.2ux)\n", t.num);
+ print("\tauth server and you do not agree on key for %s@%s\n", user, dom);
+ return;
+ }
+ if(memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
+ print("\tbad challenge2 from auth server got %.*H wanted %.*H\n",
+ sizeof t.chal, t.chal, sizeof tr.chal, tr.chal);
+ print("\tauth server is rogue\n");
+ return;
+ }
+ print("\tticket request using %s@%s key succeeded\n", user, dom);
+
+ /* try ticket request using bootes key */
+ snprint(prompt, sizeof prompt, "\tcpu server owner for domain %s ", dom);
+ readcons(prompt, "bootes", 0, tr.authid, sizeof tr.authid);
+ convTR2M(&tr, trbuf);
+ if(_asgetticket(fd, trbuf, tbuf) < 0){
+ close(fd);
+ print("\t_asgetticket failed: %r\n");
+ return;
+ }
+ convM2T(tbuf, &t, key);
+ if(t.num != AuthTc){
+ print("\tcannot decrypt ticket1 from auth server (bad t.num=0x%.2ux)\n", t.num);
+ print("\tauth server and you do not agree on key for %s@%s\n", user, dom);
+ return;
+ }
+ if(memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
+ print("\tbad challenge1 from auth server got %.*H wanted %.*H\n",
+ sizeof t.chal, t.chal, sizeof tr.chal, tr.chal);
+ print("\tauth server is rogue\n");
+ return;
+ }
+
+ snprint(prompt, sizeof prompt, "\tpassword for %s@%s [hit enter to skip test]", tr.authid, dom);
+ readcons(prompt, nil, 1, pw, sizeof pw);
+ if(pw[0] == '\0'){
+ nobootes=1;
+ goto Nobootes;
+ }
+ nobootes = 0;
+ passtokey(booteskey, pw);
+
+ convM2T(tbuf+TICKETLEN, &t, booteskey);
+ if(t.num != AuthTs){
+ print("\tcannot decrypt ticket2 from auth server (bad t.num=0x%.2ux)\n", t.num);
+ print("\tauth server and you do not agree on key for %s@%s\n", tr.authid, dom);
+ return;
+ }
+ if(memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
+ print("\tbad challenge2 from auth server got %.*H wanted %.*H\n",
+ sizeof t.chal, t.chal, sizeof tr.chal, tr.chal);
+ print("\tauth server is rogue\n");
+ return;
+ }
+ print("\tticket request using %s@%s key succeeded\n", tr.authid, dom);
+
+Nobootes:;
+ USED(nobootes);
+
+ /* try p9sk1 exchange with local factotum to test that key is right */
+
+
+ /*
+ * try p9sk1 exchange with factotum on
+ * auth server (assumes running cpu service)
+ * to test that bootes key is right over there
+ */
+
+}
+
+void
+main(int argc, char **argv)
+{
+ quotefmtinstall();
+ fmtinstall('A', _attrfmt);
+ fmtinstall('H', encodefmt);
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0)
+ usage();
+
+ debugfactotumkeys();
+}
diff --git a/sys/src/cmd/auth/disable b/sys/src/cmd/auth/disable
new file mode 100755
index 000000000..7ef714923
--- /dev/null
+++ b/sys/src/cmd/auth/disable
@@ -0,0 +1,5 @@
+#!/bin/rc
+if(test -e /mnt/keys/$1)
+ echo -n disabled > /mnt/keys/$1/status
+if(test -e /mnt/netkeys/$1)
+ echo -n disabled > /mnt/netkeys/$1/status
diff --git a/sys/src/cmd/auth/dsa2pub.c b/sys/src/cmd/auth/dsa2pub.c
new file mode 100755
index 000000000..7c516c61b
--- /dev/null
+++ b/sys/src/cmd/auth/dsa2pub.c
@@ -0,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "rsa2any.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/dsa2pub [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ DSApriv *key;
+ Attr *a;
+ char *s;
+
+ fmtinstall('A', _attrfmt);
+ fmtinstall('B', mpfmt);
+ quotefmtinstall();
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+
+ if((key = getdsakey(argc, argv, 0, &a)) == nil)
+ sysfatal("%r");
+
+ s = smprint("key %A p=%B q=%B alpha=%B key=%B\n",
+ a,
+ key->pub.p, key->pub.q, key->pub.alpha, key->pub.key);
+ if(s == nil)
+ sysfatal("smprint: %r");
+ write(1, s, strlen(s));
+ exits(nil);
+}
diff --git a/sys/src/cmd/auth/dsa2ssh.c b/sys/src/cmd/auth/dsa2ssh.c
new file mode 100755
index 000000000..70983500a
--- /dev/null
+++ b/sys/src/cmd/auth/dsa2ssh.c
@@ -0,0 +1,48 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "rsa2any.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/dsa2ssh [-c comment] [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ DSApriv *k;
+ char *comment;
+ uchar buf[8192], *p;
+
+ fmtinstall('B', mpfmt);
+ fmtinstall('[', encodefmt);
+ comment = "";
+ ARGBEGIN{
+ case 'c':
+ comment = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+
+ if((k = getdsakey(argc, argv, 0, nil)) == nil)
+ sysfatal("%r");
+
+ p = buf;
+ p = put4(p, 7);
+ p = putn(p, "ssh-dss", 7);
+ p = putmp2(p, k->pub.p);
+ p = putmp2(p, k->pub.q);
+ p = putmp2(p, k->pub.alpha);
+ p = putmp2(p, k->pub.key);
+ print("ssh-dss %.*[ %s\n", (int)(p - buf), buf, comment);
+ exits(nil);
+}
diff --git a/sys/src/cmd/auth/dsagen.c b/sys/src/cmd/auth/dsagen.c
new file mode 100755
index 000000000..d7ffa3396
--- /dev/null
+++ b/sys/src/cmd/auth/dsagen.c
@@ -0,0 +1,46 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/dsagen [-t 'attr=value attr=value ...']\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *s, *tag;
+ DSApriv *key;
+
+ tag = nil;
+ fmtinstall('B', mpfmt);
+
+ ARGBEGIN{
+ case 't':
+ tag = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0)
+ usage();
+
+ key = dsagen(nil);
+
+ s = smprint("key proto=dsa %s%sp=%B q=%B alpha=%B key=%B !secret=%B\n",
+ tag ? tag : "", tag ? " " : "",
+ key->pub.p, key->pub.q, key->pub.alpha, key->pub.key,
+ key->secret);
+ if(s == nil)
+ sysfatal("smprint: %r");
+
+ if(write(1, s, strlen(s)) != strlen(s))
+ sysfatal("write: %r");
+
+ exits(nil);
+}
diff --git a/sys/src/cmd/auth/enable b/sys/src/cmd/auth/enable
new file mode 100755
index 000000000..f7a91189c
--- /dev/null
+++ b/sys/src/cmd/auth/enable
@@ -0,0 +1,5 @@
+#!/bin/rc
+if(test -e /mnt/keys/$1)
+ echo -n ok > /mnt/keys/$1/status
+if(test -e /mnt/netkeys/$1)
+ echo -n ok > /mnt/netkeys/$1/status
diff --git a/sys/src/cmd/auth/factotum/apop.c b/sys/src/cmd/auth/factotum/apop.c
new file mode 100755
index 000000000..7709678c6
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/apop.c
@@ -0,0 +1,324 @@
+/*
+ * APOP, CRAM - MD5 challenge/response authentication
+ *
+ * The client does not authenticate the server, hence no CAI
+ *
+ * Client protocol:
+ * write challenge: randomstring@domain
+ * read response: 2*MD5dlen hex digits
+ *
+ * Server protocol:
+ * read challenge: randomstring@domain
+ * write user: user
+ * write response: 2*MD5dlen hex digits
+ */
+
+#include "dat.h"
+
+struct State
+{
+ int asfd;
+ int astype;
+ Key *key;
+ Ticket t;
+ Ticketreq tr;
+ char chal[128];
+ char resp[64];
+ char *user;
+};
+
+enum
+{
+ CNeedChal,
+ CHaveResp,
+
+ SHaveChal,
+ SNeedUser,
+ SNeedResp,
+
+ Maxphase,
+};
+
+static char *phasenames[Maxphase] = {
+[CNeedChal] "CNeedChal",
+[CHaveResp] "CHaveResp",
+
+[SHaveChal] "SHaveChal",
+[SNeedUser] "SNeedUser",
+[SNeedResp] "SNeedResp",
+};
+
+static int dochal(State*);
+static int doreply(State*, char*, char*);
+
+static int
+apopinit(Proto *p, Fsstate *fss)
+{
+ int iscli, ret;
+ State *s;
+
+ if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
+ return failure(fss, nil);
+
+ s = emalloc(sizeof *s);
+ fss->phasename = phasenames;
+ fss->maxphase = Maxphase;
+ s->asfd = -1;
+ if(p == &apop)
+ s->astype = AuthApop;
+ else if(p == &cram)
+ s->astype = AuthCram;
+ else
+ abort();
+
+ if(iscli)
+ fss->phase = CNeedChal;
+ else{
+ if((ret = findp9authkey(&s->key, fss)) != RpcOk){
+ free(s);
+ return ret;
+ }
+ if(dochal(s) < 0){
+ free(s);
+ return failure(fss, nil);
+ }
+ fss->phase = SHaveChal;
+ }
+ fss->ps = s;
+ return RpcOk;
+}
+
+static int
+apopwrite(Fsstate *fss, void *va, uint n)
+{
+ char *a, *v;
+ int i, ret;
+ uchar digest[MD5dlen];
+ DigestState *ds;
+ Key *k;
+ State *s;
+ Keyinfo ki;
+
+ s = fss->ps;
+ a = va;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "write");
+
+ case CNeedChal:
+ ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", fss->proto->keyprompt);
+ if(ret != RpcOk)
+ return ret;
+ v = _strfindattr(k->privattr, "!password");
+ if(v == nil)
+ return failure(fss, "key has no password");
+ setattrs(fss->attr, k->attr);
+ switch(s->astype){
+ default:
+ abort();
+ case AuthCram:
+ hmac_md5((uchar*)a, n, (uchar*)v, strlen(v),
+ digest, nil);
+ sprint(s->resp, "%.*H", MD5dlen, digest);
+ break;
+ case AuthApop:
+ ds = md5((uchar*)a, n, nil, nil);
+ md5((uchar*)v, strlen(v), digest, ds);
+ for(i=0; i<MD5dlen; i++)
+ sprint(&s->resp[2*i], "%2.2x", digest[i]);
+ break;
+ }
+ closekey(k);
+ fss->phase = CHaveResp;
+ return RpcOk;
+
+ case SNeedUser:
+ if((v = _strfindattr(fss->attr, "user")) && strcmp(v, a) != 0)
+ return failure(fss, "bad user");
+ fss->attr = setattr(fss->attr, "user=%q", a);
+ s->user = estrdup(a);
+ fss->phase = SNeedResp;
+ return RpcOk;
+
+ case SNeedResp:
+ if(n != 2*MD5dlen)
+ return failure(fss, "response not MD5 digest");
+ if(doreply(s, s->user, a) < 0){
+ fss->phase = SNeedUser;
+ return failure(fss, nil);
+ }
+ 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
+apopread(Fsstate *fss, void *va, uint *n)
+{
+ State *s;
+
+ s = fss->ps;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "read");
+
+ case CHaveResp:
+ if(*n > strlen(s->resp))
+ *n = strlen(s->resp);
+ memmove(va, s->resp, *n);
+ fss->phase = Established;
+ fss->haveai = 0;
+ return RpcOk;
+
+ case SHaveChal:
+ if(*n > strlen(s->chal))
+ *n = strlen(s->chal);
+ memmove(va, s->chal, *n);
+ fss->phase = SNeedUser;
+ return RpcOk;
+ }
+}
+
+static void
+apopclose(Fsstate *fss)
+{
+ State *s;
+
+ s = fss->ps;
+ if(s->asfd >= 0){
+ close(s->asfd);
+ s->asfd = -1;
+ }
+ if(s->key != nil){
+ closekey(s->key);
+ s->key = nil;
+ }
+ if(s->user != nil){
+ free(s->user);
+ s->user = nil;
+ }
+ free(s);
+}
+
+static int
+dochal(State *s)
+{
+ char *dom, *user, trbuf[TICKREQLEN];
+
+ s->asfd = -1;
+
+ /* send request to authentication server and get challenge */
+ /* send request to authentication server and get challenge */
+ if((dom = _strfindattr(s->key->attr, "dom")) == nil
+ || (user = _strfindattr(s->key->attr, "user")) == nil){
+ werrstr("apop/dochal cannot happen");
+ goto err;
+ }
+
+ s->asfd = _authdial(nil, dom);
+
+ /* could generate our own challenge on error here */
+ if(s->asfd < 0)
+ goto err;
+
+ memset(&s->tr, 0, sizeof(s->tr));
+ s->tr.type = s->astype;
+ safecpy(s->tr.authdom, dom, sizeof s->tr.authdom);
+ safecpy(s->tr.hostid, user, sizeof(s->tr.hostid));
+ convTR2M(&s->tr, trbuf);
+
+ if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+ goto err;
+ if(_asrdresp(s->asfd, s->chal, sizeof s->chal) <= 5)
+ goto err;
+ return 0;
+
+err:
+ if(s->asfd >= 0)
+ close(s->asfd);
+ s->asfd = -1;
+ return -1;
+}
+
+static int
+doreply(State *s, char *user, char *response)
+{
+ char ticket[TICKETLEN+AUTHENTLEN];
+ char trbuf[TICKREQLEN];
+ int n;
+ Authenticator a;
+
+ memrandom(s->tr.chal, CHALLEN);
+ safecpy(s->tr.uid, user, sizeof(s->tr.uid));
+ convTR2M(&s->tr, trbuf);
+ if((n=write(s->asfd, trbuf, TICKREQLEN)) != TICKREQLEN){
+ if(n >= 0)
+ werrstr("short write to auth server");
+ goto err;
+ }
+ /* send response to auth server */
+ if(strlen(response) != MD5dlen*2){
+ werrstr("response not MD5 digest");
+ goto err;
+ }
+ if((n=write(s->asfd, response, MD5dlen*2)) != MD5dlen*2){
+ if(n >= 0)
+ werrstr("short write to auth server");
+ goto err;
+ }
+ if(_asrdresp(s->asfd, ticket, TICKETLEN+AUTHENTLEN) < 0){
+ /* leave connection open so we can try again */
+ return -1;
+ }
+ close(s->asfd);
+ s->asfd = -1;
+
+ convM2T(ticket, &s->t, (char*)s->key->priv);
+ if(s->t.num != AuthTs
+ || memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0){
+ if(s->key->successes == 0)
+ disablekey(s->key);
+ werrstr(Easproto);
+ goto err;
+ }
+ s->key->successes++;
+ convM2A(ticket+TICKETLEN, &a, s->t.key);
+ if(a.num != AuthAc
+ || memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
+ || a.id != 0){
+ werrstr(Easproto);
+ goto err;
+ }
+
+ return 0;
+err:
+ if(s->asfd >= 0)
+ close(s->asfd);
+ s->asfd = -1;
+ return -1;
+}
+
+Proto apop = {
+.name= "apop",
+.init= apopinit,
+.write= apopwrite,
+.read= apopread,
+.close= apopclose,
+.addkey= replacekey,
+.keyprompt= "!password?"
+};
+
+Proto cram = {
+.name= "cram",
+.init= apopinit,
+.write= apopwrite,
+.read= apopread,
+.close= apopclose,
+.addkey= replacekey,
+.keyprompt= "!password?"
+};
diff --git a/sys/src/cmd/auth/factotum/chap.c b/sys/src/cmd/auth/factotum/chap.c
new file mode 100755
index 000000000..b941e0cf3
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/chap.c
@@ -0,0 +1,452 @@
+/*
+ * CHAP, MSCHAP
+ *
+ * The client does not authenticate the server, hence no CAI
+ *
+ * Client protocol:
+ * write Chapchal
+ * read response Chapreply or MSchaprely structure
+ *
+ * Server protocol:
+ * read challenge: 8 bytes binary
+ * write user: utf8
+ * write response: Chapreply or MSchapreply structure
+ */
+
+#include <ctype.h>
+#include "dat.h"
+
+enum {
+ ChapChallen = 8,
+ ChapResplen = 16,
+ MSchapResplen = 24,
+};
+
+static int dochal(State*);
+static int doreply(State*, void*, int);
+static void doLMchap(char *, uchar [ChapChallen], uchar [MSchapResplen]);
+static void doNTchap(char *, uchar [ChapChallen], uchar [MSchapResplen]);
+static void dochap(char *, int, char [ChapChallen], uchar [ChapResplen]);
+
+
+struct State
+{
+ char *protoname;
+ int astype;
+ int asfd;
+ Key *key;
+ Ticket t;
+ Ticketreq tr;
+ char chal[ChapChallen];
+ MSchapreply mcr;
+ char cr[ChapResplen];
+ char err[ERRMAX];
+ char user[64];
+ uchar secret[16]; /* for mschap */
+ int nsecret;
+};
+
+enum
+{
+ CNeedChal,
+ CHaveResp,
+
+ SHaveChal,
+ SNeedUser,
+ SNeedResp,
+ SHaveZero,
+ SHaveCAI,
+
+ Maxphase
+};
+
+static char *phasenames[Maxphase] =
+{
+[CNeedChal] "CNeedChal",
+[CHaveResp] "CHaveResp",
+
+[SHaveChal] "SHaveChal",
+[SNeedUser] "SNeedUser",
+[SNeedResp] "SNeedResp",
+[SHaveZero] "SHaveZero",
+[SHaveCAI] "SHaveCAI",
+};
+
+static int
+chapinit(Proto *p, Fsstate *fss)
+{
+ int iscli, ret;
+ State *s;
+
+ if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
+ return failure(fss, nil);
+
+ s = emalloc(sizeof *s);
+ fss->phasename = phasenames;
+ fss->maxphase = Maxphase;
+ s->asfd = -1;
+ if(p == &chap){
+ s->astype = AuthChap;
+ s->protoname = "chap";
+ }else{
+ s->astype = AuthMSchap;
+ s->protoname = "mschap";
+ }
+
+ if(iscli)
+ fss->phase = CNeedChal;
+ else{
+ if((ret = findp9authkey(&s->key, fss)) != RpcOk){
+ free(s);
+ return ret;
+ }
+ if(dochal(s) < 0){
+ free(s);
+ return failure(fss, nil);
+ }
+ fss->phase = SHaveChal;
+ }
+
+ fss->ps = s;
+ return RpcOk;
+}
+
+static void
+chapclose(Fsstate *fss)
+{
+ State *s;
+
+ s = fss->ps;
+ if(s->asfd >= 0){
+ close(s->asfd);
+ s->asfd = -1;
+ }
+ free(s);
+}
+
+
+static int
+chapwrite(Fsstate *fss, void *va, uint n)
+{
+ int ret, nreply;
+ char *a, *v;
+ void *reply;
+ Key *k;
+ Keyinfo ki;
+ State *s;
+ Chapreply cr;
+ MSchapreply mcr;
+ OChapreply ocr;
+ OMSchapreply omcr;
+
+ s = fss->ps;
+ a = va;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "write");
+
+ case CNeedChal:
+ ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", fss->proto->keyprompt);
+ if(ret != RpcOk)
+ return ret;
+ v = _strfindattr(k->privattr, "!password");
+ if(v == nil)
+ return failure(fss, "key has no password");
+ setattrs(fss->attr, k->attr);
+ switch(s->astype){
+ default:
+ abort();
+ case AuthMSchap:
+ doLMchap(v, (uchar *)a, (uchar *)s->mcr.LMresp);
+ doNTchap(v, (uchar *)a, (uchar *)s->mcr.NTresp);
+ break;
+ case AuthChap:
+ dochap(v, *a, a+1, (uchar *)s->cr);
+ break;
+ }
+ closekey(k);
+ fss->phase = CHaveResp;
+ return RpcOk;
+
+ case SNeedUser:
+ if(n >= sizeof s->user)
+ return failure(fss, "user name too long");
+ memmove(s->user, va, n);
+ s->user[n] = '\0';
+ fss->phase = SNeedResp;
+ return RpcOk;
+
+ case SNeedResp:
+ switch(s->astype){
+ default:
+ return failure(fss, "chap internal botch");
+ case AuthChap:
+ if(n != sizeof(Chapreply))
+ return failure(fss, "did not get Chapreply");
+ memmove(&cr, va, sizeof cr);
+ ocr.id = cr.id;
+ memmove(ocr.resp, cr.resp, sizeof ocr.resp);
+ memset(omcr.uid, 0, sizeof(omcr.uid));
+ strecpy(ocr.uid, ocr.uid+sizeof ocr.uid, s->user);
+ reply = &ocr;
+ nreply = sizeof ocr;
+ break;
+ case AuthMSchap:
+ if(n != sizeof(MSchapreply))
+ return failure(fss, "did not get MSchapreply");
+ memmove(&mcr, va, sizeof mcr);
+ memmove(omcr.LMresp, mcr.LMresp, sizeof omcr.LMresp);
+ memmove(omcr.NTresp, mcr.NTresp, sizeof omcr.NTresp);
+ memset(omcr.uid, 0, sizeof(omcr.uid));
+ strecpy(omcr.uid, omcr.uid+sizeof omcr.uid, s->user);
+ reply = &omcr;
+ nreply = sizeof omcr;
+ break;
+ }
+ if(doreply(s, reply, nreply) < 0)
+ return failure(fss, nil);
+ fss->phase = Established;
+ fss->ai.cuid = s->t.cuid;
+ fss->ai.suid = s->t.suid;
+ fss->ai.secret = s->secret;
+ fss->ai.nsecret = s->nsecret;
+ fss->haveai = 1;
+ return RpcOk;
+ }
+}
+
+static int
+chapread(Fsstate *fss, void *va, uint *n)
+{
+ State *s;
+
+ s = fss->ps;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "read");
+
+ case CHaveResp:
+ switch(s->astype){
+ default:
+ phaseerror(fss, "write");
+ break;
+ case AuthMSchap:
+ if(*n > sizeof(MSchapreply))
+ *n = sizeof(MSchapreply);
+ memmove(va, &s->mcr, *n);
+ break;
+ case AuthChap:
+ if(*n > ChapResplen)
+ *n = ChapResplen;
+ memmove(va, s->cr, ChapResplen);
+ break;
+ }
+ fss->phase = Established;
+ fss->haveai = 0;
+ return RpcOk;
+
+ case SHaveChal:
+ if(*n > sizeof s->chal)
+ *n = sizeof s->chal;
+ memmove(va, s->chal, *n);
+ fss->phase = SNeedUser;
+ return RpcOk;
+ }
+}
+
+static int
+dochal(State *s)
+{
+ char *dom, *user;
+ char trbuf[TICKREQLEN];
+
+ s->asfd = -1;
+
+ /* send request to authentication server and get challenge */
+ if((dom = _strfindattr(s->key->attr, "dom")) == nil
+ || (user = _strfindattr(s->key->attr, "user")) == nil){
+ werrstr("chap/dochal cannot happen");
+ goto err;
+ }
+ s->asfd = _authdial(nil, dom);
+ if(s->asfd < 0)
+ goto err;
+
+ memset(&s->tr, 0, sizeof(s->tr));
+ s->tr.type = s->astype;
+ safecpy(s->tr.authdom, dom, sizeof s->tr.authdom);
+ safecpy(s->tr.hostid, user, sizeof(s->tr.hostid));
+ convTR2M(&s->tr, trbuf);
+
+ if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+ goto err;
+
+ /* readn, not _asrdresp. needs to match auth.srv.c. */
+ if(readn(s->asfd, s->chal, sizeof s->chal) != sizeof s->chal)
+ goto err;
+ return 0;
+
+err:
+ if(s->asfd >= 0)
+ close(s->asfd);
+ s->asfd = -1;
+ return -1;
+}
+
+static int
+doreply(State *s, void *reply, int nreply)
+{
+ char ticket[TICKETLEN+AUTHENTLEN];
+ int n;
+ Authenticator a;
+
+ if((n=write(s->asfd, reply, nreply)) != nreply){
+ if(n >= 0)
+ werrstr("short write to auth server");
+ goto err;
+ }
+
+ if(_asrdresp(s->asfd, ticket, TICKETLEN+AUTHENTLEN) < 0){
+ /* leave connection open so we can try again */
+ return -1;
+ }
+ s->nsecret = readn(s->asfd, s->secret, sizeof s->secret);
+ if(s->nsecret < 0)
+ s->nsecret = 0;
+ close(s->asfd);
+ s->asfd = -1;
+ convM2T(ticket, &s->t, s->key->priv);
+ if(s->t.num != AuthTs
+ || memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0){
+ if(s->key->successes == 0)
+ disablekey(s->key);
+ werrstr(Easproto);
+ return -1;
+ }
+ s->key->successes++;
+ convM2A(ticket+TICKETLEN, &a, s->t.key);
+ if(a.num != AuthAc
+ || memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
+ || a.id != 0){
+ werrstr(Easproto);
+ return -1;
+ }
+
+ return 0;
+err:
+ if(s->asfd >= 0)
+ close(s->asfd);
+ s->asfd = -1;
+ return -1;
+}
+
+Proto chap = {
+.name= "chap",
+.init= chapinit,
+.write= chapwrite,
+.read= chapread,
+.close= chapclose,
+.addkey= replacekey,
+.keyprompt= "!password?"
+};
+
+Proto mschap = {
+.name= "mschap",
+.init= chapinit,
+.write= chapwrite,
+.read= chapread,
+.close= chapclose,
+.addkey= replacekey,
+.keyprompt= "!password?"
+};
+
+static void
+hash(uchar pass[16], uchar c8[ChapChallen], uchar p24[MSchapResplen])
+{
+ int i;
+ uchar p21[21];
+ ulong schedule[32];
+
+ memset(p21, 0, sizeof p21 );
+ memmove(p21, pass, 16);
+
+ for(i=0; i<3; i++) {
+ key_setup(p21+i*7, schedule);
+ memmove(p24+i*8, c8, 8);
+ block_cipher(schedule, p24+i*8, 0);
+ }
+}
+
+static void
+doNTchap(char *pass, uchar chal[ChapChallen], uchar reply[MSchapResplen])
+{
+ Rune r;
+ int i, n;
+ uchar digest[MD4dlen];
+ uchar *w, unipass[256];
+
+ // Standard says unlimited length, experience says 128 max
+ if ((n = strlen(pass)) > 128)
+ n = 128;
+
+ for(i=0, w=unipass; i < n; i++) {
+ pass += chartorune(&r, pass);
+ *w++ = r & 0xff;
+ *w++ = r >> 8;
+ }
+
+ memset(digest, 0, sizeof digest);
+ md4(unipass, w-unipass, digest, nil);
+ memset(unipass, 0, sizeof unipass);
+ hash(digest, chal, reply);
+}
+
+static void
+doLMchap(char *pass, uchar chal[ChapChallen], uchar reply[MSchapResplen])
+{
+ int i;
+ ulong schedule[32];
+ uchar p14[15], p16[16];
+ uchar s8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
+ int n = strlen(pass);
+
+ if(n > 14){
+ // let prudent people avoid the LM vulnerability
+ // and protect the loop below from buffer overflow
+ memset(reply, 0, MSchapResplen);
+ return;
+ }
+
+ // Spec says space padded, experience says otherwise
+ memset(p14, 0, sizeof p14 -1);
+ p14[sizeof p14 - 1] = '\0';
+
+ // NT4 requires uppercase, Win XP doesn't care
+ for (i = 0; pass[i]; i++)
+ p14[i] = islower(pass[i])? toupper(pass[i]): pass[i];
+
+ for(i=0; i<2; i++) {
+ key_setup(p14+i*7, schedule);
+ memmove(p16+i*8, s8, 8);
+ block_cipher(schedule, p16+i*8, 0);
+ }
+
+ memset(p14, 0, sizeof p14);
+ hash(p16, chal, reply);
+}
+
+static void
+dochap(char *pass, int id, char chal[ChapChallen], uchar resp[ChapResplen])
+{
+ char buf[1+ChapChallen+MAXNAMELEN+1];
+ int n = strlen(pass);
+
+ *buf = id;
+ if (n > MAXNAMELEN)
+ n = MAXNAMELEN-1;
+ memset(buf, 0, sizeof buf);
+ strncpy(buf+1, pass, n);
+ memmove(buf+1+n, chal, ChapChallen);
+ md5((uchar*)buf, 1+n+ChapChallen, resp, nil);
+}
+
diff --git a/sys/src/cmd/auth/factotum/confirm.c b/sys/src/cmd/auth/factotum/confirm.c
new file mode 100755
index 000000000..63086d0f2
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/confirm.c
@@ -0,0 +1,204 @@
+#include "dat.h"
+
+Logbuf confbuf;
+
+Req *cusewait; /* requests waiting for confirmation */
+Req **cuselast = &cusewait;
+
+void
+confirmread(Req *r)
+{
+ logbufread(&confbuf, r);
+}
+
+void
+confirmflush(Req *r)
+{
+ Req **l;
+
+ for(l=&cusewait; *l; l=&(*l)->aux){
+ if(*l == r){
+ *l = r->aux;
+ if(r->aux == nil)
+ cuselast = l;
+ closereq(r);
+ break;
+ }
+ }
+ logbufflush(&confbuf, r);
+}
+
+static int
+hastag(Fsstate *fss, int tag, int *tagoff)
+{
+ int i;
+
+ for(i=0; i<fss->nconf; i++)
+ if(fss->conf[i].tag == tag){
+ *tagoff = i;
+ return 1;
+ }
+ return 0;
+}
+
+int
+confirmwrite(char *s)
+{
+ char *t, *ans;
+ int allow, tagoff;
+ ulong tag;
+ Attr *a;
+ Fsstate *fss;
+ Req *r, **l;
+
+ a = _parseattr(s);
+ if(a == nil){
+ werrstr("empty write");
+ 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;
+ }
+ r = nil;
+ tagoff = -1;
+ for(l=&cusewait; *l; l=&(*l)->aux){
+ r = *l;
+ if(hastag(r->fid->aux, tag, &tagoff)){
+ *l = r->aux;
+ if(r->aux == nil)
+ cuselast = l;
+ break;
+ }
+ }
+ if(r == nil || tagoff == -1){
+ werrstr("tag not found");
+ return -1;
+ }
+ fss = r->fid->aux;
+ fss->conf[tagoff].canuse = allow;
+ rpcread(r);
+ return 0;
+}
+
+void
+confirmqueue(Req *r, Fsstate *fss)
+{
+ int i, n;
+ char msg[1024];
+
+ if(*confirminuse == 0){
+ respond(r, "confirm is closed");
+ return;
+ }
+
+ n = 0;
+ for(i=0; i<fss->nconf; i++)
+ if(fss->conf[i].canuse == -1){
+ n++;
+ snprint(msg, sizeof msg, "confirm tag=%lud %A", fss->conf[i].tag, fss->conf[i].key->attr);
+ logbufappend(&confbuf, msg);
+ }
+ if(n == 0){
+ respond(r, "no confirmations to wait for (bug)");
+ return;
+ }
+ *cuselast = r;
+ r->aux = nil;
+ cuselast = &r->aux;
+}
+
+/* Yes, I am unhappy that the code below is a copy of the code above. */
+
+Logbuf needkeybuf;
+Req *needwait; /* requests that need keys */
+Req **needlast = &needwait;
+
+void
+needkeyread(Req *r)
+{
+ logbufread(&needkeybuf, r);
+}
+
+void
+needkeyflush(Req *r)
+{
+ Req **l;
+
+ for(l=&needwait; *l; l=&(*l)->aux){
+ if(*l == r){
+ *l = r->aux;
+ if(r->aux == nil)
+ needlast = l;
+ closereq(r);
+ break;
+ }
+ }
+ logbufflush(&needkeybuf, r);
+}
+
+int
+needkeywrite(char *s)
+{
+ char *t;
+ ulong tag;
+ Attr *a;
+ Req *r, **l;
+
+ a = _parseattr(s);
+ if(a == nil){
+ werrstr("empty write");
+ return -1;
+ }
+ if((t = _strfindattr(a, "tag")) == nil){
+ werrstr("no tag");
+ return -1;
+ }
+ tag = strtoul(t, 0, 0);
+ r = nil;
+ for(l=&needwait; *l; l=&(*l)->aux){
+ r = *l;
+ if(r->tag == tag){
+ *l = r->aux;
+ if(r->aux == nil)
+ needlast = l;
+ break;
+ }
+ }
+ if(r == nil){
+ werrstr("tag not found");
+ return -1;
+ }
+ rpcread(r);
+ return 0;
+}
+
+int
+needkeyqueue(Req *r, Fsstate *fss)
+{
+ char msg[1024];
+
+ if(*needkeyinuse == 0)
+ return -1;
+
+ snprint(msg, sizeof msg, "needkey tag=%lud %s", r->tag, fss->keyinfo);
+ logbufappend(&needkeybuf, msg);
+ *needlast = r;
+ r->aux = nil;
+ needlast = &r->aux;
+ return 0;
+}
+
diff --git a/sys/src/cmd/auth/factotum/dat.h b/sys/src/cmd/auth/factotum/dat.h
new file mode 100755
index 000000000..841365774
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/dat.h
@@ -0,0 +1,238 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <authsrv.h>
+#include <mp.h>
+#include <libsec.h>
+#include <String.h>
+#include <thread.h> /* only for 9p.h */
+#include <fcall.h>
+#include <9p.h>
+
+#pragma varargck type "N" Attr*
+
+enum
+{
+ Maxname = 128,
+ Maxrpc = 4096,
+
+ /* common protocol phases; proto-specific phases start at 0 */
+ Notstarted = -3,
+ Broken = -2,
+ Established = -1,
+
+ /* rpc read/write return values */
+ RpcFailure = 0,
+ RpcNeedkey,
+ RpcOk,
+ RpcErrstr,
+ RpcToosmall,
+ RpcPhase,
+ RpcConfirm,
+};
+
+typedef struct Domain Domain;
+typedef struct Fsstate Fsstate;
+typedef struct Key Key;
+typedef struct Keyinfo Keyinfo;
+typedef struct Keyring Keyring;
+typedef struct Logbuf Logbuf;
+typedef struct Proto Proto;
+typedef struct State State;
+
+#pragma incomplete State
+
+
+struct Fsstate
+{
+ char *sysuser; /* user according to system */
+
+ /* keylist, protolist */
+ int listoff;
+
+ /* per-rpc transient information */
+ int pending;
+ struct {
+ char *arg, buf[Maxrpc], *verb;
+ int iverb, narg, nbuf, nwant;
+ } rpc;
+
+ /* persistent (cross-rpc) information */
+ char err[ERRMAX];
+ char keyinfo[3*Maxname]; /* key request */
+ char **phasename;
+ int haveai, maxphase, phase, seqnum, started;
+ Attr *attr;
+ AuthInfo ai;
+ Proto *proto;
+ State *ps;
+ struct { /* pending or finished key confirmations */
+ Key *key;
+ int canuse;
+ ulong tag;
+ } *conf;
+ int nconf;
+};
+
+struct Key
+{
+ int ref;
+ Attr *attr;
+ Attr *privattr; /* private attributes, like *data */
+ Proto *proto;
+
+ void *priv; /* protocol-specific; a parsed key, perhaps */
+ ulong successes;
+};
+
+struct Keyinfo /* for findkey */
+{
+ Fsstate *fss;
+ char *user;
+ int noconf;
+ int skip;
+ int usedisabled;
+ Attr *attr;
+};
+
+struct Keyring
+{
+ Key **key;
+ int nkey;
+};
+
+struct Logbuf
+{
+ Req *wait;
+ Req **waitlast;
+ int rp;
+ int wp;
+ char *msg[128];
+};
+
+struct Proto
+{
+ char *name;
+ int (*init)(Proto*, Fsstate*);
+ int (*addkey)(Key*, int);
+ void (*closekey)(Key*);
+ int (*write)(Fsstate*, void*, uint);
+ int (*read)(Fsstate*, void*, uint*);
+ void (*close)(Fsstate*);
+ char *keyprompt;
+};
+
+extern char *invoker;
+extern char *owner;
+extern char *authdom;
+
+extern char Easproto[];
+extern char Ebadarg[];
+extern char Ebadkey[];
+extern char Enegotiation[];
+extern char Etoolarge[];
+
+/* confirm.c */
+void confirmread(Req*);
+void confirmflush(Req*);
+int confirmwrite(char*);
+void confirmqueue(Req*, Fsstate*);
+void needkeyread(Req*);
+void needkeyflush(Req*);
+int needkeywrite(char*);
+int needkeyqueue(Req*, Fsstate*);
+
+/* fs.c */
+extern int askforkeys;
+extern char *authaddr;
+extern int *confirminuse;
+extern int debug;
+extern int gflag;
+extern int kflag;
+extern int *needkeyinuse;
+extern int sflag;
+extern int uflag;
+extern char *mtpt;
+extern char *service;
+extern Proto *prototab[];
+extern Keyring *ring;
+
+/* log.c */
+void flog(char*, ...);
+#pragma varargck argpos flog 1
+void logread(Req*);
+void logflush(Req*);
+void logbufflush(Logbuf*, Req*);
+void logbufread(Logbuf*, Req*);
+void logbufproc(Logbuf*);
+void logbufappend(Logbuf*, char*);
+void needkeyread(Req*);
+void needkeyflush(Req*);
+int needkeywrite(char*);
+int needkeyqueue(Req*, Fsstate*);
+
+/* rpc.c */
+int ctlwrite(char*, int);
+void rpcrdwrlog(Fsstate*, char*, uint, int, int);
+void rpcstartlog(Attr*, Fsstate*, int);
+void rpcread(Req*);
+void rpcwrite(Req*);
+
+/* secstore.c */
+int havesecstore(void);
+int secstorefetch(char*);
+
+/* util.c */
+#define emalloc emalloc9p
+#define estrdup estrdup9p
+#define erealloc erealloc9p
+#pragma varargck argpos failure 2
+#pragma varargck argpos findkey 3
+#pragma varargck argpos setattr 2
+
+int _authdial(char*, char*);
+void askuser(char*);
+int attrnamefmt(Fmt *fmt);
+int canusekey(Fsstate*, Key*);
+void closekey(Key*);
+uchar *convAI2M(AuthInfo*, uchar*, int);
+void disablekey(Key*);
+char *estrappend(char*, char*, ...);
+#pragma varargck argpos estrappend 2
+int failure(Fsstate*, char*, ...);
+Keyinfo* mkkeyinfo(Keyinfo*, Fsstate*, Attr*);
+int findkey(Key**, Keyinfo*, char*, ...);
+int findp9authkey(Key**, Fsstate*);
+Proto *findproto(char*);
+char *getnvramkey(int, char**);
+void initcap(void);
+int isclient(char*);
+int matchattr(Attr*, Attr*, Attr*);
+void memrandom(void*, int);
+char *mkcap(char*, char*);
+int phaseerror(Fsstate*, char*);
+char *phasename(Fsstate*, int, char*);
+void promptforhostowner(void);
+char *readcons(char*, char*, int);
+int replacekey(Key*, int before);
+char *safecpy(char*, char*, int);
+int secdial(void);
+Attr *setattr(Attr*, char*, ...);
+Attr *setattrs(Attr*, Attr*);
+void sethostowner(void);
+void setmalloctaghere(void*);
+int smatch(char*, char*);
+Attr *sortattr(Attr*);
+int toosmall(Fsstate*, uint);
+void writehostowner(char*);
+
+/* protocols */
+extern Proto apop, cram; /* apop.c */
+extern Proto p9any, p9sk1, p9sk2; /* p9sk.c */
+extern Proto chap, mschap; /* chap.c */
+extern Proto p9cr, vnc; /* p9cr.c */
+extern Proto pass; /* pass.c */
+extern Proto rsa; /* rsa.c */
+extern Proto wep; /* wep.c */
+/* extern Proto srs; /* srs.c */
+extern Proto httpdigest; /* httpdigest.c */
diff --git a/sys/src/cmd/auth/factotum/fgui.c b/sys/src/cmd/auth/factotum/fgui.c
new file mode 100755
index 000000000..f05126677
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/fgui.c
@@ -0,0 +1,780 @@
+#include "dat.h"
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <control.h>
+
+int ctldeletequits = 1;
+
+typedef struct RequestType RequestType;
+typedef struct Request Request;
+typedef struct Memory Memory;
+
+struct RequestType
+{
+ char *file; /* file to read requests from */
+ void (*f)(Request*); /* request handler */
+ void (*r)(Controlset*); /* resize handler */
+ int fd; /* fd = open(file, ORDWR) */
+ Channel *rc; /* channel requests are multiplexed to */
+ Controlset *cs;
+};
+
+struct Request
+{
+ RequestType *rt;
+ Attr *a;
+ Attr *tag;
+};
+
+struct Memory
+{
+ Memory *next;
+ Attr *a;
+ Attr *val;
+};
+Memory *mem;
+
+static void readreq(void*);
+static void hide(void);
+static void unhide(void);
+static void openkmr(void);
+static void closekmr(void);
+static Memory* searchmem(Attr*);
+static void addmem(Attr*, Attr*);
+
+static void confirm(Request*);
+static void resizeconfirm(Controlset*);
+static void needkey(Request*);
+static void resizeneedkey(Controlset*);
+
+Control *b_remember;
+Control *b_accept;
+Control *b_refuse;
+
+RequestType rt[] =
+{
+ { "/mnt/factotum/confirm", confirm, resizeconfirm, },
+ { "/mnt/factotum/needkey", needkey, resizeneedkey, },
+ { 0 },
+};
+
+enum
+{
+ ButtonDim= 15,
+};
+
+void
+threadmain(int argc, char *argv[])
+{
+ Request r;
+ Channel *rc;
+ RequestType *p;
+ Font *invis;
+
+ ARGBEGIN{
+ }ARGEND;
+
+ if(newwindow("-hide") < 0)
+ sysfatal("newwindow: %r");
+
+ fmtinstall('A', _attrfmt);
+
+ /* create the proc's that read */
+ rc = chancreate(sizeof(Request), 0);
+ for(p = rt; p->file != 0; p++){
+ p->fd = -1;
+ p->rc = rc;
+ proccreate(readreq, p, 16*1024);
+ }
+
+ /* gui initialization */
+ if(initdraw(0, 0, "auth/fgui") < 0)
+ sysfatal("initdraw failed: %r");
+ initcontrols();
+ hide();
+
+ /* get an invisible font for passwords */
+ invis = openfont(display, "/lib/font/bit/lucm/passwd.9.font");
+ if (invis == nil)
+ sysfatal("fgui: %s: %r", "/lib/font/bit/lucm/passwd.9.font");
+ namectlfont(invis, "invisible");
+
+ /* serialize all requests */
+ for(;;){
+ if(recv(rc, &r) < 0)
+ break;
+ (*r.rt->f)(&r);
+ _freeattr(r.a);
+ _freeattr(r.tag);
+ }
+
+ threadexitsall(nil);
+}
+
+/*
+ * read requests and pass them to the main loop
+ */
+enum
+{
+ Requestlen=4096,
+};
+static void
+readreq(void *a)
+{
+ RequestType *rt = a;
+ char *buf, *p;
+ int n;
+ Request r;
+ Attr **l;
+
+ rt->fd = open(rt->file, ORDWR);
+ if(rt->fd < 0)
+ sysfatal("opening %s: %r", rt->file);
+ rt->cs = nil;
+
+ buf = malloc(Requestlen);
+ if(buf == nil)
+ sysfatal("allocating read buffer: %r");
+ r.rt = rt;
+
+ for(;;){
+ n = read(rt->fd, buf, Requestlen-1);
+ if(n < 0)
+ break;
+ buf[n] = 0;
+
+ /* skip verb, parse attributes, and remove tag */
+ p = strchr(buf, ' ');
+ if(p != nil)
+ p++;
+ else
+ p = buf;
+ r.a = _parseattr(p);
+
+ /* separate out the tag */
+ r.tag = nil;
+ for(l = &r.a; *l != nil; l = &(*l)->next)
+ if(strcmp((*l)->name, "tag") == 0){
+ r.tag = *l;
+ *l = r.tag->next;
+ r.tag->next = nil;
+ break;
+ }
+
+ /* if no tag, forget it */
+ if(r.tag == nil){
+ _freeattr(r.a);
+ continue;
+ }
+
+ send(rt->rc, &r);
+ }
+}
+#ifdef asdf
+static void
+readreq(void *a)
+{
+ RequestType *rt = a;
+ char *buf, *p;
+ int n;
+ Request r;
+
+ rt->fd = -1;
+ rt->cs = nil;
+
+ buf = malloc(Requestlen);
+ if(buf == nil)
+ sysfatal("allocating read buffer: %r");
+ r.rt = rt;
+
+ for(;;){
+ strcpy(buf, "adfasdf=afdasdf asdfasdf=asdfasdf");
+ r.a = _parseattr(buf);
+ send(rt->rc, &r);
+ sleep(5000);
+ }
+}
+#endif asdf
+
+/*
+ * open/close the keyboard, mouse and resize channels
+ */
+static Channel *kbdc;
+static Channel *mousec;
+static Channel *resizec;
+static Keyboardctl *kctl;
+static Mousectl *mctl;
+
+static void
+openkmr(void)
+{
+ /* get channels for subsequent newcontrolset calls */
+ kctl = initkeyboard(nil);
+ if(kctl == nil)
+ sysfatal("can't initialize keyboard: %r");
+ kbdc = kctl->c;
+ mctl = initmouse(nil, screen);
+ if(mctl == nil)
+ sysfatal("can't initialize mouse: %r");
+ mousec = mctl->c;
+ resizec = mctl->resizec;
+}
+static void
+closekmr(void)
+{
+ Mouse m;
+
+ while(nbrecv(kbdc, &m) > 0)
+ ;
+ closekeyboard(kctl);
+ while(nbrecv(mousec, &m) > 0)
+ ;
+ closemouse(mctl);
+}
+
+
+/*
+ * called when the window is resized
+ */
+void
+resizecontrolset(Controlset *cs)
+{
+ RequestType *p;
+
+ for(p = rt; p->file != 0; p++){
+ if(p->cs == cs){
+ (*p->r)(cs);
+ break;
+ }
+ }
+}
+
+/*
+ * hide window when not in use
+ */
+static void
+unhide(void)
+{
+ int wctl;
+
+ wctl = open("/dev/wctl", OWRITE);
+ if(wctl < 0)
+ return;
+ fprint(wctl, "unhide");
+ close(wctl);
+}
+static void
+hide(void)
+{
+ int wctl;
+ int tries;
+
+ wctl = open("/dev/wctl", OWRITE);
+ if(wctl < 0)
+ return;
+ for(tries = 0; tries < 10; tries++){
+ if(fprint(wctl, "hide") >= 0)
+ break;
+ sleep(100);
+ }
+ close(wctl);
+}
+
+/*
+ * set up the controls for the confirmation window
+ */
+static Channel*
+setupconfirm(Request *r)
+{
+ Controlset *cs;
+ Channel *c;
+ Attr *a;
+
+ /* create a new control set for the confirmation */
+ openkmr();
+ cs = newcontrolset(screen, kbdc, mousec, resizec);
+
+ createtext(cs, "msg");
+ chanprint(cs->ctl, "msg image paleyellow");
+ chanprint(cs->ctl, "msg border 1");
+ chanprint(cs->ctl, "msg add 'The following key is being used:'");
+ for(a = r->a; a != nil; a = a->next)
+ chanprint(cs->ctl, "msg add ' %s = %s'", a->name,
+ a->val);
+
+ namectlimage(display->white, "i_white");
+ namectlimage(display->black, "i_black");
+
+ b_remember = createbutton(cs, "b_remember");
+ chanprint(cs->ctl, "b_remember border 1");
+ chanprint(cs->ctl, "b_remember mask i_white");
+ chanprint(cs->ctl, "b_remember image i_white");
+ chanprint(cs->ctl, "b_remember light i_black");
+
+ createtext(cs, "t_remember");
+ chanprint(cs->ctl, "t_remember image white");
+ chanprint(cs->ctl, "t_remember bordercolor white");
+ chanprint(cs->ctl, "t_remember add 'Remember this answer for future confirmations'");
+
+ b_accept = createtextbutton(cs, "b_accept");
+ chanprint(cs->ctl, "b_accept border 1");
+ chanprint(cs->ctl, "b_accept align center");
+ chanprint(cs->ctl, "b_accept text Accept");
+ chanprint(cs->ctl, "b_accept image i_white");
+ chanprint(cs->ctl, "b_accept light i_black");
+
+ b_refuse = createtextbutton(cs, "b_refuse");
+ chanprint(cs->ctl, "b_refuse border 1");
+ chanprint(cs->ctl, "b_refuse align center");
+ chanprint(cs->ctl, "b_refuse text Refuse");
+ chanprint(cs->ctl, "b_refuse image i_white");
+ chanprint(cs->ctl, "b_refuse light i_black");
+
+ c = chancreate(sizeof(char*), 0);
+ controlwire(b_remember, "event", c);
+ controlwire(b_accept, "event", c);
+ controlwire(b_refuse, "event", c);
+
+ /* make the controls interactive */
+ activate(b_remember);
+ activate(b_accept);
+ activate(b_refuse);
+ r->rt->cs = cs;
+ unhide();
+ resizecontrolset(cs);
+
+ return c;
+}
+
+/*
+ * resize the controls for the confirmation window
+ */
+static void
+resizeconfirm(Controlset *cs)
+{
+ Rectangle r, mr, nr, ntr, ar, rr;
+ int fontwidth;
+
+ fontwidth = font->height;
+
+ /* get usable window rectangle */
+ if(getwindow(display, Refnone) < 0)
+ ctlerror("resize failed: %r");
+ r = insetrect(screen->r, 10);
+
+ /* message box fills everything not needed for buttons */
+ mr = r;
+ mr.max.y = mr.min.y + font->height*((Dy(mr)-3*ButtonDim-font->height-4)/font->height);
+
+ /* remember button */
+ nr.min = Pt(mr.min.x, mr.max.y+ButtonDim);
+ nr.max = Pt(mr.max.x, r.max.y);
+ if(Dx(nr) > ButtonDim)
+ nr.max.x = nr.min.x+ButtonDim;
+ if(Dy(nr) > ButtonDim)
+ nr.max.y = nr.min.y+ButtonDim;
+ ntr.min = Pt(nr.max.x+ButtonDim, nr.min.y);
+ ntr.max = Pt(r.max.x, nr.min.y+font->height);
+
+ /* accept/refuse buttons */
+ ar.min = Pt(r.min.x+Dx(r)/2-ButtonDim-6*fontwidth, nr.max.y+ButtonDim);
+ ar.max = Pt(ar.min.x+6*fontwidth, ar.min.y+font->height+4);
+ rr.min = Pt(r.min.x+Dx(r)/2+ButtonDim, nr.max.y+ButtonDim);
+ rr.max = Pt(rr.min.x+6*fontwidth, rr.min.y+font->height+4);
+
+ /* make the controls visible */
+ chanprint(cs->ctl, "msg rect %R\nmsg show", mr);
+ chanprint(cs->ctl, "b_remember rect %R\nb_remember show", nr);
+ chanprint(cs->ctl, "t_remember rect %R\nt_remember show", ntr);
+ chanprint(cs->ctl, "b_accept rect %R\nb_accept show", ar);
+ chanprint(cs->ctl, "b_refuse rect %R\nb_refuse show", rr);
+}
+
+/*
+ * free the controls for the confirmation window
+ */
+static void
+teardownconfirm(Request *r)
+{
+ Controlset *cs;
+
+ cs = r->rt->cs;
+ r->rt->cs = nil;
+ hide();
+ closecontrolset(cs);
+ closekmr();
+}
+
+/*
+ * get user confirmation of a key
+ */
+static void
+confirm(Request *r)
+{
+ Channel *c;
+ char *s;
+ int n;
+ char *args[3];
+ int remember;
+ Attr *val;
+ Memory *m;
+
+ /* if it's something that the user wanted us not to ask again about */
+ m = searchmem(r->a);
+ if(m != nil){
+ fprint(r->rt->fd, "%A %A", r->tag, m->val);
+ return;
+ }
+
+ /* set up the controls */
+ c = setupconfirm(r);
+
+ /* wait for user to reply */
+ remember = 0;
+ for(;;){
+ s = recvp(c);
+ n = tokenize(s, args, nelem(args));
+ if(n==3 && strcmp(args[1], "value")==0){
+ if(strcmp(args[0], "b_remember:") == 0){
+ remember = atoi(args[2]);
+ }
+ if(strcmp(args[0], "b_accept:") == 0){
+ val = _mkattr(AttrNameval, "answer", "yes", nil);
+ free(s);
+ break;
+ }
+ if(strcmp(args[0], "b_refuse:") == 0){
+ val = _mkattr(AttrNameval, "answer", "no", nil);
+ free(s);
+ break;
+ }
+ }
+ free(s);
+ }
+ teardownconfirm(r);
+ fprint(r->rt->fd, "%A %A", r->tag, val);
+ if(remember)
+ addmem(_copyattr(r->a), val);
+ else
+ _freeattr(val);
+}
+
+/*
+ * confirmations that are remembered
+ */
+static int
+match(Attr *a, Attr *b)
+{
+ Attr *x;
+
+ for(; a != nil; a = a->next){
+ x = _findattr(b, a->name);
+ if(x == nil || strcmp(a->val, x->val) != 0)
+ return 0;
+ }
+ return 1;
+}
+static Memory*
+searchmem(Attr *a)
+{
+ Memory *m;
+
+ for(m = mem; m != nil; m = m->next){
+ if(match(a, m->a))
+ break;
+ }
+ return m;
+}
+static void
+addmem(Attr *a, Attr *val)
+{
+ Memory *m;
+
+ m = malloc(sizeof *m);
+ if(m == nil)
+ return;
+ m->a = a;
+ m->val = val;
+ m->next = mem;
+ mem = m;
+}
+
+/* controls for needkey */
+Control *msg;
+Control *b_done;
+enum {
+ Pprivate= 1<<0,
+ Pneed= 1<<1,
+};
+typedef struct Entry Entry;
+struct Entry {
+ Control *name;
+ Control *val;
+ Control *eq;
+ Attr *a;
+};
+static Entry *entry;
+static int entries;
+
+/*
+ * set up the controls for the confirmation window
+ */
+static Channel*
+setupneedkey(Request *r)
+{
+ Controlset *cs;
+ Channel *c;
+ Attr *a;
+ Attr **l;
+ char cn[10];
+ int i;
+
+ /* create a new control set for the confirmation */
+ openkmr();
+ cs = newcontrolset(screen, kbdc, mousec, resizec);
+
+ /* count attributes and allocate entry controls */
+ entries = 0;
+ for(l = &r->a; *l; l = &(*l)->next)
+ entries++;
+ if(entries == 0){
+ closecontrolset(cs);
+ closekmr();
+ return nil;
+ }
+ *l = a = mallocz(sizeof *a, 1);
+ a->type = AttrQuery;
+ entries++;
+ l = &(*l)->next;
+ *l = a = mallocz(sizeof *a, 1);
+ a->type = AttrQuery;
+ entries++;
+ entry = malloc(entries*sizeof(Entry));
+ if(entry == nil){
+ closecontrolset(cs);
+ closekmr();
+ return nil;
+ }
+
+ namectlimage(display->white, "i_white");
+ namectlimage(display->black, "i_black");
+
+ /* create controls */
+ msg = createtext(cs, "msg");
+ chanprint(cs->ctl, "msg image white");
+ chanprint(cs->ctl, "msg bordercolor white");
+ chanprint(cs->ctl, "msg add 'You need the following key. Fill in the blanks'");
+ chanprint(cs->ctl, "msg add 'and click on the DONE button.'");
+
+ for(i = 0, a = r->a; a != nil; i++, a = a->next){
+ entry[i].a = a;
+ snprint(cn, sizeof cn, "name_%d", i);
+ if(entry[i].a->name == nil){
+ entry[i].name = createentry(cs, cn);
+ chanprint(cs->ctl, "%s image yellow", cn);
+ chanprint(cs->ctl, "%s border 1", cn);
+ } else {
+ entry[i].name = createtext(cs, cn);
+ chanprint(cs->ctl, "%s image white", cn);
+ chanprint(cs->ctl, "%s bordercolor white", cn);
+ chanprint(cs->ctl, "%s add '%s'", cn, a->name);
+ }
+
+ snprint(cn, sizeof cn, "val_%d", i);
+ if(a->type == AttrQuery){
+ entry[i].val = createentry(cs, cn);
+ chanprint(cs->ctl, "%s image yellow", cn);
+ chanprint(cs->ctl, "%s border 1", cn);
+ if(a->name != nil){
+ if(strcmp(a->name, "user") == 0)
+ chanprint(cs->ctl, "%s value %q", cn, getuser());
+ if(*a->name == '!')
+ chanprint(cs->ctl, "%s font invisible", cn);
+ }
+ } else {
+ entry[i].val = createtext(cs, cn);
+ chanprint(cs->ctl, "%s image white", cn);
+ chanprint(cs->ctl, "%s add %q", cn, a->val);
+ }
+
+ snprint(cn, sizeof cn, "eq_%d", i);
+ entry[i].eq = createtext(cs, cn);
+ chanprint(cs->ctl, "%s image white", cn);
+ chanprint(cs->ctl, "%s add ' = '", cn);
+ }
+
+ b_done = createtextbutton(cs, "b_done");
+ chanprint(cs->ctl, "b_done border 1");
+ chanprint(cs->ctl, "b_done align center");
+ chanprint(cs->ctl, "b_done text DONE");
+ chanprint(cs->ctl, "b_done image green");
+ chanprint(cs->ctl, "b_done light green");
+
+ /* wire controls for input */
+ c = chancreate(sizeof(char*), 0);
+ controlwire(b_done, "event", c);
+ for(i = 0; i < entries; i++)
+ if(entry[i].a->type == AttrQuery)
+ controlwire(entry[i].val, "event", c);
+
+ /* make the controls interactive */
+ activate(msg);
+ activate(b_done);
+ for(i = 0; i < entries; i++){
+ if(entry[i].a->type != AttrQuery)
+ continue;
+ if(entry[i].a->name == nil)
+ activate(entry[i].name);
+ activate(entry[i].val);
+ }
+
+ /* change the display */
+ r->rt->cs = cs;
+ unhide();
+ resizecontrolset(cs);
+
+ return c;
+}
+
+/*
+ * resize the controls for the confirmation window
+ */
+static void
+resizeneedkey(Controlset *cs)
+{
+ Rectangle r, mr;
+ int mid, i, n, lasty;
+
+ /* get usable window rectangle */
+ if(getwindow(display, Refnone) < 0)
+ ctlerror("resize failed: %r");
+ r = insetrect(screen->r, 10);
+
+ /* find largest name */
+ mid = 0;
+ for(i = 0; i < entries; i++){
+ if(entry[i].a->name == nil)
+ continue;
+ n = strlen(entry[i].a->name);
+ if(n > mid)
+ mid = n;
+ }
+ mid = (mid+2) * font->height;
+
+ /* top line is the message */
+ mr = r;
+ mr.max.y = mr.min.y + 2*font->height + 2;
+ chanprint(cs->ctl, "msg rect %R\nmsg show", mr);
+
+ /* one line per attribute */
+ mr.min.x += 2*font->height;
+ lasty = mr.max.y;
+ for(i = 0; i < entries; i++){
+ r.min.x = mr.min.x;
+ r.min.y = lasty+2;
+ r.max.x = r.min.x + mid - 3*stringwidth(font, "=");
+ r.max.y = r.min.y + font->height;
+ chanprint(cs->ctl, "name_%d rect %R\nname_%d show", i, r, i);
+
+ r.min.x = r.max.x;
+ r.max.x = r.min.x + 3*stringwidth(font, "=");
+ chanprint(cs->ctl, "eq_%d rect %R\neq_%d show", i, r, i);
+
+ r.min.x = r.max.x;
+ r.max.x = mr.max.x;
+ if(Dx(r) > 32*font->height)
+ r.max.x = r.min.x + 32*font->height;
+ chanprint(cs->ctl, "val_%d rect %R\nval_%d show", i, r, i);
+ lasty = r.max.y;
+ }
+
+ /* done button */
+ mr.min.x -= 2*font->height;
+ r.min.x = mr.min.x + mid - 3*font->height;
+ r.min.y = lasty+10;
+ r.max.x = r.min.x + 6*font->height;
+ r.max.y = r.min.y + font->height + 2;
+ chanprint(cs->ctl, "b_done rect %R\nb_done show", r);
+}
+
+/*
+ * free the controls for the confirmation window
+ */
+static void
+teardownneedkey(Request *r)
+{
+ Controlset *cs;
+
+ cs = r->rt->cs;
+ r->rt->cs = nil;
+ hide();
+ closecontrolset(cs);
+ closekmr();
+
+ if(entry != nil)
+ free(entry);
+ entry = nil;
+}
+
+static void
+needkey(Request *r)
+{
+ Channel *c;
+ char *nam, *val;
+ int i, n;
+ int fd;
+ char *args[3];
+
+ /* set up the controls */
+ c = setupneedkey(r);
+ if(c == nil)
+ goto out;
+
+ /* wait for user to reply */
+ for(;;){
+ val = recvp(c);
+ n = tokenize(val, args, nelem(args));
+ if(n==3 && strcmp(args[1], "value")==0){ /* user hit 'enter' */
+ free(val);
+ break;
+ }
+ free(val);
+ }
+
+ /* get entry values */
+ for(i = 0; i < entries; i++){
+ if(entry[i].a->type != AttrQuery)
+ continue;
+
+ chanprint(r->rt->cs->ctl, "val_%d data", i);
+ val = recvp(entry[i].val->data);
+ if(entry[i].a->name == nil){
+ chanprint(r->rt->cs->ctl, "name_%d data", i);
+ nam = recvp(entry[i].name->data);
+ if(nam == nil || *nam == 0){
+ free(val);
+ continue;
+ }
+ entry[i].a->val = estrdup(val);
+ free(val);
+ entry[i].a->name = estrdup(nam);
+ free(nam);
+ } else {
+ if(val != nil){
+ entry[i].a->val = estrdup(val);
+ free(val);
+ }
+ }
+ entry[i].a->type = AttrNameval;
+ }
+
+ /* enter the new key !!!!need to do something in case of error!!!! */
+ fd = open("/mnt/factotum/ctl", OWRITE);
+ fprint(fd, "key %A", r->a);
+ close(fd);
+
+ teardownneedkey(r);
+out:
+ fprint(r->rt->fd, "%A", r->tag);
+}
diff --git a/sys/src/cmd/auth/factotum/fs.c b/sys/src/cmd/auth/factotum/fs.c
new file mode 100755
index 000000000..7d204aa09
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/fs.c
@@ -0,0 +1,601 @@
+#include "dat.h"
+
+int askforkeys = 1;
+char *authaddr;
+int debug;
+int doprivate = 1;
+int gflag;
+char *owner;
+int kflag;
+char *mtpt = "/mnt";
+Keyring *ring;
+char *service;
+int sflag;
+int uflag;
+
+extern Srv fs;
+static void notifyf(void*, char*);
+static void private(void);
+
+char Easproto[] = "auth server protocol botch";
+char Ebadarg[] = "invalid argument";
+char Ebadkey[] = "bad key";
+char Enegotiation[] = "negotiation failed, no common protocols or keys";
+char Etoolarge[] = "rpc too large";
+
+Proto*
+prototab[] =
+{
+ &apop,
+ &chap,
+ &cram,
+ &httpdigest,
+ &mschap,
+ &p9any,
+ &p9cr,
+ &p9sk1,
+ &p9sk2,
+ &pass,
+/* &srs, */
+ &rsa,
+ &vnc,
+ &wep,
+ nil,
+};
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-DSdknpu] [-a authaddr] [-m mtpt] [-s service]\n",
+ argv0);
+ fprint(2, "or %s -g 'params'\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int i, trysecstore;
+ char err[ERRMAX], *s;
+ Dir d;
+ Proto *p;
+ char *secstorepw;
+
+ trysecstore = 1;
+ secstorepw = nil;
+
+ ARGBEGIN{
+ case 'D':
+ chatty9p++;
+ break;
+ case 'S': /* server: read nvram, no prompting for keys */
+ askforkeys = 0;
+ trysecstore = 0;
+ sflag = 1;
+ break;
+ case 'a':
+ authaddr = EARGF(usage());
+ break;
+ case 'd':
+ debug = 1;
+ doprivate = 0;
+ break;
+ case 'g': /* get: prompt for key for name and domain */
+ gflag = 1;
+ break;
+ case 'k': /* reinitialize nvram */
+ kflag = 1;
+ break;
+ case 'm': /* set default mount point */
+ mtpt = EARGF(usage());
+ break;
+ case 'n':
+ trysecstore = 0;
+ break;
+ case 'p':
+ doprivate = 0;
+ break;
+ case 's': /* set service name */
+ service = EARGF(usage());
+ break;
+ case 'u': /* user: set hostowner */
+ uflag = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0 && !gflag)
+ usage();
+ if(doprivate)
+ private();
+
+ initcap();
+
+ quotefmtinstall();
+ fmtinstall('A', _attrfmt);
+ fmtinstall('N', attrnamefmt);
+ fmtinstall('H', encodefmt);
+
+ ring = emalloc(sizeof(*ring));
+ notify(notifyf);
+
+ if(gflag){
+ if(argc != 1)
+ usage();
+ askuser(argv[0]);
+ exits(nil);
+ }
+
+ for(i=0; prototab[i]; i++){
+ p = prototab[i];
+ if(p->name == nil)
+ sysfatal("protocol %d has no name", i);
+ if(p->init == nil)
+ sysfatal("protocol %s has no init", p->name);
+ if(p->write == nil)
+ sysfatal("protocol %s has no write", p->name);
+ if(p->read == nil)
+ sysfatal("protocol %s has no read", p->name);
+ if(p->close == nil)
+ sysfatal("protocol %s has no close", p->name);
+ if(p->keyprompt == nil)
+ p->keyprompt = "";
+ }
+
+ if(sflag){
+ s = getnvramkey(kflag ? NVwrite : NVwriteonerr, &secstorepw);
+ if(s == nil)
+ fprint(2, "factotum warning: cannot read nvram: %r\n");
+ else if(ctlwrite(s, 0) < 0)
+ fprint(2, "factotum warning: cannot add nvram key: %r\n");
+ if(secstorepw != nil)
+ trysecstore = 1;
+ if (s != nil) {
+ memset(s, 0, strlen(s));
+ free(s);
+ }
+ } else if(uflag)
+ promptforhostowner();
+ owner = getuser();
+
+ if(trysecstore){
+ if(havesecstore() == 1){
+ while(secstorefetch(secstorepw) < 0){
+ rerrstr(err, sizeof err);
+ if(strcmp(err, "cancel") == 0)
+ break;
+ fprint(2, "factotum: secstorefetch: %r\n");
+ fprint(2, "Enter an empty password to quit.\n");
+ free(secstorepw);
+ secstorepw = nil; /* just try nvram pw once */
+ }
+ }else{
+/*
+ rerrstr(err, sizeof err);
+ if(*err)
+ fprint(2, "factotum: havesecstore: %r\n");
+*/
+ }
+ }
+
+ postmountsrv(&fs, service, mtpt, MBEFORE);
+ if(service){
+ nulldir(&d);
+ d.mode = 0666;
+ s = emalloc(10+strlen(service));
+ strcpy(s, "/srv/");
+ strcat(s, service);
+ if(dirwstat(s, &d) < 0)
+ fprint(2, "factotum warning: cannot chmod 666 %s: %r\n", s);
+ free(s);
+ }
+ exits(nil);
+}
+
+char *pmsg = "Warning! %s can't protect itself from debugging: %r\n";
+char *smsg = "Warning! %s can't turn off swapping: %r\n";
+
+/* don't allow other processes to debug us and steal keys */
+static void
+private(void)
+{
+ int fd;
+ char buf[64];
+
+ snprint(buf, sizeof(buf), "#p/%d/ctl", getpid());
+ fd = open(buf, OWRITE);
+ if(fd < 0){
+ fprint(2, pmsg, argv0);
+ return;
+ }
+ if(fprint(fd, "private") < 0)
+ fprint(2, pmsg, argv0);
+ if(fprint(fd, "noswap") < 0)
+ fprint(2, smsg, argv0);
+ close(fd);
+}
+
+static void
+notifyf(void*, char *s)
+{
+ if(strncmp(s, "interrupt", 9) == 0)
+ noted(NCONT);
+ noted(NDFLT);
+}
+
+enum
+{
+ Qroot,
+ Qfactotum,
+ Qrpc,
+ Qkeylist,
+ Qprotolist,
+ Qconfirm,
+ Qlog,
+ Qctl,
+ Qneedkey,
+};
+
+Qid
+mkqid(int type, int path)
+{
+ Qid q;
+
+ q.type = type;
+ q.path = path;
+ q.vers = 0;
+ return q;
+}
+
+static void
+fsattach(Req *r)
+{
+ r->fid->qid = mkqid(QTDIR, Qroot);
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+static struct {
+ char *name;
+ int qidpath;
+ ulong perm;
+} dirtab[] = {
+ "confirm", Qconfirm, 0600|DMEXCL, /* we know this is slot #0 below */
+ "needkey", Qneedkey, 0600|DMEXCL, /* we know this is slot #1 below */
+ "ctl", Qctl, 0644,
+ "rpc", Qrpc, 0666,
+ "proto", Qprotolist, 0444,
+ "log", Qlog, 0400|DMEXCL,
+};
+static int inuse[nelem(dirtab)];
+int *confirminuse = &inuse[0];
+int *needkeyinuse = &inuse[1];
+
+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*)
+{
+ if(n > 0)
+ return -1;
+ fillstat(dir, "factotum", QTDIR, Qfactotum, DMDIR|0555);
+ return 0;
+}
+
+static int
+fsdirgen(int n, Dir *dir, void*)
+{
+ 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((ulong)fid->qid.path){
+ default:
+ return "cannot happen";
+ case Qroot:
+ if(strcmp(name, "factotum") == 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;
+ ulong path;
+
+ path = r->fid->qid.path;
+ if(path == Qroot){
+ fillstat(&r->d, "/", QTDIR, Qroot, 0555|DMDIR);
+ respond(r, nil);
+ return;
+ }
+ if(path == Qfactotum){
+ fillstat(&r->d, "factotum", QTDIR, Qfactotum, 0555|DMDIR);
+ respond(r, nil);
+ return;
+ }
+ 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);
+ respond(r, nil);
+ return;
+ }
+ respond(r, "file not found");
+}
+
+static void
+fsopen(Req *r)
+{
+ int i, *p, perm;
+ static int need[4] = {4, 2, 6, 1};
+ int n;
+ Fsstate *fss;
+
+ p = nil;
+ for(i=0; i<nelem(dirtab); i++)
+ if(dirtab[i].qidpath == r->fid->qid.path)
+ break;
+ if(i < nelem(dirtab)){
+ if(dirtab[i].perm & DMEXCL)
+ p = &inuse[i];
+ if(strcmp(r->fid->uid, owner) == 0)
+ perm = dirtab[i].perm>>6;
+ else
+ perm = dirtab[i].perm;
+ }else
+ perm = 5;
+
+ n = need[r->ifcall.mode&3];
+ if((r->ifcall.mode&~(3|OTRUNC)) || ((perm&n) != n)){
+ respond(r, "permission denied");
+ return;
+ }
+ if(p){
+ if(*p){
+ respond(r, "file in use");
+ return;
+ }
+ (*p)++;
+ }
+
+ r->fid->aux = fss = emalloc(sizeof(Fsstate));
+ fss->phase = Notstarted;
+ fss->sysuser = r->fid->uid;
+ fss->attr = nil;
+ strcpy(fss->err, "factotum/fs.c no error");
+ respond(r, nil);
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+ int i;
+ Fsstate *fss;
+
+ if(fid->omode != -1){
+ for(i=0; i<nelem(dirtab); i++)
+ if(dirtab[i].qidpath == fid->qid.path)
+ if(dirtab[i].perm&DMEXCL)
+ inuse[i] = 0;
+ }
+
+ fss = fid->aux;
+ if(fss == nil)
+ return;
+ if(fss->ps)
+ (*fss->proto->close)(fss);
+ _freeattr(fss->attr);
+ free(fss);
+}
+
+static int
+readlist(int off, int (*gen)(int, char*, uint, Fsstate*), Req *r, Fsstate *fss)
+{
+ char *a, *ea;
+ int n;
+
+ a = r->ofcall.data;
+ ea = a+r->ifcall.count;
+ for(;;){
+ n = (*gen)(off, a, ea-a, fss);
+ if(n == 0){
+ r->ofcall.count = a - (char*)r->ofcall.data;
+ return off;
+ }
+ a += n;
+ off++;
+ }
+}
+
+enum { Nearend = 2, }; /* at least room for \n and NUL */
+
+/* result in `a', of `n' bytes maximum */
+static int
+keylist(int i, char *a, uint n, Fsstate *fss)
+{
+ int wb;
+ Keyinfo ki;
+ Key *k;
+ static char zero[Nearend];
+
+ k = nil;
+ mkkeyinfo(&ki, fss, nil);
+ ki.attr = nil;
+ ki.skip = i;
+ ki.usedisabled = 1;
+ if(findkey(&k, &ki, "") != RpcOk)
+ return 0;
+
+ memset(a + n - Nearend, 0, Nearend);
+ wb = snprint(a, n, "key %A %N\n", k->attr, k->privattr);
+ closekey(k);
+ if (wb >= n - 1 && a[n - 2] != '\n' && a[n - 2] != '\0') {
+ /* line won't fit in `a', so just truncate */
+ strcpy(a + n - 2, "\n");
+ return 0;
+ }
+ return wb;
+}
+
+static int
+protolist(int i, char *a, uint n, Fsstate *fss)
+{
+ USED(fss);
+
+ if(i >= nelem(prototab)-1)
+ 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;
+}
+
+static void
+fsread(Req *r)
+{
+ Fsstate *s;
+
+ s = r->fid->aux;
+ switch((ulong)r->fid->qid.path){
+ default:
+ respond(r, "bug in fsread");
+ break;
+ case Qroot:
+ dirread9p(r, rootdirgen, nil);
+ respond(r, nil);
+ break;
+ case Qfactotum:
+ dirread9p(r, fsdirgen, nil);
+ respond(r, nil);
+ break;
+ case Qrpc:
+ rpcread(r);
+ break;
+ case Qneedkey:
+ needkeyread(r);
+ break;
+ case Qconfirm:
+ confirmread(r);
+ break;
+ case Qlog:
+ logread(r);
+ break;
+ case Qctl:
+ s->listoff = readlist(s->listoff, keylist, r, s);
+ respond(r, nil);
+ break;
+ case Qprotolist:
+ s->listoff = readlist(s->listoff, protolist, r, s);
+ respond(r, nil);
+ break;
+ }
+}
+
+static void
+fswrite(Req *r)
+{
+ int ret;
+ char err[ERRMAX], *s;
+
+ switch((ulong)r->fid->qid.path){
+ default:
+ respond(r, "bug in fswrite");
+ break;
+ case Qrpc:
+ rpcwrite(r);
+ break;
+ case Qneedkey:
+ case Qconfirm:
+ case Qctl:
+ s = emalloc(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = '\0';
+ switch((ulong)r->fid->qid.path){
+ default:
+ abort();
+ case Qneedkey:
+ ret = needkeywrite(s);
+ break;
+ case Qconfirm:
+ ret = confirmwrite(s);
+ break;
+ case Qctl:
+ ret = ctlwrite(s, 0);
+ break;
+ }
+ 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->oldreq);
+ needkeyflush(r->oldreq);
+ logflush(r->oldreq);
+ respond(r, nil);
+}
+
+Srv fs = {
+.attach= fsattach,
+.walk1= fswalk1,
+.open= fsopen,
+.read= fsread,
+.write= fswrite,
+.stat= fsstat,
+.flush= fsflush,
+.destroyfid= fsdestroyfid,
+};
+
diff --git a/sys/src/cmd/auth/factotum/httpdigest.c b/sys/src/cmd/auth/factotum/httpdigest.c
new file mode 100755
index 000000000..5559d5cad
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/httpdigest.c
@@ -0,0 +1,184 @@
+/*
+ * HTTPDIGEST - MD5 challenge/response authentication (RFC 2617)
+ *
+ * Client protocol:
+ * write challenge: nonce method uri
+ * read response: 2*MD5dlen hex digits
+ *
+ * Server protocol:
+ * unimplemented
+ */
+#include "dat.h"
+
+enum
+{
+ CNeedChal,
+ CHaveResp,
+
+ Maxphase,
+};
+
+static char *phasenames[Maxphase] = {
+[CNeedChal] "CNeedChal",
+[CHaveResp] "CHaveResp",
+};
+
+struct State
+{
+ char resp[MD5dlen*2+1];
+};
+
+static int
+hdinit(Proto *p, Fsstate *fss)
+{
+ int iscli;
+ State *s;
+
+ if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
+ return failure(fss, nil);
+ if(!iscli)
+ return failure(fss, "%s server not supported", p->name);
+
+ s = emalloc(sizeof *s);
+ fss->phasename = phasenames;
+ fss->maxphase = Maxphase;
+ fss->phase = CNeedChal;
+ fss->ps = s;
+ return RpcOk;
+}
+
+static void
+strtolower(char *s)
+{
+ while(*s){
+ *s = tolower(*s);
+ s++;
+ }
+}
+
+static void
+digest(char *user, char *realm, char *passwd,
+ char *nonce, char *method, char *uri,
+ char *dig)
+{
+ uchar b[MD5dlen];
+ char ha1[MD5dlen*2+1];
+ char ha2[MD5dlen*2+1];
+ DigestState *s;
+
+ /*
+ * H(A1) = MD5(uid + ":" + realm ":" + passwd)
+ */
+ s = md5((uchar*)user, strlen(user), nil, nil);
+ md5((uchar*)":", 1, nil, s);
+ md5((uchar*)realm, strlen(realm), nil, s);
+ md5((uchar*)":", 1, nil, s);
+ md5((uchar*)passwd, strlen(passwd), b, s);
+ enc16(ha1, sizeof(ha1), b, MD5dlen);
+ strtolower(ha1);
+
+ /*
+ * H(A2) = MD5(method + ":" + uri)
+ */
+ s = md5((uchar*)method, strlen(method), nil, nil);
+ md5((uchar*)":", 1, nil, s);
+ md5((uchar*)uri, strlen(uri), b, s);
+ enc16(ha2, sizeof(ha2), b, MD5dlen);
+ strtolower(ha2);
+
+ /*
+ * digest = MD5(H(A1) + ":" + nonce + ":" + H(A2))
+ */
+ s = md5((uchar*)ha1, MD5dlen*2, nil, nil);
+ md5((uchar*)":", 1, nil, s);
+ md5((uchar*)nonce, strlen(nonce), nil, s);
+ md5((uchar*)":", 1, nil, s);
+ md5((uchar*)ha2, MD5dlen*2, b, s);
+ enc16(dig, MD5dlen*2+1, b, MD5dlen);
+ strtolower(dig);
+}
+
+static int
+hdwrite(Fsstate *fss, void *va, uint n)
+{
+ State *s;
+ int ret;
+ char *a, *p, *r, *u, *t;
+ char *tok[4];
+ Key *k;
+ Keyinfo ki;
+ Attr *attr;
+
+ s = fss->ps;
+ a = va;
+
+ if(fss->phase != CNeedChal)
+ return phaseerror(fss, "write");
+
+ attr = _delattr(_copyattr(fss->attr), "role");
+ mkkeyinfo(&ki, fss, attr);
+ ret = findkey(&k, &ki, "%s", fss->proto->keyprompt);
+ _freeattr(attr);
+ if(ret != RpcOk)
+ return ret;
+ p = _strfindattr(k->privattr, "!password");
+ if(p == nil)
+ return failure(fss, "key has no password");
+ r = _strfindattr(k->attr, "realm");
+ if(r == nil)
+ return failure(fss, "key has no realm");
+ u = _strfindattr(k->attr, "user");
+ if(u == nil)
+ return failure(fss, "key has no user");
+ setattrs(fss->attr, k->attr);
+
+ /* copy in case a is not null-terminated */
+ t = emalloc(n+1);
+ memcpy(t, a, n);
+ t[n] = 0;
+
+ /* get nonce, method, uri */
+ if(tokenize(t, tok, 4) != 3)
+ return failure(fss, "bad challenge");
+
+ digest(u, r, p, tok[0], tok[1], tok[2], s->resp);
+
+ free(t);
+ closekey(k);
+ fss->phase = CHaveResp;
+ return RpcOk;
+}
+
+static int
+hdread(Fsstate *fss, void *va, uint *n)
+{
+ State *s;
+
+ s = fss->ps;
+ if(fss->phase != CHaveResp)
+ return phaseerror(fss, "read");
+ if(*n > strlen(s->resp))
+ *n = strlen(s->resp);
+ memmove(va, s->resp, *n);
+ fss->phase = Established;
+ fss->haveai = 0;
+ return RpcOk;
+}
+
+static void
+hdclose(Fsstate *fss)
+{
+ State *s;
+ s = fss->ps;
+ free(s);
+}
+
+Proto httpdigest = {
+.name= "httpdigest",
+.init= hdinit,
+.write= hdwrite,
+.read= hdread,
+.close= hdclose,
+.addkey= replacekey,
+.keyprompt= "user? realm? !password?"
+};
diff --git a/sys/src/cmd/auth/factotum/log.c b/sys/src/cmd/auth/factotum/log.c
new file mode 100755
index 000000000..82ed6adf9
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/log.c
@@ -0,0 +1,110 @@
+#include "dat.h"
+
+void
+logbufproc(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
+logbufread(Logbuf *lb, Req *r)
+{
+ if(lb->waitlast == nil)
+ lb->waitlast = &lb->wait;
+ *(lb->waitlast) = r;
+ lb->waitlast = &r->aux;
+ r->aux = nil;
+ logbufproc(lb);
+}
+
+void
+logbufflush(Logbuf *lb, Req *r)
+{
+ Req **l;
+
+ for(l=&lb->wait; *l; l=&(*l)->aux){
+ if(*l == r){
+ *l = r->aux;
+ r->aux = nil;
+ if(*l == nil)
+ lb->waitlast = l;
+ respond(r, "interrupted");
+ break;
+ }
+ }
+}
+
+void
+logbufappend(Logbuf *lb, char *buf)
+{
+ if(debug)
+ fprint(2, "%s\n", buf);
+
+ if(lb->msg[lb->wp])
+ free(lb->msg[lb->wp]);
+ lb->msg[lb->wp] = estrdup9p(buf);
+ if(++lb->wp == nelem(lb->msg))
+ lb->wp = 0;
+ logbufproc(lb);
+}
+
+Logbuf logbuf;
+
+void
+logread(Req *r)
+{
+ logbufread(&logbuf, r);
+}
+
+void
+logflush(Req *r)
+{
+ logbufflush(&logbuf, r);
+}
+
+void
+flog(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof buf, fmt, arg);
+ va_end(arg);
+ logbufappend(&logbuf, buf);
+}
+
diff --git a/sys/src/cmd/auth/factotum/mkfile b/sys/src/cmd/auth/factotum/mkfile
new file mode 100755
index 000000000..1bdf31a62
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/mkfile
@@ -0,0 +1,44 @@
+</$objtype/mkfile
+
+TARG=factotum\
+ fgui\
+
+PROTO=\
+ apop.$O\
+ chap.$O\
+ httpdigest.$O\
+ p9any.$O\
+ p9cr.$O\
+ p9sk1.$O\
+ pass.$O\
+ rsa.$O\
+ wep.$O\
+
+FOFILES=\
+ $PROTO\
+ confirm.$O\
+ fs.$O\
+ log.$O\
+ rpc.$O\
+ util.$O\
+ secstore.$O\
+
+HFILES=\
+ dat.h\
+
+LIB=/$objtype/lib/libauth.a /$objtype/lib/libauthsrv.a
+BIN=/$objtype/bin/auth
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${FOFILES:%.$O=%.c}\
+ fgui.c\
+
+</sys/src/cmd/mkmany
+
+$O.factotum: $FOFILES
+ $LD -o $target $prereq
+
+$O.fgui: fgui.$O
+ $LD -o $target $prereq
diff --git a/sys/src/cmd/auth/factotum/p9any.c b/sys/src/cmd/auth/factotum/p9any.c
new file mode 100755
index 000000000..204e99b25
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/p9any.c
@@ -0,0 +1,424 @@
+/*
+ * p9any - protocol negotiator.
+ *
+ * Protocol:
+ * Server->Client: list of proto@domain, tokenize separated, nul terminated
+ * Client->Server: proto domain, tokenize separated (not proto@domain), nul terminated
+ *
+ * Server protocol:
+ * read list of protocols.
+ * write null-terminated
+ */
+
+#include "dat.h"
+
+static Proto *negotiable[] = {
+ &p9sk1,
+};
+
+struct State
+{
+ Fsstate subfss;
+ State *substate; /* be very careful; this is not one of our States */
+ Proto *subproto;
+ int keyasked;
+ String *subdom;
+ int version;
+};
+
+enum
+{
+ CNeedProtos,
+ CHaveProto,
+ CNeedOK,
+ CRelay,
+
+ SHaveProtos,
+ SNeedProto,
+ SHaveOK,
+ SRelay,
+
+ Maxphase,
+};
+
+static char *phasenames[Maxphase] =
+{
+[CNeedProtos] "CNeedProtos",
+[CHaveProto] "CHaveProto",
+[CNeedOK] "CNeedOK",
+[CRelay] "CRelay",
+[SHaveProtos] "SHaveProtos",
+[SNeedProto] "SNeedProto",
+[SHaveOK] "SHaveOK",
+[SRelay] "SRelay",
+};
+
+static int
+p9anyinit(Proto*, Fsstate *fss)
+{
+ int iscli;
+ State *s;
+
+ if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
+ return failure(fss, nil);
+
+ s = emalloc(sizeof *s);
+ fss = fss;
+ fss->phasename = phasenames;
+ fss->maxphase = Maxphase;
+ if(iscli)
+ fss->phase = CNeedProtos;
+ else
+ fss->phase = SHaveProtos;
+ s->version = 1;
+ fss->ps = s;
+ return RpcOk;
+}
+
+static void
+p9anyclose(Fsstate *fss)
+{
+ State *s;
+
+ s = fss->ps;
+ if(s->subproto && s->subfss.ps && s->subproto->close)
+ (*s->subproto->close)(&s->subfss);
+ s->subproto = nil;
+ s->substate = nil;
+ s_free(s->subdom);
+ s->subdom = nil;
+ s->keyasked = 0;
+ memset(&s->subfss, 0, sizeof s->subfss);
+ free(s);
+}
+
+static void
+setupfss(Fsstate *fss, State *s, Key *k)
+{
+ fss->attr = setattr(fss->attr, "proto=%q", s->subproto->name);
+ fss->attr = setattr(fss->attr, "dom=%q", _strfindattr(k->attr, "dom"));
+ s->subfss.attr = fss->attr;
+ s->subfss.phase = Notstarted;
+ s->subfss.sysuser = fss->sysuser;
+ s->subfss.seqnum = fss->seqnum;
+ s->subfss.conf = fss->conf;
+ s->subfss.nconf = fss->nconf;
+}
+
+static int
+passret(Fsstate *fss, State *s, int ret)
+{
+ switch(ret){
+ default:
+ return ret;
+ case RpcFailure:
+ if(s->subfss.phase == Broken)
+ fss->phase = Broken;
+ memmove(fss->err, s->subfss.err, sizeof fss->err);
+ return ret;
+ case RpcNeedkey:
+ memmove(fss->keyinfo, s->subfss.keyinfo, sizeof fss->keyinfo);
+ return ret;
+ case RpcOk:
+ if(s->subfss.haveai){
+ fss->haveai = 1;
+ fss->ai = s->subfss.ai;
+ s->subfss.haveai = 0;
+ }
+ if(s->subfss.phase == Established)
+ fss->phase = Established;
+ return ret;
+ case RpcToosmall:
+ fss->rpc.nwant = s->subfss.rpc.nwant;
+ return ret;
+ case RpcConfirm:
+ fss->conf = s->subfss.conf;
+ fss->nconf = s->subfss.nconf;
+ return ret;
+ }
+}
+
+static int
+p9anyread(Fsstate *fss, void *a, uint *n)
+{
+ int i, m, ophase, ret;
+ Attr *anew;
+ Key *k;
+ Keyinfo ki;
+ String *negstr;
+ State *s;
+
+ s = fss->ps;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "read");
+
+ case SHaveProtos:
+ m = 0;
+ negstr = s_new();
+ mkkeyinfo(&ki, fss, nil);
+ ki.attr = nil;
+ ki.noconf = 1;
+ ki.user = nil;
+ for(i=0; i<nelem(negotiable); i++){
+ anew = setattr(_copyattr(fss->attr), "proto=%q dom?", negotiable[i]->name);
+ ki.attr = anew;
+ for(ki.skip=0; findkey(&k, &ki, nil)==RpcOk; ki.skip++){
+ if(m++)
+ s_append(negstr, " ");
+ s_append(negstr, negotiable[i]->name);
+ s_append(negstr, "@");
+ s_append(negstr, _strfindattr(k->attr, "dom"));
+ closekey(k);
+ }
+ _freeattr(anew);
+ }
+ if(m == 0){
+ s_free(negstr);
+ return failure(fss, Enegotiation);
+ }
+ i = s_len(negstr)+1;
+ if(*n < i){
+ s_free(negstr);
+ return toosmall(fss, i);
+ }
+ *n = i;
+ memmove(a, s_to_c(negstr), i+1);
+ fss->phase = SNeedProto;
+ s_free(negstr);
+ return RpcOk;
+
+ case CHaveProto:
+ i = strlen(s->subproto->name)+1+s_len(s->subdom)+1;
+ if(*n < i)
+ return toosmall(fss, i);
+ *n = i;
+ strcpy(a, s->subproto->name);
+ strcat(a, " ");
+ strcat(a, s_to_c(s->subdom));
+ if(s->version == 1)
+ fss->phase = CRelay;
+ else
+ fss->phase = CNeedOK;
+ return RpcOk;
+
+ case SHaveOK:
+ i = 3;
+ if(*n < i)
+ return toosmall(fss, i);
+ *n = i;
+ strcpy(a, "OK");
+ fss->phase = SRelay;
+ return RpcOk;
+
+ case CRelay:
+ case SRelay:
+ ophase = s->subfss.phase;
+ ret = (*s->subproto->read)(&s->subfss, a, n);
+ rpcrdwrlog(&s->subfss, "read", *n, ophase, ret);
+ return passret(fss, s, ret);
+ }
+}
+
+static char*
+getdom(char *p)
+{
+ p = strchr(p, '@');
+ if(p == nil)
+ return "";
+ return p+1;
+}
+
+static Proto*
+findneg(char *name)
+{
+ int i, len;
+ char *p;
+
+ if(p = strchr(name, '@'))
+ len = p-name;
+ else
+ len = strlen(name);
+
+ for(i=0; i<nelem(negotiable); i++)
+ if(strncmp(negotiable[i]->name, name, len) == 0 && negotiable[i]->name[len] == 0)
+ return negotiable[i];
+ return nil;
+}
+
+static int
+p9anywrite(Fsstate *fss, void *va, uint n)
+{
+ char *a, *dom, *user, *token[20];
+ int asking, i, m, ophase, ret;
+ Attr *anew, *anewsf, *attr;
+ Key *k;
+ Keyinfo ki;
+ Proto *p;
+ State *s;
+
+ s = fss->ps;
+ a = va;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "write");
+
+ case CNeedProtos:
+ if(n==0 || a[n-1] != '\0')
+ return toosmall(fss, 2048);
+ a = estrdup(a);
+ m = tokenize(a, token, nelem(token));
+ if(m > 0 && strncmp(token[0], "v.", 2) == 0){
+ s->version = atoi(token[0]+2);
+ if(s->version != 2){
+ free(a);
+ return failure(fss, "unknown version of p9any");
+ }
+ }
+
+ /*
+ * look for a key
+ */
+ anew = _delattr(_delattr(_copyattr(fss->attr), "proto"), "role");
+ anewsf = _delattr(_copyattr(anew), "user");
+ user = _strfindattr(anew, "user");
+ k = nil;
+ p = nil;
+ dom = nil;
+ for(i=(s->version==1?0:1); i<m; i++){
+ p = findneg(token[i]);
+ if(p == nil)
+ continue;
+ dom = getdom(token[i]);
+ ret = RpcFailure;
+ mkkeyinfo(&ki, fss, nil);
+ if(user==nil || strcmp(user, fss->sysuser)==0){
+ ki.attr = anewsf;
+ ki.user = nil;
+ ret = findkey(&k, &ki, "proto=%q dom=%q role=speakfor %s",
+ p->name, dom, p->keyprompt);
+ }
+ if(ret == RpcFailure){
+ ki.attr = anew;
+ ki.user = fss->sysuser;
+ ret = findkey(&k, &ki,
+ "proto=%q dom=%q role=client %s",
+ p->name, dom, p->keyprompt);
+ }
+ if(ret == RpcConfirm){
+ free(a);
+ return ret;
+ }
+ if(ret == RpcOk)
+ break;
+ }
+ _freeattr(anewsf);
+
+ /*
+ * no acceptable key, go through the proto@domains one at a time.
+ */
+ asking = 0;
+ if(k == nil){
+ while(!asking && s->keyasked < m){
+ p = findneg(token[s->keyasked]);
+ if(p == nil){
+ s->keyasked++;
+ continue;
+ }
+ dom = getdom(token[s->keyasked]);
+ mkkeyinfo(&ki, fss, nil);
+ ki.attr = anew;
+ ret = findkey(&k, &ki,
+ "proto=%q dom=%q role=client %s",
+ p->name, dom, p->keyprompt);
+ s->keyasked++;
+ if(ret == RpcNeedkey){
+ asking = 1;
+ break;
+ }
+ }
+ }
+ if(k == nil){
+ free(a);
+ _freeattr(anew);
+ if(asking)
+ return RpcNeedkey;
+ else if(s->keyasked)
+ return failure(fss, nil);
+ else
+ return failure(fss, Enegotiation);
+ }
+ s->subdom = s_copy(dom);
+ s->subproto = p;
+ free(a);
+ _freeattr(anew);
+ setupfss(fss, s, k);
+ closekey(k);
+ ret = (*s->subproto->init)(p, &s->subfss);
+ rpcstartlog(s->subfss.attr, &s->subfss, ret);
+ if(ret == RpcOk)
+ fss->phase = CHaveProto;
+ return passret(fss, s, ret);
+
+ case SNeedProto:
+ if(n==0 || a[n-1] != '\0')
+ return toosmall(fss, n+1);
+ a = estrdup(a);
+ m = tokenize(a, token, nelem(token));
+ if(m != 2){
+ free(a);
+ return failure(fss, Ebadarg);
+ }
+ p = findneg(token[0]);
+ if(p == nil){
+ free(a);
+ return failure(fss, Enegotiation);
+ }
+ attr = _delattr(_copyattr(fss->attr), "proto");
+ mkkeyinfo(&ki, fss, nil);
+ ki.attr = attr;
+ ki.user = nil;
+ ret = findkey(&k, &ki, "proto=%q dom=%q role=server", token[0], token[1]);
+ free(a);
+ _freeattr(attr);
+ if(ret == RpcConfirm)
+ return ret;
+ if(ret != RpcOk)
+ return failure(fss, Enegotiation);
+ s->subproto = p;
+ setupfss(fss, s, k);
+ closekey(k);
+ ret = (*s->subproto->init)(p, &s->subfss);
+ if(ret == RpcOk){
+ if(s->version == 1)
+ fss->phase = SRelay;
+ else
+ fss->phase = SHaveOK;
+ }
+ return passret(fss, s, ret);
+
+ case CNeedOK:
+ if(n < 3)
+ return toosmall(fss, 3);
+ if(strcmp("OK", a) != 0)
+ return failure(fss, "server gave up");
+ fss->phase = CRelay;
+ return RpcOk;
+
+ case CRelay:
+ case SRelay:
+ ophase = s->subfss.phase;
+ ret = (*s->subproto->write)(&s->subfss, va, n);
+ rpcrdwrlog(&s->subfss, "write", n, ophase, ret);
+ return passret(fss, s, ret);
+ }
+}
+
+Proto p9any =
+{
+.name= "p9any",
+.init= p9anyinit,
+.write= p9anywrite,
+.read= p9anyread,
+.close= p9anyclose,
+};
diff --git a/sys/src/cmd/auth/factotum/p9cr.c b/sys/src/cmd/auth/factotum/p9cr.c
new file mode 100755
index 000000000..ef37fcdaa
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/p9cr.c
@@ -0,0 +1,363 @@
+/*
+ * p9cr, vnc - textual challenge/response authentication
+ *
+ * Client protocol: [currently unimplemented]
+ * write challenge
+ * read response
+ *
+ * Server protocol:
+ * write user
+ * read challenge
+ * write response
+ */
+
+#include "dat.h"
+
+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;
+ Keyinfo ki;
+
+ if((iscli = isclient(_strfindattr(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, mkkeyinfo(&ki, 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 = _strfindattr(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 = _strfindattr(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, int before)
+{
+ uchar *p;
+ char *s;
+
+ k->priv = emalloc(8+1);
+ if(s = _strfindattr(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, before);
+}
+
+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){
+ if (s->key->successes == 0)
+ disablekey(s->key);
+ return failure(fss, Easproto);
+ }
+ s->key->successes++;
+ 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, _strfindattr(s->key->attr, "user"), sizeof(s->tr.hostid));
+ safecpy(s->tr.authdom, _strfindattr(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, _strfindattr(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,
+};
diff --git a/sys/src/cmd/auth/factotum/p9sk1.c b/sys/src/cmd/auth/factotum/p9sk1.c
new file mode 100755
index 000000000..f2df98866
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/p9sk1.c
@@ -0,0 +1,486 @@
+/*
+ * 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 "dat.h"
+
+struct State
+{
+ int vers;
+ Key *key;
+ Ticket t;
+ Ticketreq tr;
+ char cchal[CHALLEN];
+ char tbuf[TICKETLEN+AUTHENTLEN];
+ char authkey[DESKEYLEN];
+ uchar *secret;
+ int speakfor;
+};
+
+enum
+{
+ /* client phases */
+ CHaveChal,
+ CNeedTreq,
+ CHaveTicket,
+ CNeedAuth,
+
+ /* server phases */
+ SNeedChal,
+ SHaveTreq,
+ SNeedTicket,
+ SHaveAuth,
+
+ Maxphase,
+};
+
+static char *phasenames[Maxphase] =
+{
+[CHaveChal] "CHaveChal",
+[CNeedTreq] "CNeedTreq",
+[CHaveTicket] "CHaveTicket",
+[CNeedAuth] "CNeedAuth",
+
+[SNeedChal] "SNeedChal",
+[SHaveTreq] "SHaveTreq",
+[SNeedTicket] "SNeedTicket",
+[SHaveAuth] "SHaveAuth",
+};
+
+static int gettickets(State*, char*, char*);
+
+static int
+p9skinit(Proto *p, Fsstate *fss)
+{
+ State *s;
+ int iscli, ret;
+ Key *k;
+ Keyinfo ki;
+ Attr *attr;
+
+ if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
+ return failure(fss, nil);
+
+ s = emalloc(sizeof *s);
+ fss = fss;
+ fss->phasename = phasenames;
+ fss->maxphase = Maxphase;
+ if(p == &p9sk1)
+ s->vers = 1;
+ else if(p == &p9sk2)
+ s->vers = 2;
+ else
+ abort();
+ if(iscli){
+ switch(s->vers){
+ case 1:
+ fss->phase = CHaveChal;
+ memrandom(s->cchal, CHALLEN);
+ break;
+ case 2:
+ fss->phase = CNeedTreq;
+ break;
+ }
+ }else{
+ s->tr.type = AuthTreq;
+ attr = setattr(_copyattr(fss->attr), "proto=p9sk1");
+ mkkeyinfo(&ki, fss, attr);
+ ki.user = nil;
+ ret = findkey(&k, &ki, "user? dom?");
+ _freeattr(attr);
+ if(ret != RpcOk){
+ free(s);
+ return ret;
+ }
+ safecpy(s->tr.authid, _strfindattr(k->attr, "user"), sizeof(s->tr.authid));
+ safecpy(s->tr.authdom, _strfindattr(k->attr, "dom"), sizeof(s->tr.authdom));
+ s->key = k;
+ memrandom(s->tr.chal, sizeof s->tr.chal);
+ switch(s->vers){
+ case 1:
+ fss->phase = SNeedChal;
+ break;
+ case 2:
+ fss->phase = SHaveTreq;
+ memmove(s->cchal, s->tr.chal, CHALLEN);
+ break;
+ }
+ }
+ fss->ps = s;
+ return RpcOk;
+}
+
+static int
+p9skread(Fsstate *fss, void *a, uint *n)
+{
+ int m;
+ State *s;
+
+ s = fss->ps;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "read");
+
+ case CHaveChal:
+ m = CHALLEN;
+ if(*n < m)
+ return toosmall(fss, m);
+ *n = m;
+ memmove(a, s->cchal, m);
+ fss->phase = CNeedTreq;
+ return RpcOk;
+
+ case SHaveTreq:
+ m = TICKREQLEN;
+ if(*n < m)
+ return toosmall(fss, m);
+ *n = m;
+ convTR2M(&s->tr, a);
+ fss->phase = SNeedTicket;
+ return RpcOk;
+
+ case CHaveTicket:
+ m = TICKETLEN+AUTHENTLEN;
+ if(*n < m)
+ return toosmall(fss, m);
+ *n = m;
+ memmove(a, s->tbuf, m);
+ fss->phase = CNeedAuth;
+ return RpcOk;
+
+ case SHaveAuth:
+ m = AUTHENTLEN;
+ if(*n < m)
+ return toosmall(fss, m);
+ *n = m;
+ memmove(a, s->tbuf+TICKETLEN, m);
+ fss->ai.cuid = s->t.cuid;
+ fss->ai.suid = s->t.suid;
+ s->secret = emalloc(8);
+ des56to64((uchar*)s->t.key, s->secret);
+ fss->ai.secret = s->secret;
+ fss->ai.nsecret = 8;
+ fss->haveai = 1;
+ fss->phase = Established;
+ return RpcOk;
+ }
+}
+
+static int
+p9skwrite(Fsstate *fss, void *a, uint n)
+{
+ int m, ret, sret;
+ char tbuf[2*TICKETLEN], trbuf[TICKREQLEN], *user;
+ Attr *attr;
+ Authenticator auth;
+ State *s;
+ Key *srvkey;
+ Keyinfo ki;
+
+ s = fss->ps;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "write");
+
+ case SNeedChal:
+ m = CHALLEN;
+ if(n < m)
+ return toosmall(fss, m);
+ memmove(s->cchal, a, m);
+ fss->phase = SHaveTreq;
+ return RpcOk;
+
+ case CNeedTreq:
+ m = TICKREQLEN;
+ if(n < m)
+ return toosmall(fss, m);
+
+ /* remember server's chal */
+ convM2TR(a, &s->tr);
+ if(s->vers == 2)
+ memmove(s->cchal, s->tr.chal, CHALLEN);
+
+ if(s->key != nil)
+ closekey(s->key);
+
+ attr = _delattr(_delattr(_copyattr(fss->attr), "role"), "user");
+ attr = setattr(attr, "proto=p9sk1");
+ user = _strfindattr(fss->attr, "user");
+ /*
+ * If our client is the user who started factotum (client==owner), then
+ * he can use whatever keys we have to speak as whoever he pleases.
+ * If, on the other hand, we're speaking on behalf of someone else,
+ * we will only vouch for their name on the local system.
+ *
+ * We do the sysuser findkey second so that if we return RpcNeedkey,
+ * the correct key information gets asked for.
+ */
+ srvkey = nil;
+ s->speakfor = 0;
+ sret = RpcFailure;
+ if(user==nil || strcmp(user, fss->sysuser) == 0){
+ mkkeyinfo(&ki, fss, attr);
+ ki.user = nil;
+ sret = findkey(&srvkey, &ki,
+ "role=speakfor dom=%q user?", s->tr.authdom);
+ }
+ if(user != nil)
+ attr = setattr(attr, "user=%q", user);
+ mkkeyinfo(&ki, fss, attr);
+ ret = findkey(&s->key, &ki,
+ "role=client dom=%q %s", s->tr.authdom, p9sk1.keyprompt);
+ if(ret == RpcOk)
+ closekey(srvkey);
+ else if(sret == RpcOk){
+ s->key = srvkey;
+ s->speakfor = 1;
+ }else if(ret == RpcConfirm || sret == RpcConfirm){
+ _freeattr(attr);
+ return RpcConfirm;
+ }else{
+ _freeattr(attr);
+ return ret;
+ }
+
+ /* fill in the rest of the request */
+ s->tr.type = AuthTreq;
+ safecpy(s->tr.hostid, _strfindattr(s->key->attr, "user"), sizeof s->tr.hostid);
+ if(s->speakfor)
+ safecpy(s->tr.uid, fss->sysuser, sizeof s->tr.uid);
+ else
+ safecpy(s->tr.uid, s->tr.hostid, sizeof s->tr.uid);
+
+ convTR2M(&s->tr, trbuf);
+
+ /* get tickets, from auth server or invent if we can */
+ if(gettickets(s, trbuf, tbuf) < 0){
+ _freeattr(attr);
+ return failure(fss, nil);
+ }
+
+ convM2T(tbuf, &s->t, (char*)s->key->priv);
+ if(s->t.num != AuthTc){
+ if(s->key->successes == 0 && !s->speakfor)
+ disablekey(s->key);
+ if(askforkeys && !s->speakfor){
+ snprint(fss->keyinfo, sizeof fss->keyinfo,
+ "%A %s", attr, p9sk1.keyprompt);
+ _freeattr(attr);
+ return RpcNeedkey;
+ }else{
+ _freeattr(attr);
+ return failure(fss, Ebadkey);
+ }
+ }
+ s->key->successes++;
+ _freeattr(attr);
+ memmove(s->tbuf, tbuf+TICKETLEN, TICKETLEN);
+
+ auth.num = AuthAc;
+ memmove(auth.chal, s->tr.chal, CHALLEN);
+ auth.id = 0;
+ convA2M(&auth, s->tbuf+TICKETLEN, s->t.key);
+ fss->phase = CHaveTicket;
+ return RpcOk;
+
+ case SNeedTicket:
+ m = TICKETLEN+AUTHENTLEN;
+ if(n < m)
+ return toosmall(fss, m);
+ convM2T(a, &s->t, (char*)s->key->priv);
+ if(s->t.num != AuthTs
+ || memcmp(s->t.chal, s->tr.chal, CHALLEN) != 0)
+ return failure(fss, Easproto);
+ convM2A((char*)a+TICKETLEN, &auth, s->t.key);
+ if(auth.num != AuthAc
+ || memcmp(auth.chal, s->tr.chal, CHALLEN) != 0
+ || auth.id != 0)
+ return failure(fss, Easproto);
+ auth.num = AuthAs;
+ memmove(auth.chal, s->cchal, CHALLEN);
+ auth.id = 0;
+ convA2M(&auth, s->tbuf+TICKETLEN, s->t.key);
+ fss->phase = SHaveAuth;
+ return RpcOk;
+
+ case CNeedAuth:
+ m = AUTHENTLEN;
+ if(n < m)
+ return toosmall(fss, m);
+ convM2A(a, &auth, s->t.key);
+ if(auth.num != AuthAs
+ || memcmp(auth.chal, s->cchal, CHALLEN) != 0
+ || auth.id != 0)
+ return failure(fss, Easproto);
+ fss->ai.cuid = s->t.cuid;
+ fss->ai.suid = s->t.suid;
+ s->secret = emalloc(8);
+ des56to64((uchar*)s->t.key, s->secret);
+ fss->ai.secret = s->secret;
+ fss->ai.nsecret = 8;
+ fss->haveai = 1;
+ fss->phase = Established;
+ return RpcOk;
+ }
+}
+
+static void
+p9skclose(Fsstate *fss)
+{
+ State *s;
+
+ s = fss->ps;
+ if(s->secret != nil){
+ free(s->secret);
+ s->secret = nil;
+ }
+ if(s->key != nil){
+ closekey(s->key);
+ s->key = nil;
+ }
+ free(s);
+}
+
+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;
+}
+
+static int
+hexparse(char *hex, uchar *dat, int ndat)
+{
+ int i;
+
+ if(strlen(hex) != 2*ndat)
+ return -1;
+ if(hex[strspn(hex, "0123456789abcdefABCDEF")] != '\0')
+ return -1;
+ for(i=0; i<ndat; i++)
+ dat[i] = (unhex(hex[2*i])<<4)|unhex(hex[2*i+1]);
+ return 0;
+}
+
+static int
+p9skaddkey(Key *k, int before)
+{
+ char *s;
+
+ k->priv = emalloc(DESKEYLEN);
+ if(s = _strfindattr(k->privattr, "!hex")){
+ if(hexparse(s, k->priv, 7) < 0){
+ free(k->priv);
+ k->priv = nil;
+ werrstr("malformed key data");
+ return -1;
+ }
+ }else if(s = _strfindattr(k->privattr, "!password")){
+ passtokey((char*)k->priv, s);
+ }else{
+ werrstr("no key data");
+ free(k->priv);
+ k->priv = nil;
+ return -1;
+ }
+ return replacekey(k, before);
+}
+
+static void
+p9skclosekey(Key *k)
+{
+ free(k->priv);
+}
+
+static int
+getastickets(State *s, char *trbuf, char *tbuf)
+{
+ int asfd, rv;
+ char *dom;
+
+ if((dom = _strfindattr(s->key->attr, "dom")) == nil){
+ werrstr("auth key has no domain");
+ return -1;
+ }
+ asfd = _authdial(nil, dom);
+ if(asfd < 0)
+ return -1;
+ rv = _asgetticket(asfd, trbuf, tbuf);
+ close(asfd);
+ return rv;
+}
+
+static int
+mkserverticket(State *s, char *tbuf)
+{
+ Ticketreq *tr = &s->tr;
+ Ticket t;
+
+ if(strcmp(tr->authid, tr->hostid) != 0)
+ return -1;
+/* this keeps creating accounts on martha from working. -- presotto
+ if(strcmp(tr->uid, "none") == 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, tbuf, s->key->priv);
+ t.num = AuthTs;
+ convT2M(&t, tbuf+TICKETLEN, s->key->priv);
+ return 0;
+}
+
+static int
+gettickets(State *s, char *trbuf, char *tbuf)
+{
+/*
+ if(mktickets(s, trbuf, tbuf) >= 0)
+ return 0;
+*/
+ if(getastickets(s, trbuf, tbuf) >= 0)
+ return 0;
+ return mkserverticket(s, tbuf);
+}
+
+Proto p9sk1 = {
+.name= "p9sk1",
+.init= p9skinit,
+.write= p9skwrite,
+.read= p9skread,
+.close= p9skclose,
+.addkey= p9skaddkey,
+.closekey= p9skclosekey,
+.keyprompt= "user? !password?"
+};
+
+Proto p9sk2 = {
+.name= "p9sk2",
+.init= p9skinit,
+.write= p9skwrite,
+.read= p9skread,
+.close= p9skclose,
+};
+
diff --git a/sys/src/cmd/auth/factotum/pass.c b/sys/src/cmd/auth/factotum/pass.c
new file mode 100755
index 000000000..a9ee89fe0
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/pass.c
@@ -0,0 +1,99 @@
+/*
+ * 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 ret;
+ Key *k;
+ Keyinfo ki;
+ State *s;
+
+ ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", p->keyprompt);
+ if(ret != RpcOk)
+ return ret;
+ setattrs(fss->attr, k->attr);
+ s = emalloc(sizeof(*s));
+ s->key = k;
+ fss->ps = s;
+ fss->phase = HavePass;
+ 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?",
+};
diff --git a/sys/src/cmd/auth/factotum/rpc.c b/sys/src/cmd/auth/factotum/rpc.c
new file mode 100755
index 000000000..6945ef3ea
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/rpc.c
@@ -0,0 +1,508 @@
+/*
+ * 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).
+ *
+ * Return values are:
+ * error message - an error happened.
+ * ok [data] - success, possible data is request dependent.
+ * needkey proto domain user - request aborted, get me this key and try again
+ * badkey proto domain user - request aborted, this key might be bad
+ * done [haveai] - authentication is done [haveai: you can get an ai with authinfo]
+ * 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
+ */
+
+#include "dat.h"
+
+Req *rpcwait;
+
+typedef struct Verb Verb;
+struct Verb {
+ char *verb;
+ int iverb;
+};
+
+enum {
+ Vunknown = -1,
+ Vauthinfo = 0,
+ Vread,
+ Vstart,
+ Vwrite,
+ Vattr,
+};
+
+Verb rpctab[] = {
+ "authinfo", Vauthinfo,
+ "read", Vread,
+ "start", Vstart,
+ "write", Vwrite,
+ "attr", Vattr,
+};
+
+static int
+classify(char *s, Verb *verbtab, int nverbtab)
+{
+ int i;
+
+ for(i=0; i<nverbtab; i++)
+ if(strcmp(s, verbtab[i].verb) == 0)
+ return verbtab[i].iverb;
+ return Vunknown;
+}
+
+void
+rpcwrite(Req *r)
+{
+ Fsstate *fss;
+
+ if(r->ifcall.count >= Maxrpc){
+ respond(r, Etoolarge);
+ return;
+ }
+ fss = r->fid->aux;
+ if(fss->pending){
+ respond(r, "rpc already pending; read to clear");
+ return;
+ }
+ memmove(fss->rpc.buf, r->ifcall.data, r->ifcall.count);
+ fss->rpc.buf[r->ifcall.count] = '\0';
+ fss->rpc.verb = fss->rpc.buf;
+ if(fss->rpc.arg = strchr(fss->rpc.buf, ' ')){
+ *fss->rpc.arg++ = '\0';
+ fss->rpc.narg = r->ifcall.count - (fss->rpc.arg - fss->rpc.buf);
+ }else{
+ fss->rpc.arg = "";
+ fss->rpc.narg = 0;
+ }
+ fss->rpc.iverb = classify(fss->rpc.verb, rpctab, nelem(rpctab));
+ r->ofcall.count = r->ifcall.count;
+ fss->pending = 1;
+ respond(r, nil);
+}
+
+static void
+retstring(Req *r, Fsstate *fss, char *s)
+{
+ int n;
+
+ n = strlen(s);
+ if(n > r->ifcall.count)
+ n = r->ifcall.count;
+ memmove(r->ofcall.data, s, n);
+ r->ofcall.count = n;
+ fss->pending = 0;
+ respond(r, nil);
+ return;
+}
+
+static void
+retrpc(Req *r, int ret, Fsstate *fss)
+{
+ switch(ret){
+ default:
+ snprint(fss->rpc.buf, Maxrpc, "internal error %d", ret);
+ retstring(r, fss, fss->rpc.buf);
+ return;
+ case RpcErrstr:
+ snprint(fss->rpc.buf, Maxrpc, "error %r");
+ retstring(r, fss, fss->rpc.buf);
+ return;
+ case RpcFailure:
+ snprint(fss->rpc.buf, Maxrpc, "error %s", fss->err);
+ retstring(r, fss, fss->rpc.buf);
+ return;
+ case RpcNeedkey:
+ if(needkeyqueue(r, fss) < 0){
+ snprint(fss->rpc.buf, Maxrpc, "needkey %s", fss->keyinfo);
+ retstring(r, fss, fss->rpc.buf);
+ }
+ return;
+ case RpcOk:
+ retstring(r, fss, "ok");
+ return;
+ case RpcToosmall:
+ snprint(fss->rpc.buf, Maxrpc, "toosmall %d", fss->rpc.nwant);
+ retstring(r, fss, fss->rpc.buf);
+ return;
+ case RpcPhase:
+ snprint(fss->rpc.buf, Maxrpc, "phase %r");
+ retstring(r, fss, fss->rpc.buf);
+ return;
+ case RpcConfirm:
+ confirmqueue(r, fss);
+ return;
+ }
+}
+
+int
+rdwrcheck(Req *r, Fsstate *fss)
+{
+ if(fss->ps == nil){
+ retstring(r, fss, "error no current protocol");
+ return -1;
+ }
+ if(fss->phase == Notstarted){
+ retstring(r, fss, "protocol not started");
+ return -1;
+ }
+ if(fss->phase == Broken){
+ snprint(fss->rpc.buf, Maxrpc, "error %s", fss->err);
+ retstring(r, fss, fss->rpc.buf);
+ return -1;
+ }
+ if(fss->phase == Established){
+ if(fss->haveai)
+ retstring(r, fss, "done haveai");
+ else
+ retstring(r, fss, "done");
+ return -1;
+ }
+ return 0;
+}
+
+static void
+logret(char *pre, Fsstate *fss, int ret)
+{
+ switch(ret){
+ default:
+ flog("%s: code %d", pre, ret);
+ break;
+ case RpcErrstr:
+ flog("%s: error %r", pre);
+ break;
+ case RpcFailure:
+ flog("%s: failure %s", pre, fss->err);
+ break;
+ case RpcNeedkey:
+ flog("%s: needkey %s", pre, fss->keyinfo);
+ break;
+ case RpcOk:
+ flog("%s: ok", pre);
+ break;
+ case RpcToosmall:
+ flog("%s: toosmall %d", pre, fss->rpc.nwant);
+ break;
+ case RpcPhase:
+ flog("%s: phase: %r", pre);
+ break;
+ case RpcConfirm:
+ flog("%s: waiting for confirm", pre);
+ break;
+ }
+}
+
+void
+rpcrdwrlog(Fsstate *fss, char *rdwr, uint n, int ophase, int ret)
+{
+ char buf0[40], buf1[40], pre[300];
+
+ if(!debug)
+ return;
+ snprint(pre, sizeof pre, "%d: %s %ud in phase %s yields phase %s",
+ fss->seqnum, rdwr, n, phasename(fss, ophase, buf0), phasename(fss, fss->phase, buf1));
+ logret(pre, fss, ret);
+}
+
+void
+rpcstartlog(Attr *attr, Fsstate *fss, int ret)
+{
+ char pre[300], tmp[40];
+
+ if(!debug)
+ return;
+ snprint(pre, sizeof pre, "%d: start %A yields phase %s", fss->seqnum,
+ attr, phasename(fss, fss->phase, tmp));
+ logret(pre, fss, ret);
+}
+
+int seqnum;
+
+void
+rpcread(Req *r)
+{
+ Attr *attr;
+ char *p;
+ int ophase, ret;
+ uchar *e;
+ uint count;
+ Fsstate *fss;
+ Proto *proto;
+
+ if(r->ifcall.count < 64){
+ respond(r, "rpc read too small");
+ return;
+ }
+ fss = r->fid->aux;
+ if(!fss->pending){
+ respond(r, "no rpc pending");
+ return;
+ }
+ switch(fss->rpc.iverb){
+ default:
+ case Vunknown:
+ retstring(r, fss, "error unknown verb");
+ break;
+
+ case Vstart:
+ if(fss->phase != Notstarted){
+ flog("%d: implicit close due to second start; old attr '%A'", fss->seqnum, fss->attr);
+ if(fss->proto && fss->ps)
+ (*fss->proto->close)(fss);
+ fss->ps = nil;
+ fss->proto = nil;
+ _freeattr(fss->attr);
+ fss->attr = nil;
+ fss->phase = Notstarted;
+ }
+ attr = _parseattr(fss->rpc.arg);
+ if((p = _strfindattr(attr, "proto")) == nil){
+ retstring(r, fss, "error did not specify proto");
+ _freeattr(attr);
+ break;
+ }
+ if((proto = findproto(p)) == nil){
+ snprint(fss->rpc.buf, Maxrpc, "error unknown protocol %q", p);
+ retstring(r, fss, fss->rpc.buf);
+ _freeattr(attr);
+ break;
+ }
+ fss->attr = attr;
+ fss->proto = proto;
+ fss->seqnum = ++seqnum;
+ ret = (*proto->init)(proto, fss);
+ rpcstartlog(attr, fss, ret);
+ if(ret != RpcOk){
+ _freeattr(fss->attr);
+ fss->attr = nil;
+ fss->phase = Notstarted;
+ }
+ retrpc(r, ret, fss);
+ break;
+
+ case Vread:
+ if(fss->rpc.arg && fss->rpc.arg[0]){
+ retstring(r, fss, "error read needs no parameters");
+ break;
+ }
+ if(rdwrcheck(r, fss) < 0)
+ break;
+ count = r->ifcall.count - 3;
+ ophase = fss->phase;
+ ret = fss->proto->read(fss, (uchar*)r->ofcall.data+3, &count);
+ rpcrdwrlog(fss, "read", count, ophase, ret);
+ if(ret == RpcOk){
+ memmove(r->ofcall.data, "ok ", 3);
+ if(count == 0)
+ r->ofcall.count = 2;
+ else
+ r->ofcall.count = 3+count;
+ fss->pending = 0;
+ respond(r, nil);
+ }else
+ retrpc(r, ret, fss);
+ break;
+
+ case Vwrite:
+ if(rdwrcheck(r, fss) < 0)
+ break;
+ ophase = fss->phase;
+ ret = fss->proto->write(fss, fss->rpc.arg, fss->rpc.narg);
+ rpcrdwrlog(fss, "write", fss->rpc.narg, ophase, ret);
+ retrpc(r, ret, fss);
+ break;
+
+ case Vauthinfo:
+ if(fss->phase != Established){
+ retstring(r, fss, "error authentication unfinished");
+ break;
+ }
+ if(!fss->haveai){
+ retstring(r, fss, "error no authinfo available");
+ break;
+ }
+ memmove(r->ofcall.data, "ok ", 3);
+ fss->ai.cap = mkcap(r->fid->uid, fss->ai.suid);
+ e = convAI2M(&fss->ai, (uchar*)r->ofcall.data+3, r->ifcall.count-3);
+ free(fss->ai.cap);
+ fss->ai.cap = nil;
+ if(e == nil){
+ retstring(r, fss, "error read too small");
+ break;
+ }
+ r->ofcall.count = e - (uchar*)r->ofcall.data;
+ fss->pending = 0;
+ respond(r, nil);
+ break;
+
+ case Vattr:
+ snprint(fss->rpc.buf, Maxrpc, "ok %A", fss->attr);
+ retstring(r, fss, fss->rpc.buf);
+ break;
+ }
+}
+
+enum {
+ Vdelkey,
+ Vaddkey,
+ Vdebug,
+};
+
+Verb ctltab[] = {
+ "delkey", Vdelkey,
+ "key", Vaddkey,
+ "debug", Vdebug,
+};
+
+/*
+ * 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?]
+ */
+
+int
+ctlwrite(char *a, int atzero)
+{
+ 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, ctltab, nelem(ctltab))){
+ default:
+ case Vunknown:
+ werrstr("unknown verb");
+ return -1;
+ case Vdebug:
+ debug ^= 1;
+ return 0;
+ case Vdelkey:
+ 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++;
+ closekey(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 Vaddkey:
+ 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 = findproto(pa->val)) == nil){
+ werrstr("unknown proto %s", pa->val);
+ ret = -1;
+ continue;
+ }
+ if(proto->addkey == nil){
+ werrstr("proto %s doesn't take 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->addkey(k, atzero) < 0){
+ ret = -1;
+ closekey(k);
+ continue;
+ }
+ closekey(k);
+ }
+ _freeattr(attr);
+ _freeattr(priv);
+ _freeattr(protos);
+ return ret;
+ }
+}
diff --git a/sys/src/cmd/auth/factotum/rsa.c b/sys/src/cmd/auth/factotum/rsa.c
new file mode 100755
index 000000000..4be923202
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/rsa.c
@@ -0,0 +1,187 @@
+/*
+ * 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
+rsainit(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, "rsa server unimplemented");
+
+ s = emalloc(sizeof *s);
+ fss->phasename = phasenames;
+ fss->maxphase = Maxphase;
+ fss->phase = CHavePub;
+ fss->ps = s;
+ return RpcOk;
+}
+
+static int
+rsaread(Fsstate *fss, void *va, uint *n)
+{
+ RSApriv *priv;
+ State *s;
+ Keyinfo ki;
+
+ s = fss->ps;
+ switch(fss->phase){
+ default:
+ return phaseerror(fss, "read");
+ case CHavePub:
+ if(s->key){
+ closekey(s->key);
+ s->key = nil;
+ }
+ mkkeyinfo(&ki, fss, nil);
+ ki.skip = s->off;
+ ki.noconf = 1;
+ if(findkey(&s->key, &ki, nil) != RpcOk)
+ 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
+rsawrite(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");
+ switch(canusekey(fss, s->key)){
+ case -1:
+ return RpcConfirm;
+ case 0:
+ return failure(fss, "confirmation denied");
+ case 1:
+ break;
+ }
+ m = strtomp(va, nil, 16, nil);
+ if(m == nil)
+ return failure(fss, "invalid challenge value");
+ m = rsadecrypt(s->key->priv, m, m);
+ s->resp = m;
+ fss->phase = CHaveResp;
+ return RpcOk;
+ }
+}
+
+static void
+rsaclose(Fsstate *fss)
+{
+ State *s;
+
+ s = fss->ps;
+ if(s->key)
+ closekey(s->key);
+ if(s->resp)
+ mpfree(s->resp);
+ free(s);
+}
+
+static int
+rsaaddkey(Key *k, int before)
+{
+ fmtinstall('B', mpfmt);
+
+ if((k->priv = readrsapriv(k)) == nil){
+ werrstr("malformed key data");
+ return -1;
+ }
+ return replacekey(k, before);
+}
+
+static void
+rsaclosekey(Key *k)
+{
+ rsaprivfree(k->priv);
+}
+
+Proto rsa = {
+.name= "rsa",
+.init= rsainit,
+.write= rsawrite,
+.read= rsaread,
+.close= rsaclose,
+.addkey= rsaaddkey,
+.closekey= rsaclosekey,
+};
diff --git a/sys/src/cmd/auth/factotum/secstore.c b/sys/src/cmd/auth/factotum/secstore.c
new file mode 100755
index 000000000..1fe2ec48f
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/secstore.c
@@ -0,0 +1,628 @@
+/*
+ * Various files from /sys/src/cmd/auth/secstore, just enough
+ * to download a file at boot time.
+ */
+
+#include "dat.h"
+#include <ip.h>
+
+enum{ CHK = 16};
+enum{ MAXFILESIZE = 10*1024*1024 };
+
+enum{// PW status bits
+ Enabled = (1<<0),
+ STA = (1<<1), // extra SecurID step
+};
+
+static char testmess[] = "__secstore\tPAK\nC=%s\nm=0\n";
+
+int
+havesecstore(void)
+{
+ int m, n, fd;
+ uchar buf[500];
+
+ n = snprint((char*)buf, sizeof buf, testmess, owner);
+ hnputs(buf, 0x8000+n-2);
+
+ fd = secdial();
+ if(fd < 0)
+ return 0;
+ if(write(fd, buf, n) != n || readn(fd, buf, 2) != 2){
+ close(fd);
+ return 0;
+ }
+ n = ((buf[0]&0x7f)<<8) + buf[1];
+ if(n+1 > sizeof buf){
+ werrstr("implausibly large count %d", n);
+ close(fd);
+ return 0;
+ }
+ m = readn(fd, buf, n);
+ close(fd);
+ if(m != n){
+ if(m >= 0)
+ werrstr("short read from secstore");
+ return 0;
+ }
+ buf[n] = 0;
+ if(strcmp((char*)buf, "!account expired") == 0){
+ werrstr("account expired");
+ return 0;
+ }
+ return strcmp((char*)buf, "!account exists") == 0;
+}
+
+// delimited, authenticated, encrypted connection
+enum{ Maxmsg=4096 }; // messages > Maxmsg bytes are truncated
+typedef struct SConn SConn;
+
+extern SConn* newSConn(int); // arg is open file descriptor
+struct SConn{
+ void *chan;
+ int secretlen;
+ int (*secret)(SConn*, uchar*, int);//
+ int (*read)(SConn*, uchar*, int); // <0 if error; errmess in buffer
+ int (*write)(SConn*, uchar*, int);
+ void (*free)(SConn*); // also closes file descriptor
+};
+// secret(s,b,dir) sets secret for digest, encrypt, using the secretlen
+// bytes in b to form keys for the two directions;
+// set dir=0 in client, dir=1 in server
+
+// error convention: write !message in-band
+#define readstr secstore_readstr
+static void writerr(SConn*, char*);
+static int readstr(SConn*, char*); // call with buf of size Maxmsg+1
+ // returns -1 upon error, with error message in buf
+
+typedef struct ConnState {
+ uchar secret[SHA1dlen];
+ ulong seqno;
+ RC4state rc4;
+} ConnState;
+
+typedef struct SS{
+ int fd; // file descriptor for read/write of encrypted data
+ int alg; // if nonzero, "alg sha rc4_128"
+ ConnState in, out;
+} SS;
+
+static int
+SC_secret(SConn *conn, uchar *sigma, int direction)
+{
+ SS *ss = (SS*)(conn->chan);
+ int nsigma = conn->secretlen;
+
+ if(direction != 0){
+ hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->out.secret, nil);
+ hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->in.secret, nil);
+ }else{
+ hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->out.secret, nil);
+ hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->in.secret, nil);
+ }
+ setupRC4state(&ss->in.rc4, ss->in.secret, 16); // restrict to 128 bits
+ setupRC4state(&ss->out.rc4, ss->out.secret, 16);
+ ss->alg = 1;
+ return 0;
+}
+
+static void
+hash(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen])
+{
+ DigestState sha;
+ uchar seq[4];
+
+ seq[0] = seqno>>24;
+ seq[1] = seqno>>16;
+ seq[2] = seqno>>8;
+ seq[3] = seqno;
+ memset(&sha, 0, sizeof sha);
+ sha1(secret, SHA1dlen, nil, &sha);
+ sha1(data, len, nil, &sha);
+ sha1(seq, 4, d, &sha);
+}
+
+static int
+verify(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen])
+{
+ DigestState sha;
+ uchar seq[4];
+ uchar digest[SHA1dlen];
+
+ seq[0] = seqno>>24;
+ seq[1] = seqno>>16;
+ seq[2] = seqno>>8;
+ seq[3] = seqno;
+ memset(&sha, 0, sizeof sha);
+ sha1(secret, SHA1dlen, nil, &sha);
+ sha1(data, len, nil, &sha);
+ sha1(seq, 4, digest, &sha);
+ return memcmp(d, digest, SHA1dlen);
+}
+
+static int
+SC_read(SConn *conn, uchar *buf, int n)
+{
+ SS *ss = (SS*)(conn->chan);
+ uchar count[2], digest[SHA1dlen];
+ int len, nr;
+
+ if(read(ss->fd, count, 2) != 2 || count[0]&0x80 == 0){
+ werrstr("!SC_read invalid count");
+ return -1;
+ }
+ len = (count[0]&0x7f)<<8 | count[1]; // SSL-style count; no pad
+ if(ss->alg){
+ len -= SHA1dlen;
+ if(len <= 0 || readn(ss->fd, digest, SHA1dlen) != SHA1dlen){
+ werrstr("!SC_read missing sha1");
+ return -1;
+ }
+ if(len > n || readn(ss->fd, buf, len) != len){
+ werrstr("!SC_read missing data");
+ return -1;
+ }
+ rc4(&ss->in.rc4, digest, SHA1dlen);
+ rc4(&ss->in.rc4, buf, len);
+ if(verify(ss->in.secret, buf, len, ss->in.seqno, digest) != 0){
+ werrstr("!SC_read integrity check failed");
+ return -1;
+ }
+ }else{
+ if(len <= 0 || len > n){
+ werrstr("!SC_read implausible record length");
+ return -1;
+ }
+ if( (nr = readn(ss->fd, buf, len)) != len){
+ werrstr("!SC_read expected %d bytes, but got %d", len, nr);
+ return -1;
+ }
+ }
+ ss->in.seqno++;
+ return len;
+}
+
+static int
+SC_write(SConn *conn, uchar *buf, int n)
+{
+ SS *ss = (SS*)(conn->chan);
+ uchar count[2], digest[SHA1dlen], enc[Maxmsg+1];
+ int len;
+
+ if(n <= 0 || n > Maxmsg+1){
+ werrstr("!SC_write invalid n %d", n);
+ return -1;
+ }
+ len = n;
+ if(ss->alg)
+ len += SHA1dlen;
+ count[0] = 0x80 | len>>8;
+ count[1] = len;
+ if(write(ss->fd, count, 2) != 2){
+ werrstr("!SC_write invalid count");
+ return -1;
+ }
+ if(ss->alg){
+ hash(ss->out.secret, buf, n, ss->out.seqno, digest);
+ rc4(&ss->out.rc4, digest, SHA1dlen);
+ memcpy(enc, buf, n);
+ rc4(&ss->out.rc4, enc, n);
+ if(write(ss->fd, digest, SHA1dlen) != SHA1dlen ||
+ write(ss->fd, enc, n) != n){
+ werrstr("!SC_write error on send");
+ return -1;
+ }
+ }else{
+ if(write(ss->fd, buf, n) != n){
+ werrstr("!SC_write error on send");
+ return -1;
+ }
+ }
+ ss->out.seqno++;
+ return n;
+}
+
+static void
+SC_free(SConn *conn)
+{
+ SS *ss = (SS*)(conn->chan);
+
+ close(ss->fd);
+ free(ss);
+ free(conn);
+}
+
+SConn*
+newSConn(int fd)
+{
+ SS *ss;
+ SConn *conn;
+
+ if(fd < 0)
+ return nil;
+ ss = (SS*)emalloc(sizeof(*ss));
+ conn = (SConn*)emalloc(sizeof(*conn));
+ ss->fd = fd;
+ ss->alg = 0;
+ conn->chan = (void*)ss;
+ conn->secretlen = SHA1dlen;
+ conn->free = SC_free;
+ conn->secret = SC_secret;
+ conn->read = SC_read;
+ conn->write = SC_write;
+ return conn;
+}
+
+static void
+writerr(SConn *conn, char *s)
+{
+ char buf[Maxmsg];
+
+ snprint(buf, Maxmsg, "!%s", s);
+ conn->write(conn, (uchar*)buf, strlen(buf));
+}
+
+static int
+readstr(SConn *conn, char *s)
+{
+ int n;
+
+ n = conn->read(conn, (uchar*)s, Maxmsg);
+ if(n >= 0){
+ s[n] = 0;
+ if(s[0] == '!'){
+ memmove(s, s+1, n);
+ n = -1;
+ }
+ }else{
+ strcpy(s, "read error");
+ }
+ return n;
+}
+
+static int
+getfile(SConn *conn, uchar *key, int nkey)
+{
+ char *buf;
+ int nbuf, n, nr, len;
+ char s[Maxmsg+1], *gf, *p, *q;
+ uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw;
+ AESstate aes;
+ DigestState *sha;
+
+ gf = "factotum";
+ memset(&aes, 0, sizeof aes);
+
+ snprint(s, Maxmsg, "GET %s\n", gf);
+ conn->write(conn, (uchar*)s, strlen(s));
+
+ /* get file size */
+ s[0] = '\0';
+ if(readstr(conn, s) < 0){
+ werrstr("secstore: %r");
+ return -1;
+ }
+ if((len = atoi(s)) < 0){
+ werrstr("secstore: remote file %s does not exist", gf);
+ return -1;
+ }else if(len > MAXFILESIZE){//assert
+ werrstr("secstore: implausible file size %d for %s", len, gf);
+ return -1;
+ }
+
+ ibr = ibw = ib;
+ buf = nil;
+ nbuf = 0;
+ for(nr=0; nr < len;){
+ if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
+ werrstr("secstore: empty file chunk n=%d nr=%d len=%d: %r", n, nr, len);
+ return -1;
+ }
+ nr += n;
+ ibw += n;
+ if(!aes.setup){ /* first time, read 16 byte IV */
+ if(n < 16){
+ werrstr("secstore: no IV in file");
+ return -1;
+ }
+ sha = sha1((uchar*)"aescbc file", 11, nil, nil);
+ sha1(key, nkey, skey, sha);
+ setupAESstate(&aes, skey, AESbsize, ibr);
+ memset(skey, 0, sizeof skey);
+ ibr += AESbsize;
+ n -= AESbsize;
+ }
+ aesCBCdecrypt(ibw-n, n, &aes);
+ n = ibw-ibr-CHK;
+ if(n > 0){
+ buf = erealloc(buf, nbuf+n+1);
+ memmove(buf+nbuf, ibr, n);
+ nbuf += n;
+ ibr += n;
+ }
+ memmove(ib, ibr, ibw-ibr);
+ ibw = ib + (ibw-ibr);
+ ibr = ib;
+ }
+ n = ibw-ibr;
+ if((n != CHK) || (memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0)){
+ werrstr("secstore: decrypted file failed to authenticate!");
+ free(buf);
+ return -1;
+ }
+ if(nbuf == 0){
+ werrstr("secstore got empty file");
+ return -1;
+ }
+ buf[nbuf] = '\0';
+ p = buf;
+ n = 0;
+ while(p){
+ if(q = strchr(p, '\n'))
+ *q++ = '\0';
+ n++;
+ if(ctlwrite(p, 0) < 0)
+ fprint(2, "factotum: secstore(%s) line %d: %r\n", gf, n);
+ p = q;
+ }
+ free(buf);
+ return 0;
+}
+
+static char VERSION[] = "secstore";
+
+typedef struct PAKparams{
+ mpint *q, *p, *r, *g;
+} PAKparams;
+
+static PAKparams *pak;
+
+// This group was generated by the seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E.
+static void
+initPAKparams(void)
+{
+ if(pak)
+ return;
+ pak = (PAKparams*)emalloc(sizeof(*pak));
+ pak->q = strtomp("E0F0EF284E10796C5A2A511E94748BA03C795C13", nil, 16, nil);
+ pak->p = strtomp("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBBD"
+ "B12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86"
+ "3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9"
+ "3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", nil, 16, nil);
+ pak->r = strtomp("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241CEF"
+ "2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E887"
+ "D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D21"
+ "C4656848614D888A4", nil, 16, nil);
+ pak->g = strtomp("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D2327173444"
+ "ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD41"
+ "0E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734E3E"
+ "2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", nil, 16, nil);
+}
+
+// H = (sha(ver,C,sha(passphrase)))^r mod p,
+// a hash function expensive to attack by brute force.
+static void
+longhash(char *ver, char *C, uchar *passwd, mpint *H)
+{
+ uchar *Cp;
+ int i, n, nver, nC;
+ uchar buf[140], key[1];
+
+ nver = strlen(ver);
+ nC = strlen(C);
+ n = nver + nC + SHA1dlen;
+ Cp = (uchar*)emalloc(n);
+ memmove(Cp, ver, nver);
+ memmove(Cp+nver, C, nC);
+ memmove(Cp+nver+nC, passwd, SHA1dlen);
+ for(i = 0; i < 7; i++){
+ key[0] = 'A'+i;
+ hmac_sha1(Cp, n, key, sizeof key, buf+i*SHA1dlen, nil);
+ }
+ memset(Cp, 0, n);
+ free(Cp);
+ betomp(buf, sizeof buf, H);
+ mpmod(H, pak->p, H);
+ mpexp(H, pak->r, pak->p, H);
+}
+
+// Hi = H^-1 mod p
+static char *
+PAK_Hi(char *C, char *passphrase, mpint *H, mpint *Hi)
+{
+ uchar passhash[SHA1dlen];
+
+ sha1((uchar *)passphrase, strlen(passphrase), passhash, nil);
+ initPAKparams();
+ longhash(VERSION, C, passhash, H);
+ mpinvert(H, pak->p, Hi);
+ return mptoa(Hi, 64, nil, 0);
+}
+
+// another, faster, hash function for each party to
+// confirm that the other has the right secrets.
+static void
+shorthash(char *mess, char *C, char *S, char *m, char *mu, char *sigma, char *Hi, uchar *digest)
+{
+ SHA1state *state;
+
+ state = sha1((uchar*)mess, strlen(mess), 0, 0);
+ state = sha1((uchar*)C, strlen(C), 0, state);
+ state = sha1((uchar*)S, strlen(S), 0, state);
+ state = sha1((uchar*)m, strlen(m), 0, state);
+ state = sha1((uchar*)mu, strlen(mu), 0, state);
+ state = sha1((uchar*)sigma, strlen(sigma), 0, state);
+ state = sha1((uchar*)Hi, strlen(Hi), 0, state);
+ state = sha1((uchar*)mess, strlen(mess), 0, state);
+ state = sha1((uchar*)C, strlen(C), 0, state);
+ state = sha1((uchar*)S, strlen(S), 0, state);
+ state = sha1((uchar*)m, strlen(m), 0, state);
+ state = sha1((uchar*)mu, strlen(mu), 0, state);
+ state = sha1((uchar*)sigma, strlen(sigma), 0, state);
+ sha1((uchar*)Hi, strlen(Hi), digest, state);
+}
+
+// On input, conn provides an open channel to the server;
+// C is the name this client calls itself;
+// pass is the user's passphrase
+// On output, session secret has been set in conn
+// (unless return code is negative, which means failure).
+// If pS is not nil, it is set to the (alloc'd) name the server calls itself.
+static int
+PAKclient(SConn *conn, char *C, char *pass, char **pS)
+{
+ char *mess, *mess2, *eol, *S, *hexmu, *ks, *hexm, *hexsigma = nil, *hexHi;
+ char kc[2*SHA1dlen+1];
+ uchar digest[SHA1dlen];
+ int rc = -1, n;
+ mpint *x, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0);
+ mpint *H = mpnew(0), *Hi = mpnew(0);
+
+ hexHi = PAK_Hi(C, pass, H, Hi);
+
+ // random 1<=x<=q-1; send C, m=g**x H
+ x = mprand(164, genrandom, nil);
+ mpmod(x, pak->q, x);
+ if(mpcmp(x, mpzero) == 0)
+ mpassign(mpone, x);
+ mpexp(pak->g, x, pak->p, m);
+ mpmul(m, H, m);
+ mpmod(m, pak->p, m);
+ hexm = mptoa(m, 64, nil, 0);
+ mess = (char*)emalloc(2*Maxmsg+2);
+ mess2 = mess+Maxmsg+1;
+ snprint(mess, Maxmsg, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm);
+ conn->write(conn, (uchar*)mess, strlen(mess));
+
+ // recv g**y, S, check hash1(g**xy)
+ if(readstr(conn, mess) < 0){
+ fprint(2, "factotum: error: %s\n", mess);
+ writerr(conn, "couldn't read g**y");
+ goto done;
+ }
+ eol = strchr(mess, '\n');
+ if(strncmp("mu=", mess, 3) != 0 || !eol || strncmp("\nk=", eol, 3) != 0){
+ writerr(conn, "verifier syntax error");
+ goto done;
+ }
+ hexmu = mess+3;
+ *eol = 0;
+ ks = eol+3;
+ eol = strchr(ks, '\n');
+ if(!eol || strncmp("\nS=", eol, 3) != 0){
+ writerr(conn, "verifier syntax error for secstore 1.0");
+ goto done;
+ }
+ *eol = 0;
+ S = eol+3;
+ eol = strchr(S, '\n');
+ if(!eol){
+ writerr(conn, "verifier syntax error for secstore 1.0");
+ goto done;
+ }
+ *eol = 0;
+ if(pS)
+ *pS = estrdup(S);
+ strtomp(hexmu, nil, 64, mu);
+ mpexp(mu, x, pak->p, sigma);
+ hexsigma = mptoa(sigma, 64, nil, 0);
+ shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ enc64(kc, sizeof kc, digest, SHA1dlen);
+ if(strcmp(ks, kc) != 0){
+ writerr(conn, "verifier didn't match");
+ goto done;
+ }
+
+ // send hash2(g**xy)
+ shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ enc64(kc, sizeof kc, digest, SHA1dlen);
+ snprint(mess2, Maxmsg, "k'=%s\n", kc);
+ conn->write(conn, (uchar*)mess2, strlen(mess2));
+
+ // set session key
+ shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ memset(hexsigma, 0, strlen(hexsigma));
+ n = conn->secret(conn, digest, 0);
+ memset(digest, 0, SHA1dlen);
+ if(n < 0){//assert
+ writerr(conn, "can't set secret");
+ goto done;
+ }
+
+ rc = 0;
+done:
+ mpfree(x);
+ mpfree(sigma);
+ mpfree(mu);
+ mpfree(m);
+ mpfree(Hi);
+ mpfree(H);
+ free(hexsigma);
+ free(hexHi);
+ free(hexm);
+ free(mess);
+ return rc;
+}
+
+int
+secstorefetch(char *password)
+{
+ int rv = -1, fd;
+ char s[Maxmsg+1];
+ SConn *conn;
+ char *pass, *sta;
+
+ sta = nil;
+ conn = nil;
+ if(password != nil && *password)
+ pass = estrdup(password);
+ else
+ pass = readcons("secstore password", nil, 1);
+ if(pass==nil || strlen(pass)==0){
+ werrstr("cancel");
+ goto Out;
+ }
+ if((fd = secdial()) < 0)
+ goto Out;
+ if((conn = newSConn(fd)) == nil)
+ goto Out;
+ if(PAKclient(conn, owner, pass, nil) < 0){
+ werrstr("password mistyped?");
+ goto Out;
+ }
+ if(readstr(conn, s) < 0)
+ goto Out;
+ if(strcmp(s, "STA") == 0){
+ sta = readcons("STA PIN+SecureID", nil, 1);
+ if(sta==nil || strlen(sta)==0){
+ werrstr("cancel");
+ goto Out;
+ }
+ if(strlen(sta) >= sizeof s - 3){
+ werrstr("STA response too long");
+ goto Out;
+ }
+ strcpy(s+3, sta);
+ conn->write(conn, (uchar*)s, strlen(s));
+ readstr(conn, s);
+ }
+ if(strcmp(s, "OK") !=0){
+ werrstr("%s", s);
+ goto Out;
+ }
+ if(getfile(conn, (uchar*)pass, strlen(pass)) < 0)
+ goto Out;
+ conn->write(conn, (uchar*)"BYE", 3);
+ rv = 0;
+
+Out:
+ if(conn)
+ conn->free(conn);
+ if(pass)
+ free(pass);
+ if(sta)
+ free(sta);
+ return rv;
+}
+
diff --git a/sys/src/cmd/auth/factotum/sshrsa.c b/sys/src/cmd/auth/factotum/sshrsa.c
new file mode 100755
index 000000000..edaa3334b
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/sshrsa.c
@@ -0,0 +1,180 @@
+/*
+ * 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(findkey(&s->key, fss, fss->sysuser, 1, s->off, fss->attr, nil) != RpcOk)
+ 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");
+ switch(canusekey(fss, s->key)){
+ case -1:
+ return RpcConfirm;
+ case 0:
+ return failure(fss, "confirmation denied");
+ case 1:
+ break;
+ }
+ 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, int before)
+{
+ fmtinstall('B', mpfmt);
+
+ if((k->priv = readrsapriv(k)) == nil){
+ werrstr("malformed key data");
+ return -1;
+ }
+ return replacekey(k, before);
+}
+
+static void
+sshrsaclosekey(Key *k)
+{
+ rsaprivfree(k->priv);
+}
+
+Proto sshrsa = {
+.name= "sshrsa",
+.init= sshrsainit,
+.write= sshrsawrite,
+.read= sshrsaread,
+.close= sshrsaclose,
+.addkey= sshrsaaddkey,
+.closekey= sshrsaclosekey,
+};
diff --git a/sys/src/cmd/auth/factotum/util.c b/sys/src/cmd/auth/factotum/util.c
new file mode 100755
index 000000000..7fb26e86f
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/util.c
@@ -0,0 +1,1002 @@
+#include "dat.h"
+
+static char secstore[100]; /* server name */
+
+/* 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){
+ if(mount(srvfd, -1, "/net", MBEFORE, "") >= 0)
+ return 0;
+ close(srvfd);
+ }
+ return -1;
+ }
+ return 0;
+}
+
+int
+_authdial(char *net, char *authdom)
+{
+ int fd, vanilla;
+
+ vanilla = net==nil || strcmp(net, "/net")==0;
+
+ if(!vanilla || bindnetcs()>=0)
+ return authdial(net, authdom);
+
+ /*
+ * If we failed to mount /srv/cs, assume that
+ * we're still bootstrapping the system and dial
+ * the one auth server passed to us on the command line.
+ * In normal operation, it is important *not* to do this,
+ * because the bootstrap auth server is only good for
+ * a single auth domain.
+ *
+ * The ticket request code should really check the
+ * remote authentication domain too.
+ */
+
+ /* use the auth server passed to us as an arg */
+ if(authaddr == nil)
+ return -1;
+ fd = dial(netmkaddr(authaddr, "tcp", "567"), 0, 0, 0);
+ if(fd >= 0)
+ return fd;
+ return dial(netmkaddr(authaddr, "il", "566"), 0, 0, 0);
+}
+
+int
+secdial(void)
+{
+ char *p, buf[80], *f[3];
+ int fd, nf;
+
+ p = secstore; /* take it from writehostowner, if set there */
+ if(*p == 0) /* else use the authserver */
+ p = "$auth";
+
+ if(bindnetcs() >= 0)
+ return dial(netmkaddr(p, "net", "secstore"), 0, 0, 0);
+
+ /* translate $auth ourselves.
+ * authaddr is something like il!host!566 or tcp!host!567.
+ * extract host, accounting for a change of format to something
+ * like il!host or tcp!host or host.
+ */
+ if(strcmp(p, "$auth")==0){
+ if(authaddr == nil)
+ return -1;
+ safecpy(buf, authaddr, sizeof buf);
+ nf = getfields(buf, f, nelem(f), 0, "!");
+ switch(nf){
+ default:
+ return -1;
+ case 1:
+ p = f[0];
+ break;
+ case 2:
+ case 3:
+ p = f[1];
+ break;
+ }
+ }
+ fd = dial(netmkaddr(p, "tcp", "5356"), 0, 0, 0);
+ if(fd >= 0)
+ return fd;
+ return -1;
+}
+/*
+ * prompt user for a key. don't care about memory leaks, runs standalone
+ */
+static Attr*
+promptforkey(char *params)
+{
+ char *v;
+ int fd;
+ Attr *a, *attr;
+ char *def;
+
+ fd = open("/dev/cons", ORDWR);
+ if(fd < 0)
+ sysfatal("opening /dev/cons: %r");
+
+ attr = _parseattr(params);
+ fprint(fd, "\n!Adding key:");
+ for(a=attr; a; a=a->next)
+ if(a->type != AttrQuery && 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;
+}
+
+/* askuser */
+void
+askuser(char *params)
+{
+ Attr *attr;
+
+ attr = promptforkey(params);
+ if(attr == nil)
+ sysfatal("no key supplied");
+ if(sendkey(attr) < 0)
+ sysfatal("sending key to factotum: %r");
+}
+
+ulong conftaggen;
+int
+canusekey(Fsstate *fss, Key *k)
+{
+ int i;
+
+ if(_strfindattr(k->attr, "confirm")){
+ for(i=0; i<fss->nconf; i++)
+ if(fss->conf[i].key == k)
+ return fss->conf[i].canuse;
+ if(fss->nconf%16 == 0)
+ fss->conf = erealloc(fss->conf, (fss->nconf+16)*(sizeof(fss->conf[0])));
+ fss->conf[fss->nconf].key = k;
+ k->ref++;
+ fss->conf[fss->nconf].canuse = -1;
+ fss->conf[fss->nconf].tag = conftaggen++;
+ fss->nconf++;
+ return -1;
+ }
+ return 1;
+}
+
+/* closekey */
+void
+closekey(Key *k)
+{
+ if(k == nil)
+ return;
+ if(--k->ref != 0)
+ return;
+ if(k->proto && k->proto->closekey)
+ (*k->proto->closekey)(k);
+ _freeattr(k->attr);
+ _freeattr(k->privattr);
+ k->attr = (void*)~1;
+ k->privattr = (void*)~1;
+ k->proto = nil;
+ free(k);
+}
+
+static uchar*
+pstring(uchar *p, uchar *e, char *s)
+{
+ uint n;
+
+ if(p == nil)
+ return nil;
+ if(s == nil)
+ s = "";
+ n = strlen(s);
+ if(p+n+BIT16SZ >= e)
+ return nil;
+ PBIT16(p, n);
+ p += BIT16SZ;
+ memmove(p, s, n);
+ p += n;
+ return p;
+}
+
+static uchar*
+pcarray(uchar *p, uchar *e, uchar *s, uint n)
+{
+ if(p == nil)
+ return nil;
+ if(s == nil){
+ if(n > 0)
+ sysfatal("pcarray");
+ s = (uchar*)"";
+ }
+ if(p+n+BIT16SZ >= e)
+ return nil;
+ PBIT16(p, n);
+ p += BIT16SZ;
+ memmove(p, s, n);
+ p += n;
+ return p;
+}
+
+uchar*
+convAI2M(AuthInfo *ai, uchar *p, int n)
+{
+ uchar *e = p+n;
+
+ p = pstring(p, e, ai->cuid);
+ p = pstring(p, e, ai->suid);
+ p = pstring(p, e, ai->cap);
+ p = pcarray(p, e, ai->secret, ai->nsecret);
+ return p;
+}
+
+int
+failure(Fsstate *s, char *fmt, ...)
+{
+ char e[ERRMAX];
+ va_list arg;
+
+ if(fmt == nil)
+ rerrstr(s->err, sizeof(s->err));
+ else {
+ va_start(arg, fmt);
+ vsnprint(e, sizeof e, fmt, arg);
+ va_end(arg);
+ strecpy(s->err, s->err+sizeof(s->err), e);
+ werrstr(e);
+ }
+ flog("%d: failure %s", s->seqnum, s->err);
+ return RpcFailure;
+}
+
+static int
+hasqueries(Attr *a)
+{
+ for(; a; a=a->next)
+ if(a->type == AttrQuery)
+ return 1;
+ return 0;
+}
+
+char *ignored[] = {
+ "role",
+ "disabled",
+};
+
+static int
+ignoreattr(char *s)
+{
+ int i;
+
+ for(i=0; i<nelem(ignored); i++)
+ if(strcmp(ignored[i], s)==0)
+ return 1;
+ return 0;
+}
+
+Keyinfo*
+mkkeyinfo(Keyinfo *k, Fsstate *fss, Attr *attr)
+{
+ memset(k, 0, sizeof *k);
+ k->fss = fss;
+ k->user = fss->sysuser;
+ if(attr)
+ k->attr = attr;
+ else
+ k->attr = fss->attr;
+ return k;
+}
+
+int
+findkey(Key **ret, Keyinfo *ki, char *fmt, ...)
+{
+ int i, s, nmatch;
+ char buf[1024], *p, *who;
+ va_list arg;
+ Attr *a, *attr0, *attr1, *attr2, *attr3, **l;
+ Key *k;
+
+ *ret = nil;
+
+ who = ki->user;
+ attr0 = ki->attr;
+ if(fmt){
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof buf, fmt, arg);
+ va_end(arg);
+ attr1 = _parseattr(buf);
+ }else
+ attr1 = nil;
+
+ if(who && strcmp(who, owner) == 0)
+ who = nil;
+
+ if(who){
+ snprint(buf, sizeof buf, "owner=%q", who);
+ attr2 = _parseattr(buf);
+ attr3 = _parseattr("owner=*");
+ }else
+ attr2 = attr3 = nil;
+
+ p = _strfindattr(attr0, "proto");
+ if(p == nil)
+ p = _strfindattr(attr1, "proto");
+ if(p && findproto(p) == nil){
+ werrstr("unknown protocol %s", p);
+ _freeattr(attr1);
+ _freeattr(attr2);
+ _freeattr(attr3);
+ return failure(ki->fss, nil);
+ }
+
+ nmatch = 0;
+ for(i=0; i<ring->nkey; i++){
+ k = ring->key[i];
+ if(_strfindattr(k->attr, "disabled") && !ki->usedisabled)
+ continue;
+ if(matchattr(attr0, k->attr, k->privattr) && matchattr(attr1, k->attr, k->privattr)){
+ /* check ownership */
+ if(!matchattr(attr2, k->attr, nil) && !matchattr(attr3, k->attr, nil))
+ continue;
+ if(nmatch++ < ki->skip)
+ continue;
+ if(!ki->noconf){
+ switch(canusekey(ki->fss, k)){
+ case -1:
+ _freeattr(attr1);
+ return RpcConfirm;
+ case 0:
+ continue;
+ case 1:
+ break;
+ }
+ }
+ _freeattr(attr1);
+ _freeattr(attr2);
+ _freeattr(attr3);
+ k->ref++;
+ *ret = k;
+ return RpcOk;
+ }
+ }
+ flog("%d: no key matches %A %A %A %A", ki->fss->seqnum, attr0, attr1, attr2, attr3);
+ werrstr("no key matches %A %A", attr0, attr1);
+ _freeattr(attr2);
+ _freeattr(attr3);
+ s = RpcFailure;
+ if(askforkeys && who==nil && (hasqueries(attr0) || hasqueries(attr1))){
+ if(nmatch == 0){
+ attr0 = _copyattr(attr0);
+ for(l=&attr0; *l; l=&(*l)->next)
+ ;
+ *l = attr1;
+ for(l=&attr0; *l; ){
+ if(ignoreattr((*l)->name)){
+ a = *l;
+ *l = (*l)->next;
+ a->next = nil;
+ _freeattr(a);
+ }else
+ l = &(*l)->next;
+ }
+ attr0 = sortattr(attr0);
+ snprint(ki->fss->keyinfo, sizeof ki->fss->keyinfo, "%A", attr0);
+ _freeattr(attr0);
+ attr1 = nil; /* attr1 was linked to attr0 */
+ }else
+ ki->fss->keyinfo[0] = '\0';
+ s = RpcNeedkey;
+ }
+ _freeattr(attr1);
+ if(s == RpcFailure)
+ return failure(ki->fss, nil); /* loads error string */
+ return s;
+}
+
+int
+findp9authkey(Key **k, Fsstate *fss)
+{
+ char *dom;
+ Keyinfo ki;
+
+ /*
+ * We don't use fss->attr here because we don't
+ * care about what the user name is set to, for instance.
+ */
+ mkkeyinfo(&ki, fss, nil);
+ ki.attr = nil;
+ ki.user = nil;
+ if(dom = _strfindattr(fss->attr, "dom"))
+ return findkey(k, &ki, "proto=p9sk1 dom=%q role=server user?", dom);
+ else
+ return findkey(k, &ki, "proto=p9sk1 role=server dom? user?");
+}
+
+Proto*
+findproto(char *name)
+{
+ int i;
+
+ for(i=0; prototab[i]; i++)
+ if(strcmp(name, prototab[i]->name) == 0)
+ return prototab[i];
+ return nil;
+}
+
+char*
+getnvramkey(int flag, char **secstorepw)
+{
+ char *s;
+ Nvrsafe safe;
+ char spw[CONFIGLEN+1];
+ int i;
+
+ memset(&safe, 0, sizeof safe);
+ /*
+ * readnvram can return -1 meaning nvram wasn't written,
+ * but safe still holds good data.
+ */
+ if(readnvram(&safe, flag)<0 && safe.authid[0]==0)
+ return nil;
+
+ /*
+ * we're using the config area to hold the secstore
+ * password. if there's anything there, return it.
+ */
+ memmove(spw, safe.config, CONFIGLEN);
+ spw[CONFIGLEN] = 0;
+ if(spw[0] != 0)
+ *secstorepw = estrdup(spw);
+
+ /*
+ * only use nvram key if it is non-zero
+ */
+ for(i = 0; i < DESKEYLEN; i++)
+ if(safe.machkey[i] != 0)
+ break;
+ if(i == DESKEYLEN)
+ return nil;
+
+ s = emalloc(512);
+ fmtinstall('H', encodefmt);
+ sprint(s, "key proto=p9sk1 user=%q dom=%q !hex=%.*H !password=______",
+ safe.authid, safe.authdom, DESKEYLEN, safe.machkey);
+ writehostowner(safe.authid);
+
+ return s;
+}
+
+int
+isclient(char *role)
+{
+ if(role == nil){
+ werrstr("role not specified");
+ return -1;
+ }
+ if(strcmp(role, "server") == 0)
+ return 0;
+ if(strcmp(role, "client") == 0)
+ return 1;
+ werrstr("unknown role %q", role);
+ return -1;
+}
+
+static int
+hasname(Attr *a0, Attr *a1, char *name)
+{
+ return _findattr(a0, name) || _findattr(a1, name);
+}
+
+static int
+hasnameval(Attr *a0, Attr *a1, char *name, char *val)
+{
+ Attr *a;
+
+ for(a=_findattr(a0, name); a; a=_findattr(a->next, name))
+ if(strcmp(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;
+}
+
+void
+memrandom(void *p, int n)
+{
+ uchar *cp;
+
+ for(cp = (uchar*)p; n > 0; n--)
+ *cp++ = fastrand();
+}
+
+/*
+ * keep caphash fd open since opens of it could be disabled
+ */
+static int caphashfd;
+
+void
+initcap(void)
+{
+ caphashfd = open("#¤/caphash", OWRITE);
+// if(caphashfd < 0)
+// fprint(2, "%s: opening #¤/caphash: %r\n", argv0);
+}
+
+/*
+ * create a change uid capability
+ */
+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;
+}
+
+int
+phaseerror(Fsstate *s, char *op)
+{
+ char tmp[32];
+
+ werrstr("protocol phase error: %s in state %s", op, phasename(s, s->phase, tmp));
+ return RpcPhase;
+}
+
+char*
+phasename(Fsstate *fss, int phase, char *tmp)
+{
+ char *name;
+
+ if(fss->phase == Broken)
+ name = "Broken";
+ else if(phase == Established)
+ name = "Established";
+ else if(phase == Notstarted)
+ name = "Notstarted";
+ else if(phase < 0 || phase >= fss->maxphase
+ || (name = fss->phasename[phase]) == nil){
+ sprint(tmp, "%d", phase);
+ name = tmp;
+ }
+ return name;
+}
+
+static int
+outin(char *prompt, char *def, int len)
+{
+ char *s;
+
+ s = readcons(prompt, def, 0);
+ if(s == nil)
+ return -1;
+ if(s == nil)
+ sysfatal("s==nil???");
+ strncpy(def, s, len);
+ def[len-1] = 0;
+ free(s);
+ return strlen(def);
+}
+
+/*
+ * get host owner and set it
+ */
+void
+promptforhostowner(void)
+{
+ char owner[64], *p;
+
+ /* hack for bitsy; can't prompt during boot */
+ if(p = getenv("user")){
+ writehostowner(p);
+ free(p);
+ return;
+ }
+ free(p);
+
+ strcpy(owner, "none");
+ do{
+ outin("user", owner, sizeof(owner));
+ } while(*owner == 0);
+ writehostowner(owner);
+}
+
+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;
+}
+
+
+/*
+ * 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]);
+ }
+ }
+}
+
+/*
+ * Insert a key into the keyring.
+ * If the public attributes are identical to some other key, replace that one.
+ */
+int
+replacekey(Key *kn, int before)
+{
+ int i;
+ Key *k;
+
+ for(i=0; i<ring->nkey; i++){
+ k = ring->key[i];
+ if(matchattr(kn->attr, k->attr, nil) && matchattr(k->attr, kn->attr, nil)){
+ closekey(k);
+ kn->ref++;
+ ring->key[i] = kn;
+ return 0;
+ }
+ }
+ if(ring->nkey%16 == 0)
+ ring->key = erealloc(ring->key, (ring->nkey+16)*sizeof(ring->key[0]));
+ kn->ref++;
+ if(before){
+ memmove(ring->key+1, ring->key, ring->nkey*sizeof ring->key[0]);
+ ring->key[0] = kn;
+ ring->nkey++;
+ }else
+ ring->key[ring->nkey++] = kn;
+ return 0;
+}
+
+char*
+safecpy(char *to, char *from, int n)
+{
+ memset(to, 0, n);
+ if(n == 1)
+ return to;
+ if(from==nil)
+ sysfatal("safecpy called with from==nil, pc=%#p",
+ getcallerpc(&to));
+ strncpy(to, from, n-1);
+ return to;
+}
+
+Attr*
+setattr(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 = setattrs(a, b);
+ setmalloctag(a, getcallerpc(&a));
+ _freeattr(b);
+ return a;
+}
+
+/*
+ * add attributes in list b to list a. If any attributes are in
+ * both lists, replace those in a by those in b.
+ */
+Attr*
+setattrs(Attr *a, Attr *b)
+{
+ int found;
+ Attr **l, *freea;
+
+ for(; b; b=b->next){
+ found = 0;
+ for(l=&a; *l; ){
+ if(strcmp(b->name, (*l)->name) == 0){
+ switch(b->type){
+ case AttrNameval:
+ if(!found){
+ found = 1;
+ free((*l)->val);
+ (*l)->val = estrdup(b->val);
+ (*l)->type = AttrNameval;
+ l = &(*l)->next;
+ }else{
+ freea = *l;
+ *l = (*l)->next;
+ freea->next = nil;
+ _freeattr(freea);
+ }
+ break;
+ case AttrQuery:
+ goto continue2;
+ }
+ }else
+ l = &(*l)->next;
+ }
+ if(found == 0){
+ *l = _mkattr(b->type, b->name, b->val, nil);
+ setmalloctag(*l, getcallerpc(&a));
+ }
+continue2:;
+ }
+ 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
+toosmall(Fsstate *fss, uint n)
+{
+ fss->rpc.nwant = n;
+ return RpcToosmall;
+}
+
+void
+writehostowner(char *owner)
+{
+ int fd;
+ char *s;
+
+ if((s = strchr(owner,'@')) != nil){
+ *s++ = 0;
+ strncpy(secstore, s, (sizeof secstore)-1);
+ }
+ fd = open("#c/hostowner", OWRITE);
+ if(fd >= 0){
+ if(fprint(fd, "%s", owner) < 0)
+ fprint(2, "factotum: setting #c/hostowner to %q: %r\n",
+ owner);
+ close(fd);
+ }
+}
+
+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);
+}
+
+void
+disablekey(Key *k)
+{
+ Attr *a;
+
+ if(sflag) /* not on servers */
+ return;
+ for(a=k->attr; a; a=a->next){
+ if(a->type==AttrNameval && strcmp(a->name, "disabled") == 0)
+ return;
+ if(a->next == nil)
+ break;
+ }
+ if(a)
+ a->next = _mkattr(AttrNameval, "disabled", "by.factotum", nil);
+ else
+ k->attr = _mkattr(AttrNameval, "disabled", "by.factotum", nil); /* not reached: always a proto attribute */
+}
diff --git a/sys/src/cmd/auth/factotum/wep.c b/sys/src/cmd/auth/factotum/wep.c
new file mode 100755
index 000000000..6efcf5daa
--- /dev/null
+++ b/sys/src/cmd/auth/factotum/wep.c
@@ -0,0 +1,128 @@
+/*
+ * The caller supplies the device, we do the flavoring. There
+ * are no phases, everything happens in the init routine.
+ */
+
+#include "dat.h"
+
+typedef struct State State;
+struct State
+{
+ Key *key;
+};
+
+enum
+{
+ HavePass,
+};
+
+static int
+wepinit(Proto*, Fsstate *fss)
+{
+ int ret;
+ Key *k;
+ Keyinfo ki;
+ State *s;
+
+ /* find a key with at least one password */
+ mkkeyinfo(&ki, fss, nil);
+ ret = findkey(&k, &ki, "!key1?");
+ if(ret != RpcOk)
+ ret = findkey(&k, &ki, "!key2?");
+ if(ret != RpcOk)
+ ret = findkey(&k, &ki, "!key3?");
+ if(ret != RpcOk)
+ return ret;
+
+ setattrs(fss->attr, k->attr);
+ s = emalloc(sizeof(*s));
+ s->key = k;
+ fss->ps = s;
+ fss->phase = HavePass;
+
+ return RpcOk;
+}
+
+static void
+wepclose(Fsstate *fss)
+{
+ State *s;
+
+ s = fss->ps;
+ if(s->key)
+ closekey(s->key);
+ free(s);
+}
+
+static int
+wepread(Fsstate *fss, void*, uint*)
+{
+ return phaseerror(fss, "read");
+}
+
+static int
+wepwrite(Fsstate *fss, void *va, uint n)
+{
+ char *data = va;
+ State *s;
+ char dev[64];
+ int fd, cfd;
+ int rv;
+ char *p;
+
+ /* get the device */
+ if(n > sizeof(dev)-5){
+ werrstr("device too long");
+ return RpcErrstr;
+ }
+ memmove(dev, data, n);
+ dev[n] = 0;
+ s = fss->ps;
+
+ /* legal? */
+ if(dev[0] != '#' || dev[1] != 'l'){
+ werrstr("%s not an ether device", dev);
+ return RpcErrstr;
+ }
+ strcat(dev, "!0");
+ fd = dial(dev, 0, 0, &cfd);
+ if(fd < 0)
+ return RpcErrstr;
+
+ /* flavor it with passwords, essid, and turn on wep */
+ rv = RpcErrstr;
+ p = _strfindattr(s->key->privattr, "!key1");
+ if(p != nil)
+ if(fprint(cfd, "key1 %s", p) < 0)
+ goto out;
+ p = _strfindattr(s->key->privattr, "!key2");
+ if(p != nil)
+ if(fprint(cfd, "key2 %s", p) < 0)
+ goto out;
+ p = _strfindattr(s->key->privattr, "!key3");
+ if(p != nil)
+ if(fprint(cfd, "key3 %s", p) < 0)
+ goto out;
+ p = _strfindattr(fss->attr, "essid");
+ if(p != nil)
+ if(fprint(cfd, "essid %s", p) < 0)
+ goto out;
+ if(fprint(cfd, "crypt on") < 0)
+ goto out;
+ rv = RpcOk;
+out:
+ close(fd);
+ close(cfd);
+ return rv;
+}
+
+Proto wep =
+{
+.name= "wep",
+.init= wepinit,
+.write= wepwrite,
+.read= wepread,
+.close= wepclose,
+.addkey= replacekey,
+.keyprompt= "!key1? !key2? !key3? essid?",
+};
diff --git a/sys/src/cmd/auth/guard.srv.c b/sys/src/cmd/auth/guard.srv.c
new file mode 100755
index 000000000..ffe902e5f
--- /dev/null
+++ b/sys/src/cmd/auth/guard.srv.c
@@ -0,0 +1,156 @@
+/*
+ * guard service
+ */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ndb.h>
+#include <authsrv.h>
+#include "authcmdlib.h"
+
+enum {
+ Pinlen = 4,
+};
+
+/*
+ * c -> a client
+ * a -> c challenge prompt
+ * c -> a KC'{challenge}
+ * a -> c OK or NO
+ */
+
+void catchalarm(void*, char*);
+void getraddr(char*);
+
+char user[ANAMELEN];
+char raddr[128];
+int debug;
+Ndb *db;
+
+void
+main(int argc, char *argv[])
+{
+ int n;
+ long chal;
+ char *err;
+ char ukey[DESKEYLEN], resp[32], buf[NETCHLEN];
+ Ndb *db2;
+
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ break;
+ }ARGEND;
+
+ db = ndbopen("/lib/ndb/auth");
+ if(db == 0)
+ syslog(0, AUTHLOG, "no /lib/ndb/auth");
+ db2 = ndbopen(0);
+ if(db2 == 0)
+ syslog(0, AUTHLOG, "no /lib/ndb/local");
+ db = ndbcat(db, db2);
+ werrstr("");
+
+ strcpy(raddr, "unknown");
+ if(argc >= 1)
+ getraddr(argv[argc-1]);
+
+ argv0 = "guard";
+ srand((getpid()*1103515245)^time(0));
+ notify(catchalarm);
+
+ /*
+ * read the host and client and get their keys
+ */
+ if(readarg(0, user, sizeof user) < 0)
+ fail(0);
+
+ /*
+ * challenge-response
+ */
+ chal = lnrand(MAXNETCHAL);
+ sprint(buf, "challenge: %lud\nresponse: ", chal);
+ n = strlen(buf) + 1;
+ if(write(1, buf, n) != n){
+ if(debug)
+ syslog(0, AUTHLOG, "g-fail %s@%s: %r sending chal",
+ user, raddr);
+ exits("replying to server");
+ }
+ alarm(3*60*1000);
+ werrstr("");
+ if(readarg(0, resp, sizeof resp) < 0){
+ if(debug)
+ syslog(0, AUTHLOG, "g-fail %s@%s: %r reading resp",
+ user, raddr);
+ fail(0);
+ }
+ alarm(0);
+
+ /* remove password login from guard.research.bell-labs.com, sucre, etc. */
+// if(!findkey(KEYDB, user, ukey) || !netcheck(ukey, chal, resp))
+ if(!findkey(NETKEYDB, user, ukey) || !netcheck(ukey, chal, resp))
+ if((err = secureidcheck(user, resp)) != nil){
+ print("NO %s", err);
+ write(1, "NO", 2);
+ if(debug) {
+ char *r;
+
+ /*
+ * don't log the entire response, since the first
+ * Pinlen digits may be the user's secure-id pin.
+ */
+ if (strlen(resp) < Pinlen)
+ r = strdup("<too short for pin>");
+ else if (strlen(resp) == Pinlen)
+ r = strdup("<pin only>");
+ else
+ r = smprint("%.*s%s", Pinlen,
+ "******************", resp + Pinlen);
+ syslog(0, AUTHLOG,
+ "g-fail %s@%s: %s: resp %s to chal %lud",
+ user, raddr, err, r, chal);
+ free(r);
+ }
+ fail(user);
+ }
+ write(1, "OK", 2);
+ if(debug)
+ syslog(0, AUTHLOG, "g-ok %s@%s", user, raddr);
+ succeed(user);
+ exits(0);
+}
+
+void
+catchalarm(void *x, char *msg)
+{
+ USED(x, msg);
+ if(debug)
+ syslog(0, AUTHLOG, "g-timed out %s", raddr);
+ fail(0);
+}
+
+void
+getraddr(char *dir)
+{
+ int n, fd;
+ char *cp;
+ char file[128];
+
+ snprint(file, sizeof(file), "%s/remote", dir);
+ fd = open(file, OREAD);
+ if(fd < 0)
+ return;
+ n = read(fd, raddr, sizeof(raddr)-1);
+ close(fd);
+ if(n <= 0)
+ return;
+ raddr[n] = 0;
+ cp = strchr(raddr, '\n');
+ if(cp)
+ *cp = 0;
+ cp = strchr(raddr, '!');
+ if(cp)
+ *cp = 0;
+}
diff --git a/sys/src/cmd/auth/keyfs.c b/sys/src/cmd/auth/keyfs.c
new file mode 100755
index 000000000..61df8d61b
--- /dev/null
+++ b/sys/src/cmd/auth/keyfs.c
@@ -0,0 +1,1113 @@
+/*
+ * keyfs
+ */
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <authsrv.h>
+#include <fcall.h>
+#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
+#include "authcmdlib.h"
+
+#pragma varargck type "W" char*
+
+char authkey[8];
+
+typedef struct Fid Fid;
+typedef struct User User;
+
+enum {
+ Qroot,
+ Quser,
+ Qkey,
+ Qsecret,
+ Qlog,
+ Qstatus,
+ Qexpire,
+ Qwarnings,
+ Qmax,
+
+ Nuser = 512,
+ MAXBAD = 10, /* max # of bad attempts before disabling the account */
+ /* file must be randomly addressible, so names have fixed length */
+ Namelen = ANAMELEN,
+};
+
+enum {
+ Sok,
+ Sdisabled,
+ Smax,
+};
+
+struct Fid {
+ int fid;
+ ulong qtype;
+ User *user;
+ int busy;
+ Fid *next;
+};
+
+struct User {
+ char *name;
+ char key[DESKEYLEN];
+ char secret[SECRETLEN];
+ ulong expire; /* 0 == never */
+ uchar status;
+ ulong bad; /* # of consecutive bad authentication attempts */
+ int ref;
+ char removed;
+ uchar warnings;
+ long purgatory; /* time purgatory ends */
+ ulong uniq;
+ User *link;
+};
+
+char *qinfo[Qmax] = {
+ [Qroot] "keys",
+ [Quser] ".",
+ [Qkey] "key",
+ [Qsecret] "secret",
+ [Qlog] "log",
+ [Qexpire] "expire",
+ [Qstatus] "status",
+ [Qwarnings] "warnings",
+};
+
+char *status[Smax] = {
+ [Sok] "ok",
+ [Sdisabled] "disabled",
+};
+
+Fid *fids;
+User *users[Nuser];
+char *userkeys;
+int nuser;
+ulong uniq = 1;
+Fcall rhdr,
+ thdr;
+int usepass;
+char *warnarg;
+uchar mdata[8192 + IOHDRSZ];
+int messagesize = sizeof mdata;
+
+int readusers(void);
+ulong hash(char*);
+Fid *findfid(int);
+User *finduser(char*);
+User *installuser(char*);
+int removeuser(User*);
+void insertuser(User*);
+void writeusers(void);
+void io(int, int);
+void *emalloc(ulong);
+Qid mkqid(User*, ulong);
+int dostat(User*, ulong, void*, int);
+int newkeys(void);
+void warning(void);
+int weirdfmt(Fmt *f);
+
+char *Auth(Fid*), *Attach(Fid*), *Version(Fid*),
+ *Flush(Fid*), *Walk(Fid*),
+ *Open(Fid*), *Create(Fid*),
+ *Read(Fid *), *Write(Fid*), *Clunk(Fid*),
+ *Remove(Fid *), *Stat(Fid*), *Wstat(Fid*);
+char *(*fcalls[])(Fid*) = {
+ [Tattach] Attach,
+ [Tauth] Auth,
+ [Tclunk] Clunk,
+ [Tcreate] Create,
+ [Tflush] Flush,
+ [Topen] Open,
+ [Tread] Read,
+ [Tremove] Remove,
+ [Tstat] Stat,
+ [Tversion] Version,
+ [Twalk] Walk,
+ [Twrite] Write,
+ [Twstat] Wstat,
+};
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-p] [-m mtpt] [-w warn] [keyfile]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ char *mntpt;
+ int p[2];
+
+ fmtinstall('W', weirdfmt);
+ mntpt = "/mnt/keys";
+ ARGBEGIN{
+ case 'm':
+ mntpt = EARGF(usage());
+ break;
+ case 'p':
+ usepass = 1;
+ break;
+ case 'w':
+ warnarg = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+ argv0 = "keyfs";
+
+ userkeys = "/adm/keys";
+ if(argc > 1)
+ usage();
+ if(argc == 1)
+ userkeys = argv[0];
+
+ if(pipe(p) < 0)
+ error("can't make pipe: %r");
+
+ if(usepass) {
+ getpass(authkey, nil, 0, 0);
+ } else {
+ if(!getauthkey(authkey))
+ print("keyfs: warning: can't read NVRAM\n");
+ }
+
+ switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG)){
+ case 0:
+ close(p[0]);
+ io(p[1], p[1]);
+ exits(0);
+ case -1:
+ error("fork");
+ default:
+ close(p[1]);
+ if(mount(p[0], -1, mntpt, MREPL|MCREATE, "") < 0)
+ error("can't mount: %r");
+ exits(0);
+ }
+}
+
+char *
+Flush(Fid *f)
+{
+ USED(f);
+ return 0;
+}
+
+char *
+Auth(Fid *)
+{
+ return "keyfs: authentication not required";
+}
+
+char *
+Attach(Fid *f)
+{
+ if(f->busy)
+ Clunk(f);
+ f->user = 0;
+ f->qtype = Qroot;
+ f->busy = 1;
+ thdr.qid = mkqid(f->user, f->qtype);
+ return 0;
+}
+
+char*
+Version(Fid*)
+{
+ Fid *f;
+
+ for(f = fids; f; f = f->next)
+ if(f->busy)
+ Clunk(f);
+ if(rhdr.msize > sizeof mdata)
+ thdr.msize = sizeof mdata;
+ else
+ thdr.msize = rhdr.msize;
+ messagesize = thdr.msize;
+ if(strncmp(rhdr.version, "9P2000", 6) != 0)
+ return "bad 9P version";
+ thdr.version = "9P2000";
+ return 0;
+}
+
+char *
+Walk(Fid *f)
+{
+ char *name, *err;
+ int i, j, max;
+ Fid *nf;
+ ulong qtype;
+ User *user;
+
+ if(!f->busy)
+ return "walk of unused fid";
+ nf = nil;
+ qtype = f->qtype;
+ user = f->user;
+ if(rhdr.fid != rhdr.newfid){
+ nf = findfid(rhdr.newfid);
+ if(nf->busy)
+ return "fid in use";
+ f = nf; /* walk f */
+ }
+
+ err = nil;
+ i = 0;
+ if(rhdr.nwname > 0){
+ for(; i<rhdr.nwname; i++){
+ if(i >= MAXWELEM){
+ err = "too many path name elements";
+ break;
+ }
+ name = rhdr.wname[i];
+ switch(qtype){
+ case Qroot:
+ if(strcmp(name, "..") == 0)
+ goto Accept;
+ user = finduser(name);
+ if(!user)
+ goto Out;
+ qtype = Quser;
+
+ Accept:
+ thdr.wqid[i] = mkqid(user, qtype);
+ break;
+
+ case Quser:
+ if(strcmp(name, "..") == 0) {
+ qtype = Qroot;
+ user = 0;
+ goto Accept;
+ }
+ max = Qmax;
+ for(j = Quser + 1; j < Qmax; j++)
+ if(strcmp(name, qinfo[j]) == 0){
+ qtype = j;
+ break;
+ }
+ if(j < max)
+ goto Accept;
+ goto Out;
+
+ default:
+ err = "file is not a directory";
+ goto Out;
+ }
+ }
+ Out:
+ if(i < rhdr.nwname && err == nil)
+ err = "file not found";
+ }
+
+ if(err != nil){
+ return err;
+ }
+
+ /* if we cloned and then completed the walk, update new fid */
+ if(rhdr.fid != rhdr.newfid && i == rhdr.nwname){
+ nf->busy = 1;
+ nf->qtype = qtype;
+ if(nf->user = user)
+ nf->user->ref++;
+ }else if(nf == nil && rhdr.nwname > 0){ /* walk without clone (rare) */
+ Clunk(f);
+ f->busy = 1;
+ f->qtype = qtype;
+ if(f->user = user)
+ f->user->ref++;
+ }
+
+ thdr.nwqid = i;
+ return 0;
+}
+
+char *
+Clunk(Fid *f)
+{
+ f->busy = 0;
+ if(f->user && --f->user->ref == 0 && f->user->removed) {
+ free(f->user->name);
+ free(f->user);
+ }
+ f->user = 0;
+ return 0;
+}
+
+char *
+Open(Fid *f)
+{
+ int mode;
+
+ if(!f->busy)
+ return "open of unused fid";
+ mode = rhdr.mode;
+ if(f->qtype == Quser && (mode & (OWRITE|OTRUNC)))
+ return "user already exists";
+ thdr.qid = mkqid(f->user, f->qtype);
+ thdr.iounit = messagesize - IOHDRSZ;
+ return 0;
+}
+
+char *
+Create(Fid *f)
+{
+ char *name;
+ long perm;
+
+ if(!f->busy)
+ return "create of unused fid";
+ name = rhdr.name;
+ if(f->user){
+ return "permission denied";
+ }else{
+ perm = rhdr.perm;
+ if(!(perm & DMDIR))
+ return "permission denied";
+ if(strcmp(name, "") == 0)
+ return "empty file name";
+ if(strlen(name) >= Namelen)
+ return "file name too long";
+ if(finduser(name))
+ return "user already exists";
+ f->user = installuser(name);
+ f->user->ref++;
+ f->qtype = Quser;
+ }
+ thdr.qid = mkqid(f->user, f->qtype);
+ thdr.iounit = messagesize - IOHDRSZ;
+ writeusers();
+ return 0;
+}
+
+char *
+Read(Fid *f)
+{
+ User *u;
+ char *data;
+ ulong off, n, m;
+ int i, j, max;
+
+ if(!f->busy)
+ return "read of unused fid";
+ n = rhdr.count;
+ off = rhdr.offset;
+ thdr.count = 0;
+ data = thdr.data;
+ switch(f->qtype){
+ case Qroot:
+ j = 0;
+ for(i = 0; i < Nuser; i++)
+ for(u = users[i]; u; j += m, u = u->link){
+ m = dostat(u, Quser, data, n);
+ if(m <= BIT16SZ)
+ break;
+ if(j < off)
+ continue;
+ data += m;
+ n -= m;
+ }
+ thdr.count = data - thdr.data;
+ return 0;
+ case Quser:
+ max = Qmax;
+ max -= Quser + 1;
+ j = 0;
+ for(i = 0; i < max; j += m, i++){
+ m = dostat(f->user, i + Quser + 1, data, n);
+ if(m <= BIT16SZ)
+ break;
+ if(j < off)
+ continue;
+ data += m;
+ n -= m;
+ }
+ thdr.count = data - thdr.data;
+ return 0;
+ case Qkey:
+ if(f->user->status != Sok)
+ return "user disabled";
+ if(f->user->purgatory > time(0))
+ return "user in purgatory";
+ if(f->user->expire != 0 && f->user->expire < time(0))
+ return "user expired";
+ if(off != 0)
+ return 0;
+ if(n > DESKEYLEN)
+ n = DESKEYLEN;
+ memmove(thdr.data, f->user->key, n);
+ thdr.count = n;
+ return 0;
+ case Qsecret:
+ if(f->user->status != Sok)
+ return "user disabled";
+ if(f->user->purgatory > time(0))
+ return "user in purgatory";
+ if(f->user->expire != 0 && f->user->expire < time(0))
+ return "user expired";
+ if(off != 0)
+ return 0;
+ if(n > strlen(f->user->secret))
+ n = strlen(f->user->secret);
+ memmove(thdr.data, f->user->secret, n);
+ thdr.count = n;
+ return 0;
+ case Qstatus:
+ if(off != 0){
+ thdr.count = 0;
+ return 0;
+ }
+ if(f->user->status == Sok && f->user->expire && f->user->expire < time(0))
+ sprint(thdr.data, "expired\n");
+ else
+ sprint(thdr.data, "%s\n", status[f->user->status]);
+ thdr.count = strlen(thdr.data);
+ return 0;
+ case Qexpire:
+ if(off != 0){
+ thdr.count = 0;
+ return 0;
+ }
+ if(!f->user->expire)
+ strcpy(data, "never\n");
+ else
+ sprint(data, "%lud\n", f->user->expire);
+ if(n > strlen(data))
+ n = strlen(data);
+ thdr.count = n;
+ return 0;
+ case Qlog:
+ if(off != 0){
+ thdr.count = 0;
+ return 0;
+ }
+ sprint(data, "%lud\n", f->user->bad);
+ if(n > strlen(data))
+ n = strlen(data);
+ thdr.count = n;
+ return 0;
+ case Qwarnings:
+ if(off != 0){
+ thdr.count = 0;
+ return 0;
+ }
+ sprint(data, "%ud\n", f->user->warnings);
+ if(n > strlen(data))
+ n = strlen(data);
+ thdr.count = n;
+ return 0;
+ default:
+ return "permission denied: unknown qid";
+ }
+}
+
+char *
+Write(Fid *f)
+{
+ char *data, *p;
+ ulong n, expire;
+ int i;
+
+ if(!f->busy)
+ return "permission denied";
+ n = rhdr.count;
+ data = rhdr.data;
+ switch(f->qtype){
+ case Qkey:
+ if(n != DESKEYLEN)
+ return "garbled write data";
+ memmove(f->user->key, data, DESKEYLEN);
+ thdr.count = DESKEYLEN;
+ break;
+ case Qsecret:
+ if(n >= SECRETLEN)
+ return "garbled write data";
+ memmove(f->user->secret, data, n);
+ f->user->secret[n] = 0;
+ thdr.count = n;
+ break;
+ case Qstatus:
+ data[n] = '\0';
+ if(p = strchr(data, '\n'))
+ *p = '\0';
+ for(i = 0; i < Smax; i++)
+ if(strcmp(data, status[i]) == 0){
+ f->user->status = i;
+ break;
+ }
+ if(i == Smax)
+ return "unknown status";
+ f->user->bad = 0;
+ thdr.count = n;
+ break;
+ case Qexpire:
+ data[n] = '\0';
+ if(p = strchr(data, '\n'))
+ *p = '\0';
+ else
+ p = &data[n];
+ if(strcmp(data, "never") == 0)
+ expire = 0;
+ else{
+ expire = strtoul(data, &data, 10);
+ if(data != p)
+ return "bad expiration date";
+ }
+ f->user->expire = expire;
+ f->user->warnings = 0;
+ thdr.count = n;
+ break;
+ case Qlog:
+ data[n] = '\0';
+ if(strcmp(data, "good") == 0)
+ f->user->bad = 0;
+ else
+ f->user->bad++;
+ if(f->user->bad && ((f->user->bad)%MAXBAD) == 0)
+ f->user->purgatory = time(0) + f->user->bad;
+ return 0;
+ case Qwarnings:
+ data[n] = '\0';
+ f->user->warnings = strtoul(data, 0, 10);
+ thdr.count = n;
+ break;
+ case Qroot:
+ case Quser:
+ default:
+ return "permission denied";
+ }
+ writeusers();
+ return 0;
+}
+
+char *
+Remove(Fid *f)
+{
+ if(!f->busy)
+ return "permission denied";
+ if(f->qtype == Qwarnings)
+ f->user->warnings = 0;
+ else if(f->qtype == Quser)
+ removeuser(f->user);
+ else {
+ Clunk(f);
+ return "permission denied";
+ }
+ Clunk(f);
+ writeusers();
+ return 0;
+}
+
+char *
+Stat(Fid *f)
+{
+ static uchar statbuf[1024];
+
+ if(!f->busy)
+ return "stat on unattached fid";
+ thdr.nstat = dostat(f->user, f->qtype, statbuf, sizeof statbuf);
+ if(thdr.nstat <= BIT16SZ)
+ return "stat buffer too small";
+ thdr.stat = statbuf;
+ return 0;
+}
+
+char *
+Wstat(Fid *f)
+{
+ Dir d;
+ int n;
+ char buf[1024];
+
+ if(!f->busy || f->qtype != Quser)
+ return "permission denied";
+ if(rhdr.nstat > sizeof buf)
+ return "wstat buffer too big";
+ if(convM2D(rhdr.stat, rhdr.nstat, &d, buf) == 0)
+ return "bad stat buffer";
+ n = strlen(d.name);
+ if(n == 0 || n >= Namelen)
+ return "bad user name";
+ if(finduser(d.name))
+ return "user already exists";
+ if(!removeuser(f->user))
+ return "user previously removed";
+ free(f->user->name);
+ f->user->name = strdup(d.name);
+ if(f->user->name == nil)
+ error("wstat: malloc failed: %r");
+ insertuser(f->user);
+ writeusers();
+ return 0;
+}
+
+Qid
+mkqid(User *u, ulong qtype)
+{
+ Qid q;
+
+ q.vers = 0;
+ q.path = qtype;
+ if(u)
+ q.path |= u->uniq * 0x100;
+ if(qtype == Quser || qtype == Qroot)
+ q.type = QTDIR;
+ else
+ q.type = QTFILE;
+ return q;
+}
+
+int
+dostat(User *user, ulong qtype, void *p, int n)
+{
+ Dir d;
+
+ if(qtype == Quser)
+ d.name = user->name;
+ else
+ d.name = qinfo[qtype];
+ d.uid = d.gid = d.muid = "auth";
+ d.qid = mkqid(user, qtype);
+ if(d.qid.type & QTDIR)
+ d.mode = 0777|DMDIR;
+ else
+ d.mode = 0666;
+ d.atime = d.mtime = time(0);
+ d.length = 0;
+ return convD2M(&d, p, n);
+}
+
+int
+passline(Biobuf *b, void *vbuf)
+{
+ char *buf = vbuf;
+
+ if(Bread(b, buf, KEYDBLEN) != KEYDBLEN)
+ return 0;
+ decrypt(authkey, buf, KEYDBLEN);
+ buf[Namelen-1] = '\0';
+ return 1;
+}
+
+void
+randombytes(uchar *p, int len)
+{
+ int i, fd;
+
+ fd = open("/dev/random", OREAD);
+ if(fd < 0){
+ fprint(2, "keyfs: can't open /dev/random, using rand()\n");
+ srand(time(0));
+ for(i = 0; i < len; i++)
+ p[i] = rand();
+ return;
+ }
+ read(fd, p, len);
+ close(fd);
+}
+
+void
+oldCBCencrypt(char *key7, uchar *p, int len)
+{
+ uchar ivec[8];
+ uchar key[8];
+ DESstate s;
+
+ memset(ivec, 0, 8);
+ des56to64((uchar*)key7, key);
+ setupDESstate(&s, key, ivec);
+ desCBCencrypt((uchar*)p, len, &s);
+}
+
+void
+oldCBCdecrypt(char *key7, uchar *p, int len)
+{
+ uchar ivec[8];
+ uchar key[8];
+ DESstate s;
+
+ memset(ivec, 0, 8);
+ des56to64((uchar*)key7, key);
+ setupDESstate(&s, key, ivec);
+ desCBCdecrypt((uchar*)p, len, &s);
+
+}
+
+void
+writeusers(void)
+{
+ int fd, i, nu;
+ User *u;
+ uchar *p, *buf;
+ ulong expire;
+
+ /* count users */
+ nu = 0;
+ for(i = 0; i < Nuser; i++)
+ for(u = users[i]; u; u = u->link)
+ nu++;
+
+ /* pack into buffer */
+ buf = malloc(KEYDBOFF + nu*KEYDBLEN);
+ if(buf == 0){
+ fprint(2, "keyfs: can't write keys file, out of memory\n");
+ return;
+ }
+ p = buf;
+ randombytes(p, KEYDBOFF);
+ p += KEYDBOFF;
+ for(i = 0; i < Nuser; i++)
+ for(u = users[i]; u; u = u->link){
+ strncpy((char*)p, u->name, Namelen);
+ p += Namelen;
+ memmove(p, u->key, DESKEYLEN);
+ p += DESKEYLEN;
+ *p++ = u->status;
+ *p++ = u->warnings;
+ expire = u->expire;
+ *p++ = expire;
+ *p++ = expire >> 8;
+ *p++ = expire >> 16;
+ *p++ = expire >> 24;
+ memmove(p, u->secret, SECRETLEN);
+ p += SECRETLEN;
+ }
+
+ /* encrypt */
+ oldCBCencrypt(authkey, buf, p - buf);
+
+ /* write file */
+ fd = create(userkeys, OWRITE, 0660);
+ if(fd < 0){
+ free(buf);
+ fprint(2, "keyfs: can't write keys file\n");
+ return;
+ }
+ if(write(fd, buf, p - buf) != (p - buf))
+ fprint(2, "keyfs: can't write keys file\n");
+
+ free(buf);
+ close(fd);
+}
+
+int
+weirdfmt(Fmt *f)
+{
+ char *s, buf[ANAMELEN*4 + 1];
+ int i, j, n;
+ Rune r;
+
+ s = va_arg(f->args, char*);
+ j = 0;
+ for(i = 0; i < ANAMELEN; i += n){
+ n = chartorune(&r, s + i);
+ if(r == Runeerror)
+ j += sprint(buf+j, "[%.2x]", buf[i]);
+ else if(isascii(r) && iscntrl(r))
+ j += sprint(buf+j, "[%.2x]", r);
+ else if(r == ' ' || r == '/')
+ j += sprint(buf+j, "[%c]", r);
+ else
+ j += sprint(buf+j, "%C", r);
+ }
+ return fmtstrcpy(f, buf);
+}
+
+int
+userok(char *user, int nu)
+{
+ int i, n, rv;
+ Rune r;
+ char buf[ANAMELEN+1];
+
+ memset(buf, 0, sizeof buf);
+ memmove(buf, user, ANAMELEN);
+
+ if(buf[ANAMELEN-1] != 0){
+ fprint(2, "keyfs: %d: no termination: %W\n", nu, buf);
+ return -1;
+ }
+
+ rv = 0;
+ for(i = 0; buf[i]; i += n){
+ n = chartorune(&r, buf+i);
+ if(r == Runeerror){
+// fprint(2, "keyfs: name %W bad rune byte %d\n", buf, i);
+ rv = -1;
+ } else if(isascii(r) && iscntrl(r) || r == ' ' || r == '/'){
+// fprint(2, "keyfs: name %W bad char %C\n", buf, r);
+ rv = -1;
+ }
+ }
+
+ if(i == 0){
+ fprint(2, "keyfs: %d: nil name\n", nu);
+ return -1;
+ }
+ if(rv == -1)
+ fprint(2, "keyfs: %d: bad syntax: %W\n", nu, buf);
+ return rv;
+}
+
+int
+readusers(void)
+{
+ int fd, i, n, nu;
+ uchar *p, *buf, *ep;
+ User *u;
+ Dir *d;
+
+ /* read file into an array */
+ fd = open(userkeys, OREAD);
+ if(fd < 0)
+ return 0;
+ d = dirfstat(fd);
+ if(d == nil){
+ close(fd);
+ return 0;
+ }
+ buf = malloc(d->length);
+ if(buf == 0){
+ close(fd);
+ free(d);
+ return 0;
+ }
+ n = readn(fd, buf, d->length);
+ close(fd);
+ free(d);
+ if(n != d->length){
+ free(buf);
+ return 0;
+ }
+
+ /* decrypt */
+ n -= n % KEYDBLEN;
+ oldCBCdecrypt(authkey, buf, n);
+
+ /* unpack */
+ nu = 0;
+ for(i = KEYDBOFF; i < n; i += KEYDBLEN){
+ ep = buf + i;
+ if(userok((char*)ep, i/KEYDBLEN) < 0)
+ continue;
+ u = finduser((char*)ep);
+ if(u == 0)
+ u = installuser((char*)ep);
+ memmove(u->key, ep + Namelen, DESKEYLEN);
+ p = ep + Namelen + DESKEYLEN;
+ u->status = *p++;
+ u->warnings = *p++;
+ if(u->status >= Smax)
+ fprint(2, "keyfs: warning: bad status in key file\n");
+ u->expire = p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24);
+ p += 4;
+ memmove(u->secret, p, SECRETLEN);
+ u->secret[SECRETLEN-1] = 0;
+ nu++;
+ }
+ free(buf);
+
+ print("%d keys read\n", nu);
+ return 1;
+}
+
+User *
+installuser(char *name)
+{
+ User *u;
+ int h;
+
+ h = hash(name);
+ u = emalloc(sizeof *u);
+ u->name = strdup(name);
+ if(u->name == nil)
+ error("malloc failed: %r");
+ u->removed = 0;
+ u->ref = 0;
+ u->purgatory = 0;
+ u->expire = 0;
+ u->status = Sok;
+ u->bad = 0;
+ u->warnings = 0;
+ u->uniq = uniq++;
+ u->link = users[h];
+ users[h] = u;
+ return u;
+}
+
+User *
+finduser(char *name)
+{
+ User *u;
+
+ for(u = users[hash(name)]; u; u = u->link)
+ if(strcmp(name, u->name) == 0)
+ return u;
+ return 0;
+}
+
+int
+removeuser(User *user)
+{
+ User *u, **last;
+ char *name;
+
+ user->removed = 1;
+ name = user->name;
+ last = &users[hash(name)];
+ for(u = *last; u; u = *last){
+ if(strcmp(name, u->name) == 0){
+ *last = u->link;
+ return 1;
+ }
+ last = &u->link;
+ }
+ return 0;
+}
+
+void
+insertuser(User *user)
+{
+ int h;
+
+ user->removed = 0;
+ h = hash(user->name);
+ user->link = users[h];
+ users[h] = user;
+}
+
+ulong
+hash(char *s)
+{
+ ulong h;
+
+ h = 0;
+ while(*s)
+ h = (h << 1) ^ *s++;
+ return h % Nuser;
+}
+
+Fid *
+findfid(int fid)
+{
+ Fid *f, *ff;
+
+ ff = 0;
+ for(f = fids; f; f = f->next)
+ if(f->fid == fid)
+ return f;
+ else if(!ff && !f->busy)
+ ff = f;
+ if(ff){
+ ff->fid = fid;
+ return ff;
+ }
+ f = emalloc(sizeof *f);
+ f->fid = fid;
+ f->busy = 0;
+ f->user = 0;
+ f->next = fids;
+ fids = f;
+ return f;
+}
+
+void
+io(int in, int out)
+{
+ char *err;
+ int n;
+ long now, lastwarning;
+
+ /* after restart, let the system settle for 5 mins before warning */
+ lastwarning = time(0) - 24*60*60 + 5*60;
+
+ for(;;){
+ n = read9pmsg(in, mdata, messagesize);
+ if(n == 0)
+ continue;
+ if(n < 0)
+ error("mount read %d", n);
+ if(convM2S(mdata, n, &rhdr) == 0)
+ continue;
+
+ if(newkeys())
+ readusers();
+
+ thdr.data = (char*)mdata + IOHDRSZ;
+ thdr.fid = rhdr.fid;
+ if(!fcalls[rhdr.type])
+ err = "fcall request";
+ else
+ err = (*fcalls[rhdr.type])(findfid(rhdr.fid));
+ thdr.tag = rhdr.tag;
+ thdr.type = rhdr.type+1;
+ if(err){
+ thdr.type = Rerror;
+ thdr.ename = err;
+ }
+ n = convS2M(&thdr, mdata, messagesize);
+ if(write(out, mdata, n) != n)
+ error("mount write");
+
+ now = time(0);
+ if(warnarg && (now - lastwarning > 24*60*60)){
+ syslog(0, "auth", "keyfs starting warnings: %lux %lux",
+ now, lastwarning);
+ warning();
+ lastwarning = now;
+ }
+ }
+}
+
+int
+newkeys(void)
+{
+ Dir *d;
+ static long ftime;
+
+ d = dirstat(userkeys);
+ if(d == nil)
+ return 0;
+ if(d->mtime > ftime){
+ ftime = d->mtime;
+ free(d);
+ return 1;
+ }
+ free(d);
+ return 0;
+}
+
+void *
+emalloc(ulong n)
+{
+ void *p;
+
+ if(p = malloc(n))
+ return p;
+ error("out of memory");
+ return 0; /* not reached */
+}
+
+void
+warning(void)
+{
+ int i;
+ char buf[64];
+
+ snprint(buf, sizeof buf, "-%s", warnarg);
+ switch(rfork(RFPROC|RFNAMEG|RFNOTEG|RFNOWAIT|RFENVG|RFFDG)){
+ case 0:
+ i = open("/sys/log/auth", OWRITE);
+ if(i >= 0){
+ dup(i, 2);
+ seek(2, 0, 2);
+ close(i);
+ }
+ execl("/bin/auth/warning", "warning", warnarg, nil);
+ error("can't exec warning");
+ }
+}
diff --git a/sys/src/cmd/auth/lib/error.c b/sys/src/cmd/auth/lib/error.c
new file mode 100755
index 000000000..71bf63df1
--- /dev/null
+++ b/sys/src/cmd/auth/lib/error.c
@@ -0,0 +1,20 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+void
+error(char *fmt, ...)
+{
+ char buf[8192], *s;
+ va_list arg;
+
+ s = buf;
+ s += sprint(s, "%s: ", argv0);
+ va_start(arg, fmt);
+ s = vseprint(s, buf + sizeof(buf), fmt, arg);
+ va_end(arg);
+ *s++ = '\n';
+ write(2, buf, s - buf);
+ exits(buf);
+}
diff --git a/sys/src/cmd/auth/lib/fs.c b/sys/src/cmd/auth/lib/fs.c
new file mode 100755
index 000000000..43f7db845
--- /dev/null
+++ b/sys/src/cmd/auth/lib/fs.c
@@ -0,0 +1,10 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+Fs fs[3] =
+{
+[Plan9] { "/mnt/keys", "plan 9 key", "/adm/keys.who", 0 },
+[Securenet] { "/mnt/netkeys", "network access key", "/adm/netkeys.who", 0 },
+};
diff --git a/sys/src/cmd/auth/lib/getauthkey.c b/sys/src/cmd/auth/lib/getauthkey.c
new file mode 100755
index 000000000..1ae8d4e87
--- /dev/null
+++ b/sys/src/cmd/auth/lib/getauthkey.c
@@ -0,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+static int
+getkey(char *authkey)
+{
+ Nvrsafe safe;
+
+ if(readnvram(&safe, 0) < 0)
+ return -1;
+ memmove(authkey, safe.machkey, DESKEYLEN);
+ memset(&safe, 0, sizeof safe);
+ return 0;
+}
+
+int
+getauthkey(char *authkey)
+{
+ if(getkey(authkey) == 0)
+ return 1;
+ print("can't read NVRAM, please enter machine key\n");
+ getpass(authkey, nil, 0, 1);
+ return 1;
+}
diff --git a/sys/src/cmd/auth/lib/getexpiration.c b/sys/src/cmd/auth/lib/getexpiration.c
new file mode 100755
index 000000000..fb07d261e
--- /dev/null
+++ b/sys/src/cmd/auth/lib/getexpiration.c
@@ -0,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+/*
+ * get the date in the format yyyymmdd
+ */
+Tm
+getdate(char *d)
+{
+ Tm date;
+ int i;
+
+ date.year = date.mon = date.mday = 0;
+ date.hour = date.min = date.sec = 0;
+ for(i = 0; i < 8; i++)
+ if(!isdigit(d[i]))
+ return date;
+ date.year = (d[0]-'0')*1000 + (d[1]-'0')*100 + (d[2]-'0')*10 + d[3]-'0';
+ date.year -= 1900;
+ d += 4;
+ date.mon = (d[0]-'0')*10 + d[1]-'0' - 1;
+ d += 2;
+ date.mday = (d[0]-'0')*10 + d[1]-'0';
+ date.yday = 0;
+ return date;
+}
+
+long
+getexpiration(char *db, char *u)
+{
+ char buf[Maxpath];
+ char prompt[128];
+ char cdate[32];
+ Tm date;
+ ulong secs, now;
+ int n, fd;
+
+ /* read current expiration (if any) */
+ snprint(buf, sizeof buf, "%s/%s/expire", db, u);
+ fd = open(buf, OREAD);
+ buf[0] = 0;
+ if(fd >= 0){
+ n = read(fd, buf, sizeof(buf)-1);
+ if(n > 0)
+ buf[n-1] = 0;
+ close(fd);
+ }
+
+ if(buf[0]){
+ if(strncmp(buf, "never", 5)){
+ secs = atoi(buf);
+ memmove(&date, localtime(secs), sizeof(date));
+ sprint(buf, "%4.4d%2.2d%2.2d", date.year+1900, date.mon+1, date.mday);
+ } else
+ buf[5] = 0;
+ } else
+ strcpy(buf, "never");
+ sprint(prompt, "Expiration date (YYYYMMDD or never)[return = %s]: ", buf);
+
+ now = time(0);
+ for(;;){
+ readln(prompt, cdate, sizeof cdate, 0);
+ if(*cdate == 0)
+ return -1;
+ if(strcmp(cdate, "never") == 0)
+ return 0;
+ date = getdate(cdate);
+ secs = tm2sec(&date);
+ if(secs > now && secs < now + 2*365*24*60*60)
+ break;
+ print("expiration time must fall between now and 2 years from now\n");
+ }
+ return secs;
+}
diff --git a/sys/src/cmd/auth/lib/keyfmt.c b/sys/src/cmd/auth/lib/keyfmt.c
new file mode 100755
index 000000000..86c2378a3
--- /dev/null
+++ b/sys/src/cmd/auth/lib/keyfmt.c
@@ -0,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+/*
+ * print a key in des standard form
+ */
+int
+keyfmt(Fmt *f)
+{
+ uchar key[8];
+ char buf[32];
+ uchar *k;
+ int i;
+
+ k = va_arg(f->args, uchar*);
+ key[0] = 0;
+ for(i = 0; i < 7; i++){
+ key[i] |= k[i] >> i;
+ key[i] &= ~1;
+ key[i+1] = k[i] << (7 - i);
+ }
+ key[7] &= ~1;
+ sprint(buf, "%.3uo %.3uo %.3uo %.3uo %.3uo %.3uo %.3uo %.3uo",
+ key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]);
+ fmtstrcpy(f, buf);
+ return 0;
+}
diff --git a/sys/src/cmd/auth/lib/log.c b/sys/src/cmd/auth/lib/log.c
new file mode 100755
index 000000000..bd4eaf1a3
--- /dev/null
+++ b/sys/src/cmd/auth/lib/log.c
@@ -0,0 +1,45 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+static void
+record(char *db, char *user, char *msg)
+{
+ char buf[Maxpath];
+ int fd;
+
+ snprint(buf, sizeof buf, "%s/%s/log", db, user);
+ fd = open(buf, OWRITE);
+ if(fd < 0)
+ return;
+ write(fd, msg, strlen(msg));
+ close(fd);
+ return;
+}
+
+void
+logfail(char *user)
+{
+ if(!user)
+ return;
+ record(KEYDB, user, "bad");
+ record(NETKEYDB, user, "bad");
+}
+
+void
+succeed(char *user)
+{
+ if(!user)
+ return;
+ record(KEYDB, user, "good");
+ record(NETKEYDB, user, "good");
+}
+
+void
+fail(char *user)
+{
+ logfail(user);
+ exits("failure");
+}
diff --git a/sys/src/cmd/auth/lib/mkfile b/sys/src/cmd/auth/lib/mkfile
new file mode 100755
index 000000000..9e54d35fc
--- /dev/null
+++ b/sys/src/cmd/auth/lib/mkfile
@@ -0,0 +1,30 @@
+</$objtype/mkfile
+
+
+LIB=../lib.$O.a
+OFILES=\
+ keyfmt.$O\
+ netcheck.$O\
+ okpasswd.$O\
+ readwrite.$O\
+ readarg.$O\
+ readln.$O\
+ getauthkey.$O\
+ log.$O\
+ error.$O\
+ fs.$O\
+ rdbio.$O\
+ querybio.$O\
+ wrbio.$O\
+ getexpiration.$O\
+
+
+HFILES=/sys/include/auth.h /sys/include/authsrv.h ../authcmdlib.h
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mksyslib
+CFLAGS=$CFLAGS -I..
diff --git a/sys/src/cmd/auth/lib/netcheck.c b/sys/src/cmd/auth/lib/netcheck.c
new file mode 100755
index 000000000..5e4220c25
--- /dev/null
+++ b/sys/src/cmd/auth/lib/netcheck.c
@@ -0,0 +1,111 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+/*
+ * compute the key verification checksum
+ */
+void
+checksum(char key[], char csum[]) {
+ uchar buf[8];
+
+ memset(buf, 0, 8);
+ encrypt(key, buf, 8);
+ sprint(csum, "C %.2ux%.2ux%.2ux%.2ux", buf[0], buf[1], buf[2], buf[3]);
+}
+
+/*
+ * compute the proper response. We encrypt the ascii of
+ * challenge number, with trailing binary zero fill.
+ * This process was derived empirically.
+ * this was copied from inet's guard.
+ */
+char *
+netresp(char *key, long chal, char *answer)
+{
+ uchar buf[8];
+
+ memset(buf, 0, 8);
+ sprint((char *)buf, "%lud", chal);
+ if(encrypt(key, buf, 8) < 0)
+ error("can't encrypt response");
+ chal = (buf[0]<<24)+(buf[1]<<16)+(buf[2]<<8)+buf[3];
+ sprint(answer, "%.8lux", chal);
+
+ return answer;
+}
+
+char *
+netdecimal(char *answer)
+{
+ int i;
+
+ for(i = 0; answer[i]; i++)
+ switch(answer[i]){
+ case 'a': case 'b': case 'c':
+ answer[i] = '2';
+ break;
+ case 'd': case 'e': case 'f':
+ answer[i] = '3';
+ break;
+ }
+ return answer;
+}
+
+int
+netcheck(void *key, long chal, char *response)
+{
+ char answer[32], *p;
+ int i;
+
+ if(smartcheck(key, chal, response))
+ return 1;
+
+ if(p = strchr(response, '\n'))
+ *p = '\0';
+ netresp(key, chal, answer);
+
+ /*
+ * check for hex response -- securenet mode 1 or 5
+ */
+ for(i = 0; response[i]; i++)
+ if(response[i] >= 'A' && response[i] <= 'Z')
+ response[i] -= 'A' - 'a';
+ if(strcmp(answer, response) == 0)
+ return 1;
+
+ /*
+ * check for decimal response -- securenet mode 0 or 4
+ */
+ return strcmp(netdecimal(answer), response) == 0;
+}
+
+int
+smartcheck(void *key, long chal, char *response)
+{
+ uchar buf[2*8];
+ int i, c, cslo, cshi;
+
+ sprint((char*)buf, "%lud ", chal);
+ cslo = 0x52;
+ cshi = cslo;
+ for(i = 0; i < 8; i++){
+ c = buf[i];
+ if(c >= '0' && c <= '9')
+ c -= '0';
+ cslo += c;
+ if(cslo > 0xff)
+ cslo -= 0xff;
+ cshi += cslo;
+ if(cshi > 0xff)
+ cshi -= 0xff;
+ buf[i] = c | (cshi & 0xf0);
+ }
+
+ encrypt(key, buf, 8);
+ for(i = 0; i < 8; i++)
+ if(response[i] != buf[i] % 10 + '0')
+ return 0;
+ return 1;
+}
diff --git a/sys/src/cmd/auth/lib/okpasswd.c b/sys/src/cmd/auth/lib/okpasswd.c
new file mode 100755
index 000000000..b904db5aa
--- /dev/null
+++ b/sys/src/cmd/auth/lib/okpasswd.c
@@ -0,0 +1,42 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+char *trivial[] = {
+ "login",
+ "guest",
+ "change me",
+ "passwd",
+ "no passwd",
+ "anonymous",
+ 0
+};
+
+char*
+okpasswd(char *p)
+{
+ char passwd[ANAMELEN];
+ char back[ANAMELEN];
+ int i, n;
+
+ strncpy(passwd, p, sizeof passwd - 1);
+ passwd[sizeof passwd - 1] = '\0';
+ n = strlen(passwd);
+ while(passwd[n - 1] == ' ')
+ n--;
+ passwd[n] = '\0';
+ for(i = 0; i < n; i++)
+ back[i] = passwd[n - 1 - i];
+ back[n] = '\0';
+ if(n < 8)
+ return "password must be at least 8 chars";
+
+ for(i = 0; trivial[i]; i++)
+ if(strcmp(passwd, trivial[i]) == 0
+ || strcmp(back, trivial[i]) == 0)
+ return "trivial password";
+
+ return 0;
+}
diff --git a/sys/src/cmd/auth/lib/querybio.c b/sys/src/cmd/auth/lib/querybio.c
new file mode 100755
index 000000000..97218a6dc
--- /dev/null
+++ b/sys/src/cmd/auth/lib/querybio.c
@@ -0,0 +1,66 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "authcmdlib.h"
+
+
+#define TABLEN 8
+
+static char*
+defreadln(char *prompt, char *def, int must, int *changed)
+{
+ char pr[512];
+ char reply[256];
+
+ do {
+ if(def && *def){
+ if(must)
+ snprint(pr, sizeof pr, "%s[return = %s]: ", prompt, def);
+ else
+ snprint(pr, sizeof pr, "%s[return = %s, space = none]: ", prompt, def);
+ } else
+ snprint(pr, sizeof pr, "%s: ", prompt);
+ readln(pr, reply, sizeof(reply), 0);
+ switch(*reply){
+ case ' ':
+ break;
+ case 0:
+ return def;
+ default:
+ *changed = 1;
+ if(def)
+ free(def);
+ return strdup(reply);
+ }
+ } while(must);
+
+ if(def){
+ *changed = 1;
+ free(def);
+ }
+ return 0;
+}
+
+/*
+ * get bio from stdin
+ */
+int
+querybio(char *file, char *user, Acctbio *a)
+{
+ int i;
+ int changed;
+
+ rdbio(file, user, a);
+ a->postid = defreadln("Post id", a->postid, 0, &changed);
+ a->name = defreadln("User's full name", a->name, 1, &changed);
+ a->dept = defreadln("Department #", a->dept, 1, &changed);
+ a->email[0] = defreadln("User's email address", a->email[0], 1, &changed);
+ a->email[1] = defreadln("Sponsor's email address", a->email[1], 0, &changed);
+ for(i = 2; i < Nemail; i++){
+ if(a->email[i-1] == 0)
+ break;
+ a->email[i] = defreadln("other email address", a->email[i], 0, &changed);
+ }
+ return changed;
+}
diff --git a/sys/src/cmd/auth/lib/rdbio.c b/sys/src/cmd/auth/lib/rdbio.c
new file mode 100755
index 000000000..7363e4284
--- /dev/null
+++ b/sys/src/cmd/auth/lib/rdbio.c
@@ -0,0 +1,58 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "authcmdlib.h"
+
+void
+clrbio(Acctbio *a)
+{
+ int i;
+
+ if(a->user)
+ free(a->user);
+ if(a->postid)
+ free(a->postid);
+ if(a->name)
+ free(a->name);
+ if(a->dept)
+ free(a->dept);
+ for(i = 0; i < Nemail; i++)
+ if(a->email[i])
+ free(a->email[i]);
+ memset(a, 0, sizeof(Acctbio));
+}
+
+void
+rdbio(char *file, char *user, Acctbio *a)
+{
+ int i,n;
+ Biobuf *b;
+ char *p;
+ char *field[20];
+
+ memset(a, 0, sizeof(Acctbio));
+ b = Bopen(file, OREAD);
+ if(b == 0)
+ return;
+ while(p = Brdline(b, '\n')){
+ p[Blinelen(b)-1] = 0;
+ n = getfields(p, field, nelem(field), 0, "|");
+ if(n < 4)
+ continue;
+ if(strcmp(field[0], user) != 0)
+ continue;
+
+ clrbio(a);
+
+ a->postid = strdup(field[1]);
+ a->name = strdup(field[2]);
+ a->dept = strdup(field[3]);
+ if(n-4 >= Nemail)
+ n = Nemail-4;
+ for(i = 4; i < n; i++)
+ a->email[i-4] = strdup(field[i]);
+ }
+ a->user = strdup(user);
+ Bterm(b);
+}
diff --git a/sys/src/cmd/auth/lib/readarg.c b/sys/src/cmd/auth/lib/readarg.c
new file mode 100755
index 000000000..cce957db9
--- /dev/null
+++ b/sys/src/cmd/auth/lib/readarg.c
@@ -0,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+int
+readarg(int fd, char *arg, int len)
+{
+ char buf[1];
+ int i;
+
+ i = 0;
+ memset(arg, 0, len);
+ while(read(fd, buf, 1) == 1){
+ if(i < len - 1)
+ arg[i++] = *buf;
+ if(*buf == '\0'){
+ arg[i] = '\0';
+ return 0;
+ }
+ }
+ return -1;
+}
diff --git a/sys/src/cmd/auth/lib/readln.c b/sys/src/cmd/auth/lib/readln.c
new file mode 100755
index 000000000..ee470a52c
--- /dev/null
+++ b/sys/src/cmd/auth/lib/readln.c
@@ -0,0 +1,115 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+void
+getpass(char *key, char *pass, int check, int confirm)
+{
+ char rpass[32], npass[32];
+ char *err;
+
+ if(pass == nil)
+ pass = npass;
+
+ for(;;){
+ readln("Password: ", pass, sizeof npass, 1);
+ if(confirm){
+ readln("Confirm password: ", rpass, sizeof rpass, 1);
+ if(strcmp(pass, rpass) != 0){
+ print("mismatch, try again\n");
+ continue;
+ }
+ }
+ if(!passtokey(key, pass)){
+ print("bad password, try again\n");
+ continue;
+ }
+ if(check)
+ if(err = okpasswd(pass)){
+ print("%s, try again\n", err);
+ continue;
+ }
+ break;
+ }
+}
+
+int
+getsecret(int passvalid, char *p9pass)
+{
+ char answer[32];
+
+ readln("assign Inferno/POP secret? (y/n) ", answer, sizeof answer, 0);
+ if(*answer != 'y' && *answer != 'Y')
+ return 0;
+
+ if(passvalid){
+ readln("make it the same as the plan 9 password? (y/n) ",
+ answer, sizeof answer, 0);
+ if(*answer == 'y' || *answer == 'Y')
+ return 1;
+ }
+
+ for(;;){
+ readln("Secret(0 to 256 characters): ", p9pass,
+ sizeof answer, 1);
+ readln("Confirm: ", answer, sizeof answer, 1);
+ if(strcmp(p9pass, answer) == 0)
+ break;
+ print("mismatch, try again\n");
+ }
+ return 1;
+}
+
+void
+readln(char *prompt, char *line, int len, int raw)
+{
+ char *p;
+ int fdin, fdout, ctl, n, nr;
+
+ fdin = open("/dev/cons", OREAD);
+ fdout = open("/dev/cons", OWRITE);
+ fprint(fdout, "%s", prompt);
+ if(raw){
+ ctl = open("/dev/consctl", OWRITE);
+ if(ctl < 0)
+ error("couldn't set raw mode");
+ write(ctl, "rawon", 5);
+ } else
+ ctl = -1;
+ nr = 0;
+ p = line;
+ for(;;){
+ n = read(fdin, p, 1);
+ if(n < 0){
+ close(ctl);
+ error("can't read cons\n");
+ }
+ if(*p == 0x7f)
+ exits(0);
+ if(n == 0 || *p == '\n' || *p == '\r'){
+ *p = '\0';
+ if(raw){
+ write(ctl, "rawoff", 6);
+ write(fdout, "\n", 1);
+ }
+ close(ctl);
+ return;
+ }
+ if(*p == '\b'){
+ if(nr > 0){
+ nr--;
+ p--;
+ }
+ }else{
+ nr++;
+ p++;
+ }
+ if(nr == len){
+ fprint(fdout, "line too long; try again\n");
+ nr = 0;
+ p = line;
+ }
+ }
+}
diff --git a/sys/src/cmd/auth/lib/readn.c b/sys/src/cmd/auth/lib/readn.c
new file mode 100755
index 000000000..6f9324958
--- /dev/null
+++ b/sys/src/cmd/auth/lib/readn.c
@@ -0,0 +1,20 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+/*
+ * read exactly len bytes
+ */
+int
+readn(int fd, char *buf, int len)
+{
+ int m, n;
+
+ for(n = 0; n < len; n += m){
+ m = read(fd, buf+n, len-n);
+ if(m <= 0)
+ return -1;
+ }
+ return n;
+}
diff --git a/sys/src/cmd/auth/lib/readwrite.c b/sys/src/cmd/auth/lib/readwrite.c
new file mode 100755
index 000000000..54f494eb4
--- /dev/null
+++ b/sys/src/cmd/auth/lib/readwrite.c
@@ -0,0 +1,90 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+int
+readfile(char *file, char *buf, int n)
+{
+ int fd;
+
+ fd = open(file, OREAD);
+ if(fd < 0){
+ werrstr("%s: %r", file);
+ return -1;
+ }
+ n = read(fd, buf, n);
+ close(fd);
+ return n;
+}
+
+int
+writefile(char *file, char *buf, int n)
+{
+ int fd;
+
+ fd = open(file, OWRITE);
+ if(fd < 0)
+ return -1;
+ n = write(fd, buf, n);
+ close(fd);
+ return n;
+}
+
+char*
+findkey(char *db, char *user, char *key)
+{
+ int n;
+ char filename[Maxpath];
+
+ snprint(filename, sizeof filename, "%s/%s/key", db, user);
+ n = readfile(filename, key, DESKEYLEN);
+ if(n != DESKEYLEN)
+ return 0;
+ else
+ return key;
+}
+
+char*
+findsecret(char *db, char *user, char *secret)
+{
+ int n;
+ char filename[Maxpath];
+
+ snprint(filename, sizeof filename, "%s/%s/secret", db, user);
+ n = readfile(filename, secret, SECRETLEN-1);
+ secret[n]=0;
+ if(n <= 0)
+ return 0;
+ else
+ return secret;
+}
+
+char*
+setkey(char *db, char *user, char *key)
+{
+ int n;
+ char filename[Maxpath];
+
+ snprint(filename, sizeof filename, "%s/%s/key", db, user);
+ n = writefile(filename, key, DESKEYLEN);
+ if(n != DESKEYLEN)
+ return 0;
+ else
+ return key;
+}
+
+char*
+setsecret(char *db, char *user, char *secret)
+{
+ int n;
+ char filename[Maxpath];
+
+ snprint(filename, sizeof filename, "%s/%s/secret", db, user);
+ n = writefile(filename, secret, strlen(secret));
+ if(n != strlen(secret))
+ return 0;
+ else
+ return secret;
+}
diff --git a/sys/src/cmd/auth/lib/wrbio.c b/sys/src/cmd/auth/lib/wrbio.c
new file mode 100755
index 000000000..e50ca500b
--- /dev/null
+++ b/sys/src/cmd/auth/lib/wrbio.c
@@ -0,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "authcmdlib.h"
+
+void
+wrbio(char *file, Acctbio *a)
+{
+ char buf[1024];
+ int i, fd, n;
+
+ fd = open(file, OWRITE);
+ if(fd < 0)
+ error("can't open %s", file);
+ if(seek(fd, 0, 2) < 0)
+ error("can't seek %s", file);
+
+ if(a->postid == 0)
+ a->postid = "";
+ if(a->name == 0)
+ a->name = "";
+ if(a->dept == 0)
+ a->dept = "";
+ if(a->email[0] == 0)
+ a->email[0] = strdup(a->user);
+
+ n = 0;
+ n += snprint(buf+n, sizeof(buf)-n, "%s|%s|%s|%s",
+ a->user, a->postid, a->name, a->dept);
+ for(i = 0; i < Nemail; i++){
+ if(a->email[i] == 0)
+ break;
+ n += snprint(buf+n, sizeof(buf)-n, "|%s", a->email[i]);
+ }
+ n += snprint(buf+n, sizeof(buf)-n, "\n");
+
+ write(fd, buf, n);
+ close(fd);
+}
diff --git a/sys/src/cmd/auth/login.c b/sys/src/cmd/auth/login.c
new file mode 100755
index 000000000..4f487c513
--- /dev/null
+++ b/sys/src/cmd/auth/login.c
@@ -0,0 +1,216 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <authsrv.h>
+
+void
+readln(char *prompt, char *line, int len, int raw)
+{
+ char *p;
+ int fdin, fdout, ctl, n, nr;
+
+ fdin = open("/dev/cons", OREAD);
+ fdout = open("/dev/cons", OWRITE);
+ fprint(fdout, "%s", prompt);
+ if(raw){
+ ctl = open("/dev/consctl", OWRITE);
+ if(ctl < 0){
+ fprint(2, "login: couldn't set raw mode");
+ exits("readln");
+ }
+ write(ctl, "rawon", 5);
+ } else
+ ctl = -1;
+ nr = 0;
+ p = line;
+ for(;;){
+ n = read(fdin, p, 1);
+ if(n < 0){
+ close(ctl);
+ close(fdin);
+ close(fdout);
+ fprint(2, "login: can't read cons");
+ exits("readln");
+ }
+ if(*p == 0x7f)
+ exits(0);
+ if(n == 0 || *p == '\n' || *p == '\r'){
+ *p = '\0';
+ if(raw){
+ write(ctl, "rawoff", 6);
+ write(fdout, "\n", 1);
+ }
+ close(ctl);
+ close(fdin);
+ close(fdout);
+ return;
+ }
+ if(*p == '\b'){
+ if(nr > 0){
+ nr--;
+ p--;
+ }
+ }else{
+ nr++;
+ p++;
+ }
+ if(nr == len){
+ fprint(fdout, "line too long; try again\n");
+ nr = 0;
+ p = line;
+ }
+ }
+}
+
+void
+setenv(char *var, char *val)
+{
+ int fd;
+
+ fd = create(var, OWRITE, 0644);
+ if(fd < 0)
+ print("init: can't open %s\n", var);
+ else{
+ fprint(fd, val);
+ close(fd);
+ }
+}
+
+/*
+ * become the authenticated user
+ */
+void
+chuid(AuthInfo *ai)
+{
+ int rv, fd;
+
+ /* change uid */
+ fd = open("#¤/capuse", OWRITE);
+ if(fd < 0)
+ sysfatal("can't change uid: %r");
+ rv = write(fd, ai->cap, strlen(ai->cap));
+ close(fd);
+ if(rv < 0)
+ sysfatal("can't change uid: %r");
+}
+
+/*
+ * mount a factotum
+ */
+void
+mountfactotum(char *srvname)
+{
+ int fd;
+
+ /* mount it */
+ fd = open(srvname, ORDWR);
+ if(fd < 0)
+ sysfatal("opening factotum: %r");
+ mount(fd, -1, "/mnt", MBEFORE, "");
+ close(fd);
+}
+
+/*
+ * start a new factotum and pass it the username and password
+ */
+void
+startfactotum(char *user, char *password, char *srvname)
+{
+ int fd;
+
+ strcpy(srvname, "/srv/factotum.XXXXXXXXXXX");
+ mktemp(srvname);
+
+ switch(fork()){
+ case -1:
+ sysfatal("can't start factotum: %r");
+ case 0:
+ execl("/boot/factotum", "loginfactotum", "-ns", srvname+5, nil);
+ sysfatal("starting factotum: %r");
+ break;
+ }
+
+ /* wait for agent to really be there */
+ while(access(srvname, 0) < 0)
+ sleep(250);
+
+ /* mount it */
+ mountfactotum(srvname);
+
+ /* write in new key */
+ fd = open("/mnt/factotum/ctl", ORDWR);
+ if(fd < 0)
+ sysfatal("opening factotum: %r");
+ fprint(fd, "key proto=p9sk1 dom=cs.bell-labs.com user=%q !password=%q", user, password);
+ close(fd);
+}
+
+void
+main(int argc, char *argv[])
+{
+ char pass[ANAMELEN];
+ char buf[2*ANAMELEN];
+ char home[2*ANAMELEN];
+ char srvname[2*ANAMELEN];
+ char *user, *sysname, *tz, *cputype, *service;
+ AuthInfo *ai;
+
+ ARGBEGIN{
+ }ARGEND;
+
+ rfork(RFENVG|RFNAMEG);
+
+ service = getenv("service");
+ if(strcmp(service, "cpu") == 0)
+ fprint(2, "login: warning: running on a cpu server!\n");
+ if(argc != 1){
+ fprint(2, "usage: login username\n");
+ exits("usage");
+ }
+ user = argv[0];
+ memset(pass, 0, sizeof(pass));
+ readln("Password: ", pass, sizeof(pass), 1);
+
+ /* authenticate */
+ ai = auth_userpasswd(user, pass);
+ if(ai == nil || ai->cap == nil)
+ sysfatal("login incorrect");
+
+ /* change uid */
+ chuid(ai);
+
+ /* start a new factotum and hand it a new key */
+ startfactotum(user, pass, srvname);
+
+ /* set up new namespace */
+ newns(ai->cuid, nil);
+ auth_freeAI(ai);
+
+ /* remount the factotum */
+ mountfactotum(srvname);
+
+ /* set up a new environment */
+ cputype = getenv("cputype");
+ sysname = getenv("sysname");
+ tz = getenv("timezone");
+ rfork(RFCENVG);
+ setenv("#e/service", "con");
+ setenv("#e/user", user);
+ snprint(home, sizeof(home), "/usr/%s", user);
+ setenv("#e/home", home);
+ setenv("#e/cputype", cputype);
+ setenv("#e/objtype", cputype);
+ if(sysname != nil)
+ setenv("#e/sysname", sysname);
+ if(tz != nil)
+ setenv("#e/timezone", tz);
+
+ /* go to new home directory */
+ snprint(buf, sizeof(buf), "/usr/%s", user);
+ if(chdir(buf) < 0)
+ chdir("/");
+
+ /* read profile and start interactive rc */
+ execl("/bin/rc", "rc", "-li", nil);
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/mkfile b/sys/src/cmd/auth/mkfile
new file mode 100755
index 000000000..801daa1b0
--- /dev/null
+++ b/sys/src/cmd/auth/mkfile
@@ -0,0 +1,139 @@
+</$objtype/mkfile
+#
+# programs
+#
+TARG=\
+ as\
+ asn12dsa\
+ asn12rsa\
+ authsrv\
+ changeuser\
+ convkeys2\
+ convkeys\
+ cron\
+ debug\
+ dsa2pub\
+ dsa2ssh\
+ dsagen\
+ guard.srv\
+ keyfs\
+ login\
+ netkey\
+ newns\
+ none\
+ passwd\
+ pemdecode\
+ pemencode\
+ printnetkey\
+ readnvram\
+ rsa2csr\
+ rsa2pub\
+ rsa2ssh\
+ rsa2x509\
+ rsafill\
+ rsagen\
+ uniq\
+ warning\
+ wrkey\
+
+DIRS=lib\
+ factotum\
+ secstore\
+
+OTHEROFILES=\
+ challenge.$O\
+ convbio.$O\
+ respond.$O\
+ rsa2any.$O\
+ secureidcheck.$O\
+ userpasswd.$O\
+
+HFILES=/sys/include/auth.h /sys/include/authsrv.h authcmdlib.h
+
+LIB=lib.$O.a
+BIN=/$objtype/bin/auth
+CLIB=`{ls lib/*.c}
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ rsa2any.h\
+ ${OTHEROFILES:%.$O=%.c}\
+ ${TARG:%=%.c}\
+
+</sys/src/cmd/mkmany
+
+all:V: $DIRS
+
+$DIRS:V:
+ for (i in $DIRS) @{
+ echo mk $i
+ cd $i
+ mk all
+ }
+
+lib.$O.a:
+ cd lib
+ mk
+
+install:V: installdirs $BIN/status $BIN/enable $BIN/disable
+
+installdirs:V:
+ for (i in $DIRS) @{
+ echo mk $i
+ cd $i
+ mk install
+ }
+
+cleandirs:V:
+ for (i in $DIRS) @{
+ echo clean $i
+ cd $i
+ mk clean
+ }
+
+nukedirs:V:
+ for (i in $DIRS) @{
+ echo nuke $i
+ cd $i
+ mk nuke
+ }
+
+update:V:
+ update $UPDATEFLAGS $UPDATE
+ for (i in $DIRS) @{
+ echo update $i
+ cd $i
+ mk 'UPDATEFLAGS='$"UPDATEFLAGS update
+ }
+
+clean:V:
+ mk cleandirs
+ rm -f *.[$OS] *.[$OS].a [$OS].* y.tab.? y.debug y.output $TARG
+
+nuke:V:
+ mk nukedirs
+ rm -f *.[$OS] *.[$OS].a [$OS].* y.tab.? y.debug y.output $TARG *.acid
+
+$O.%: lib.$O.a
+$O.dsa2ssh $O.dsafill $O.dsa2x509 $O.dsa2pub $O.dsa2csr: rsa2any.$O
+$O.rsa2ssh $O.rsafill $O.rsa2x509 $O.rsa2pub $O.rsa2csr: rsa2any.$O
+$O.authsrv $O.guard.srv: secureidcheck.$O
+
+rsa2ssh.$O rsafill.$O rsa2x509.$O rsa2pub.$O rsa2csr.$O: rsa2any.h
+
+$BIN/netkey:V: $O.netkey
+ cp $O.netkey /$objtype/bin/netkey
+
+$BIN/passwd:V: $O.passwd
+ cp $O.passwd /$objtype/bin/passwd
+
+$BIN/status:V:
+ cp status $target
+
+$BIN/enable:V:
+ cp enable $target
+
+$BIN/disable:V:
+ cp disable $target
+
diff --git a/sys/src/cmd/auth/netkey.c b/sys/src/cmd/auth/netkey.c
new file mode 100755
index 000000000..ee7b10a28
--- /dev/null
+++ b/sys/src/cmd/auth/netkey.c
@@ -0,0 +1,49 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+
+void
+usage(void)
+{
+ fprint(2, "usage: netkey\n");
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ char buf[32], pass[32], key[DESKEYLEN];
+ char *s;
+ int n;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+ if(argc)
+ usage();
+
+ s = getenv("service");
+ if(s && strcmp(s, "cpu") == 0){
+ fprint(2, "netkey must not be run on the cpu server\n");
+ exits("boofhead");
+ }
+
+ readln("Password: ", pass, sizeof pass, 1);
+ passtokey(key, pass);
+
+ for(;;){
+ print("challenge: ");
+ n = read(0, buf, sizeof buf - 1);
+ if(n <= 0)
+ exits(0);
+ buf[n] = '\0';
+ n = strtol(buf, 0, 10);
+ sprint(buf, "%d", n);
+ netcrypt(key, buf);
+ print("response: %s\n", buf);
+ }
+}
diff --git a/sys/src/cmd/auth/newns.c b/sys/src/cmd/auth/newns.c
new file mode 100755
index 000000000..792e8e55c
--- /dev/null
+++ b/sys/src/cmd/auth/newns.c
@@ -0,0 +1,62 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: newns [-ad] [-n namespace] [cmd [args...]]\n");
+ exits("usage");
+}
+
+static int
+rooted(char *s)
+{
+ if(s[0] == '/')
+ return 1;
+ if(s[0] == '.' && s[1] == '/')
+ return 1;
+ if(s[0] == '.' && s[1] == '.' && s[2] == '/')
+ return 1;
+ return 0;
+}
+
+void
+main(int argc, char **argv)
+{
+ extern int newnsdebug;
+ char *defargv[] = { "/bin/rc", "-i", nil };
+ char *nsfile, err[ERRMAX];
+ int add;
+
+ rfork(RFNAMEG);
+ add = 0;
+ nsfile = "/lib/namespace";
+ ARGBEGIN{
+ case 'a':
+ add = 1;
+ break;
+ case 'd':
+ newnsdebug = 1;
+ break;
+ case 'n':
+ nsfile = ARGF();
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+ if(argc == 0)
+ argv = defargv;
+ if (add)
+ addns(getuser(), nsfile);
+ else
+ newns(getuser(), nsfile);
+ exec(argv[0], argv);
+ if(!rooted(argv[0])){
+ rerrstr(err, sizeof err);
+ exec(smprint("/bin/%s", argv[0]), argv);
+ errstr(err, sizeof err);
+ }
+ sysfatal("exec: %s: %r", argv[0]);
+}
diff --git a/sys/src/cmd/auth/none.c b/sys/src/cmd/auth/none.c
new file mode 100755
index 000000000..df2a2e5d2
--- /dev/null
+++ b/sys/src/cmd/auth/none.c
@@ -0,0 +1,55 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+char *namespace;
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/none [-n namespace] [cmd ...]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ char cmd[256];
+ int fd;
+
+ ARGBEGIN{
+ case 'n':
+ namespace = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if (rfork(RFENVG|RFNAMEG) < 0)
+ sysfatal("can't make new pgrp");
+
+ fd = open("#c/user", OWRITE);
+ if (fd < 0)
+ sysfatal("can't open #c/user");
+ if (write(fd, "none", strlen("none")) < 0)
+ sysfatal("can't become none");
+ close(fd);
+
+ if (newns("none", namespace) < 0)
+ sysfatal("can't build namespace");
+
+ if (argc > 0) {
+ strecpy(cmd, cmd+sizeof cmd, argv[0]);
+ exec(cmd, &argv[0]);
+ if (strncmp(cmd, "/", 1) != 0
+ && strncmp(cmd, "./", 2) != 0
+ && strncmp(cmd, "../", 3) != 0) {
+ snprint(cmd, sizeof cmd, "/bin/%s", argv[0]);
+ exec(cmd, &argv[0]);
+ }
+ } else {
+ strcpy(cmd, "/bin/rc");
+ execl(cmd, cmd, nil);
+ }
+ sysfatal(cmd);
+}
diff --git a/sys/src/cmd/auth/passwd.c b/sys/src/cmd/auth/passwd.c
new file mode 100755
index 000000000..cd9c98572
--- /dev/null
+++ b/sys/src/cmd/auth/passwd.c
@@ -0,0 +1,139 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+static char *pbmsg = "AS protocol botch";
+
+int
+asrdresp(int fd, char *buf, int len)
+{
+ char error[AERRLEN];
+
+ if(read(fd, buf, 1) != 1){
+ werrstr(pbmsg);
+ return -1;
+ }
+
+ switch(buf[0]){
+ case AuthOK:
+ if(readn(fd, buf, len) < 0){
+ werrstr(pbmsg);
+ return -1;
+ }
+ break;
+ case AuthErr:
+ if(readn(fd, error, AERRLEN) < 0){
+ werrstr(pbmsg);
+ return -1;
+ }
+ error[AERRLEN-1] = 0;
+ werrstr(error);
+ return -1;
+ default:
+ werrstr(pbmsg);
+ return -1;
+ }
+ return 0;
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd;
+ Ticketreq tr;
+ Ticket t;
+ Passwordreq pr;
+ char tbuf[TICKETLEN];
+ char key[DESKEYLEN];
+ char buf[512];
+ char *s, *user;
+
+ user = getuser();
+
+ ARGBEGIN{
+ }ARGEND
+
+ s = nil;
+ if(argc > 0){
+ user = argv[0];
+ s = strchr(user, '@');
+ if(s != nil)
+ *s++ = 0;
+ if(*user == 0)
+ user = getuser();
+ }
+
+ fd = authdial(nil, s);
+ if(fd < 0)
+ error("protocol botch: %r");
+
+ /* send ticket request to AS */
+ memset(&tr, 0, sizeof(tr));
+ strcpy(tr.uid, user);
+ tr.type = AuthPass;
+ convTR2M(&tr, buf);
+ if(write(fd, buf, TICKREQLEN) != TICKREQLEN)
+ error("protocol botch: %r");
+ if(asrdresp(fd, buf, TICKETLEN) < 0)
+ error("%r");
+ memmove(tbuf, buf, TICKETLEN);
+
+ /*
+ * get a password from the user and try to decrypt the
+ * ticket. If it doesn't work we've got a bad password,
+ * give up.
+ */
+ readln("Plan 9 Password: ", pr.old, sizeof pr.old, 1);
+ passtokey(key, pr.old);
+ convM2T(tbuf, &t, key);
+ if(t.num != AuthTp || strcmp(t.cuid, tr.uid))
+ error("bad password");
+
+ /* loop trying new passwords */
+ for(;;){
+ pr.changesecret = 0;
+ *pr.new = 0;
+ readln("change Plan 9 Password? (y/n) ", buf, sizeof buf, 0);
+ if(*buf == 'y' || *buf == 'Y'){
+ readln("Password(8 to 31 characters): ", pr.new,
+ sizeof pr.new, 1);
+ readln("Confirm: ", buf, sizeof buf, 1);
+ if(strcmp(pr.new, buf)){
+ print("!mismatch\n");
+ continue;
+ }
+ }
+ readln("change Inferno/POP password? (y/n) ", buf, sizeof buf, 0);
+ if(*buf == 'y' || *buf == 'Y'){
+ pr.changesecret = 1;
+ readln("make it the same as your plan 9 password? (y/n) ",
+ buf, sizeof buf, 0);
+ if(*buf == 'y' || *buf == 'Y'){
+ if(*pr.new == 0)
+ strcpy(pr.secret, pr.old);
+ else
+ strcpy(pr.secret, pr.new);
+ } else {
+ readln("Secret(0 to 256 characters): ", pr.secret,
+ sizeof pr.secret, 1);
+ readln("Confirm: ", buf, sizeof buf, 1);
+ if(strcmp(pr.secret, buf)){
+ print("!mismatch\n");
+ continue;
+ }
+ }
+ }
+ pr.num = AuthPass;
+ convPR2M(&pr, buf, t.key);
+ if(write(fd, buf, PASSREQLEN) != PASSREQLEN)
+ error("AS protocol botch: %r");
+ if(asrdresp(fd, buf, 0) == 0)
+ break;
+ fprint(2, "passwd: refused: %r\n");
+ }
+ close(fd);
+
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/pemdecode.c b/sys/src/cmd/auth/pemdecode.c
new file mode 100755
index 000000000..ceedb94d9
--- /dev/null
+++ b/sys/src/cmd/auth/pemdecode.c
@@ -0,0 +1,59 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
+
+void
+usage(void)
+{
+ fprint(2, "auth/pemdecode section [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *buf;
+ uchar *bin;
+ int fd;
+ long n, tot;
+ int len;
+ char *tag, *file;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1 && argc != 2)
+ usage();
+
+ tag = argv[0];
+ if(argc == 2)
+ file = argv[1];
+ else
+ file = "#d/0";
+
+ if((fd = open(file, OREAD)) < 0)
+ sysfatal("open %s: %r", file);
+ buf = nil;
+ tot = 0;
+ for(;;){
+ buf = realloc(buf, tot+8192);
+ if(buf == nil)
+ sysfatal("realloc: %r");
+ if((n = read(fd, buf+tot, 8192)) < 0)
+ sysfatal("read: %r");
+ if(n == 0)
+ break;
+ tot += n;
+ }
+ buf[tot] = 0;
+ bin = decodePEM(buf, tag, &len, nil);
+ if(bin == nil)
+ sysfatal("cannot extract section '%s' from pem", tag);
+ if((n=write(1, bin, len)) != len)
+ sysfatal("writing %d bytes got %ld: %r", len, n);
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/pemencode.c b/sys/src/cmd/auth/pemencode.c
new file mode 100755
index 000000000..b6e886fa5
--- /dev/null
+++ b/sys/src/cmd/auth/pemencode.c
@@ -0,0 +1,64 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
+
+void
+usage(void)
+{
+ fprint(2, "auth/pemdecode section [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *buf, *cbuf;
+ int fd;
+ long n, tot;
+ int len;
+ char *tag, *file;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1 && argc != 2)
+ usage();
+
+ tag = argv[0];
+ if(argc == 2)
+ file = argv[1];
+ else
+ file = "#d/0";
+
+ if((fd = open(file, OREAD)) < 0)
+ sysfatal("open %s: %r", file);
+ buf = nil;
+ tot = 0;
+ for(;;){
+ buf = realloc(buf, tot+8192);
+ if(buf == nil)
+ sysfatal("realloc: %r");
+ if((n = read(fd, buf+tot, 8192)) < 0)
+ sysfatal("read: %r");
+ if(n == 0)
+ break;
+ tot += n;
+ }
+ buf[tot] = 0;
+ cbuf = malloc(2*tot);
+ if(cbuf == nil)
+ sysfatal("malloc: %r");
+ len = enc64(cbuf, 2*tot, (uchar*)buf, tot);
+ print("-----BEGIN %s-----\n", tag);
+ while(len > 0){
+ print("%.64s\n", cbuf);
+ cbuf += 64;
+ len -= 64;
+ }
+ print("-----END %s-----\n", tag);
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/printnetkey.c b/sys/src/cmd/auth/printnetkey.c
new file mode 100755
index 000000000..5840bb8f8
--- /dev/null
+++ b/sys/src/cmd/auth/printnetkey.c
@@ -0,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+#include <bio.h>
+#include "authcmdlib.h"
+
+void install(char*, char*, int);
+void usage(void);
+
+void
+main(int argc, char *argv[])
+{
+ char *key;
+ char *u;
+ char keybuf[DESKEYLEN];
+
+ argv0 = "printnetkey";
+ fmtinstall('K', keyfmt);
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+ if(argc != 1)
+ usage();
+
+ u = argv[0];
+ fmtinstall('K', keyfmt);
+
+ if(memchr(u, '\0', ANAMELEN) == 0)
+ error("bad user name");
+ key = findkey(NETKEYDB, u, keybuf);
+ if(!key)
+ error("%s has no netkey\n", u);
+ print("user %s: net key %K\n", u, key);
+ exits(0);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: printnetkey user\n");
+ exits("usage");
+}
diff --git a/sys/src/cmd/auth/readnvram.c b/sys/src/cmd/auth/readnvram.c
new file mode 100755
index 000000000..9949de2de
--- /dev/null
+++ b/sys/src/cmd/auth/readnvram.c
@@ -0,0 +1,36 @@
+/* readnvram */
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <authsrv.h>
+
+void
+main(int, char **)
+{
+ int i;
+ Nvrsafe safe;
+
+ quotefmtinstall();
+
+ memset(&safe, 0, sizeof safe);
+ /*
+ * readnvram can return -1 meaning nvram wasn't written,
+ * but safe still holds good data.
+ */
+ if(readnvram(&safe, 0) < 0 && safe.authid[0] == '\0')
+ sysfatal("readnvram: %r");
+
+ /*
+ * only use nvram key if it is non-zero
+ */
+ for(i = 0; i < DESKEYLEN; i++)
+ if(safe.machkey[i] != 0)
+ break;
+ if(i == DESKEYLEN)
+ sysfatal("bad key");
+
+ fmtinstall('H', encodefmt);
+ print("key proto=p9sk1 user=%q dom=%q !hex=%.*H !password=______\n",
+ safe.authid, safe.authdom, DESKEYLEN, safe.machkey);
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/respond.c b/sys/src/cmd/auth/respond.c
new file mode 100755
index 000000000..c6634220b
--- /dev/null
+++ b/sys/src/cmd/auth/respond.c
@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/respond 'params' chal\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char buf[128];
+ int n;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 2)
+ usage();
+
+ memset(buf, 0, sizeof buf);
+ n = auth_respond(argv[1], strlen(argv[1]), buf, sizeof buf-1, auth_getkey, "%s", argv[0]);
+ if(n < 0)
+ sysfatal("auth_respond: %r");
+ write(1, buf, n);
+ print("\n");
+}
+
+ \ No newline at end of file
diff --git a/sys/src/cmd/auth/rsa2any.c b/sys/src/cmd/auth/rsa2any.c
new file mode 100755
index 000000000..32cd71338
--- /dev/null
+++ b/sys/src/cmd/auth/rsa2any.c
@@ -0,0 +1,301 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "rsa2any.h"
+
+RSApriv*
+getkey(int argc, char **argv, int needprivate, Attr **pa)
+{
+ char *file, *s, *p;
+ int sz;
+ RSApriv *key;
+ Biobuf *b;
+ int regen;
+ Attr *a;
+
+ if(argc == 0)
+ file = "#d/0";
+ else
+ file = argv[0];
+
+ key = mallocz(sizeof(RSApriv), 1);
+ if(key == nil)
+ return nil;
+
+ if((b = Bopen(file, OREAD)) == nil){
+ werrstr("open %s: %r", file);
+ return nil;
+ }
+ s = Brdstr(b, '\n', 1);
+ if(s == nil){
+ werrstr("read %s: %r", file);
+ return nil;
+ }
+ if(strncmp(s, "key ", 4) != 0){
+ werrstr("bad key format");
+ return nil;
+ }
+
+ regen = 0;
+ a = _parseattr(s+4);
+ if(a == nil){
+ werrstr("empty key");
+ return nil;
+ }
+ if((p = _strfindattr(a, "proto")) == nil){
+ werrstr("no proto");
+ return nil;
+ }
+ if(strcmp(p, "rsa") != 0){
+ werrstr("proto not rsa");
+ return nil;
+ }
+ if((p = _strfindattr(a, "ek")) == nil){
+ werrstr("no ek");
+ return nil;
+ }
+ if((key->pub.ek = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad ek");
+ return nil;
+ }
+ if((p = _strfindattr(a, "n")) == nil){
+ werrstr("no n");
+ return nil;
+ }
+ if((key->pub.n = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad n");
+ return nil;
+ }
+ if((p = _strfindattr(a, "size")) == nil)
+ fprint(2, "warning: missing size; will add\n");
+ else if((sz = strtol(p, &p, 10)) == 0 || *p != 0)
+ fprint(2, "warning: bad size; will correct\n");
+ else if(sz != mpsignif(key->pub.n))
+ fprint(2, "warning: wrong size (got %d, expected %d); will correct\n",
+ sz, mpsignif(key->pub.n));
+ if(!needprivate)
+ goto call;
+ if((p = _strfindattr(a, "!dk")) == nil){
+ werrstr("no !dk");
+ return nil;
+ }
+ if((key->dk = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad !dk");
+ return nil;
+ }
+ if((p = _strfindattr(a, "!p")) == nil){
+ werrstr("no !p");
+ return nil;
+ }
+ if((key->p = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad !p");
+ return nil;
+ }
+ if((p = _strfindattr(a, "!q")) == nil){
+ werrstr("no !q");
+ return nil;
+ }
+ if((key->q = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad !q");
+ return nil;
+ }
+ if((p = _strfindattr(a, "!kp")) == nil){
+ fprint(2, "warning: no !kp\n");
+ regen = 1;
+ goto regen;
+ }
+ if((key->kp = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ fprint(2, "warning: bad !kp\n");
+ regen = 1;
+ goto regen;
+ }
+ if((p = _strfindattr(a, "!kq")) == nil){
+ fprint(2, "warning: no !kq\n");
+ regen = 1;
+ goto regen;
+ }
+ if((key->kq = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ fprint(2, "warning: bad !kq\n");
+ regen = 1;
+ goto regen;
+ }
+ if((p = _strfindattr(a, "!c2")) == nil){
+ fprint(2, "warning: no !c2\n");
+ regen = 1;
+ goto regen;
+ }
+ if((key->c2 = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ fprint(2, "warning: bad !c2\n");
+ regen = 1;
+ goto regen;
+ }
+regen:
+ if(regen){
+ RSApriv *k2;
+
+ k2 = rsafill(key->pub.n, key->pub.ek, key->dk, key->p, key->q);
+ if(k2 == nil){
+ werrstr("regenerating chinese-remainder parts failed: %r");
+ return nil;
+ }
+ key = k2;
+ }
+call:
+ a = _delattr(a, "ek");
+ a = _delattr(a, "n");
+ a = _delattr(a, "size");
+ a = _delattr(a, "!dk");
+ a = _delattr(a, "!p");
+ a = _delattr(a, "!q");
+ a = _delattr(a, "!c2");
+ a = _delattr(a, "!kp");
+ a = _delattr(a, "!kq");
+ if(pa)
+ *pa = a;
+ return key;
+}
+
+DSApriv*
+getdsakey(int argc, char **argv, int needprivate, Attr **pa)
+{
+ char *file, *s, *p;
+ DSApriv *key;
+ Biobuf *b;
+ Attr *a;
+
+ if(argc == 0)
+ file = "#d/0";
+ else
+ file = argv[0];
+
+ key = mallocz(sizeof(RSApriv), 1);
+ if(key == nil)
+ return nil;
+
+ if((b = Bopen(file, OREAD)) == nil){
+ werrstr("open %s: %r", file);
+ return nil;
+ }
+ s = Brdstr(b, '\n', 1);
+ if(s == nil){
+ werrstr("read %s: %r", file);
+ return nil;
+ }
+ if(strncmp(s, "key ", 4) != 0){
+ werrstr("bad key format");
+ return nil;
+ }
+
+ a = _parseattr(s+4);
+ if(a == nil){
+ werrstr("empty key");
+ return nil;
+ }
+ if((p = _strfindattr(a, "proto")) == nil){
+ werrstr("no proto");
+ return nil;
+ }
+ if(strcmp(p, "dsa") != 0){
+ werrstr("proto not dsa");
+ return nil;
+ }
+ if((p = _strfindattr(a, "p")) == nil){
+ werrstr("no p");
+ return nil;
+ }
+ if((key->pub.p = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad p");
+ return nil;
+ }
+ if((p = _strfindattr(a, "q")) == nil){
+ werrstr("no q");
+ return nil;
+ }
+ if((key->pub.q = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad q");
+ return nil;
+ }
+ if((p = _strfindattr(a, "alpha")) == nil){
+ werrstr("no alpha");
+ return nil;
+ }
+ if((key->pub.alpha = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad alpha");
+ return nil;
+ }
+ if((p = _strfindattr(a, "key")) == nil){
+ werrstr("no key=");
+ return nil;
+ }
+ if((key->pub.key = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad key=");
+ return nil;
+ }
+ if(!needprivate)
+ goto call;
+ if((p = _strfindattr(a, "!secret")) == nil){
+ werrstr("no !secret");
+ return nil;
+ }
+ if((key->secret = strtomp(p, &p, 16, nil)) == nil || *p != 0){
+ werrstr("bad !secret");
+ return nil;
+ }
+call:
+ a = _delattr(a, "p");
+ a = _delattr(a, "q");
+ a = _delattr(a, "alpha");
+ a = _delattr(a, "key");
+ a = _delattr(a, "!secret");
+ if(pa)
+ *pa = a;
+ return key;
+}
+
+uchar*
+put4(uchar *p, uint n)
+{
+ p[0] = (n>>24)&0xFF;
+ p[1] = (n>>16)&0xFF;
+ p[2] = (n>>8)&0xFF;
+ p[3] = n&0xFF;
+ return p+4;
+}
+
+uchar*
+putn(uchar *p, void *v, uint n)
+{
+ memmove(p, v, n);
+ p += n;
+ return p;
+}
+
+uchar*
+putstr(uchar *p, char *s)
+{
+ p = put4(p, strlen(s));
+ p = putn(p, s, strlen(s));
+ return p;
+}
+
+uchar*
+putmp2(uchar *p, mpint *b)
+{
+ int bits, n;
+
+ if(mpcmp(b, mpzero) == 0)
+ return put4(p, 0);
+ bits = mpsignif(b);
+ n = (bits+7)/8;
+ if(bits%8 == 0){
+ p = put4(p, n+1);
+ *p++ = 0;
+ }else
+ p = put4(p, n);
+ mptobe(b, p, n, nil);
+ p += n;
+ return p;
+}
diff --git a/sys/src/cmd/auth/rsa2any.h b/sys/src/cmd/auth/rsa2any.h
new file mode 100755
index 000000000..5574edb44
--- /dev/null
+++ b/sys/src/cmd/auth/rsa2any.h
@@ -0,0 +1,6 @@
+DSApriv*getdsakey(int argc, char **argv, int needprivate, Attr **pa);
+RSApriv*getkey(int argc, char **argv, int needprivate, Attr **pa);
+uchar* put4(uchar *p, uint n);
+uchar* putmp2(uchar *p, mpint *b);
+uchar* putn(uchar *p, void *v, uint n);
+uchar* putstr(uchar *p, char *s);
diff --git a/sys/src/cmd/auth/rsa2csr.c b/sys/src/cmd/auth/rsa2csr.c
new file mode 100755
index 000000000..cba3f2d35
--- /dev/null
+++ b/sys/src/cmd/auth/rsa2csr.c
@@ -0,0 +1,43 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "rsa2any.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: aux/rsa2csr 'C=US ...CN=xxx' [key]");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int len;
+ uchar *cert;
+ RSApriv *key;
+
+ fmtinstall('B', mpfmt);
+ fmtinstall('H', encodefmt);
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1 && argc != 2)
+ usage();
+
+ if((key = getkey(argc-1, argv+1, 1, nil)) == nil)
+ sysfatal("%r");
+
+ cert = X509req(key, argv[0], &len);
+ if(cert == nil)
+ sysfatal("X509req: %r");
+
+ write(1, cert, len);
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/rsa2pub.c b/sys/src/cmd/auth/rsa2pub.c
new file mode 100755
index 000000000..7de105064
--- /dev/null
+++ b/sys/src/cmd/auth/rsa2pub.c
@@ -0,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "rsa2any.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/rsa2pub [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ RSApriv *key;
+ Attr *a;
+ char *s;
+
+ fmtinstall('A', _attrfmt);
+ fmtinstall('B', mpfmt);
+ quotefmtinstall();
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+
+ if((key = getkey(argc, argv, 0, &a)) == nil)
+ sysfatal("%r");
+
+ s = smprint("key %A size=%d ek=%B n=%B\n",
+ a,
+ mpsignif(key->pub.n), key->pub.ek, key->pub.n);
+ if(s == nil)
+ sysfatal("smprint: %r");
+ write(1, s, strlen(s));
+ exits(nil);
+}
diff --git a/sys/src/cmd/auth/rsa2ssh.c b/sys/src/cmd/auth/rsa2ssh.c
new file mode 100755
index 000000000..96f1b1ed0
--- /dev/null
+++ b/sys/src/cmd/auth/rsa2ssh.c
@@ -0,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "rsa2any.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/rsa2ssh [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ RSApriv *k;
+
+ fmtinstall('B', mpfmt);
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+
+ if((k = getkey(argc, argv, 0, nil)) == nil)
+ sysfatal("%r");
+
+ print("%d %.10B %.10B\n", mpsignif(k->pub.n), k->pub.ek, k->pub.n);
+ exits(nil);
+}
diff --git a/sys/src/cmd/auth/rsa2x509.c b/sys/src/cmd/auth/rsa2x509.c
new file mode 100755
index 000000000..a2bd67ccc
--- /dev/null
+++ b/sys/src/cmd/auth/rsa2x509.c
@@ -0,0 +1,50 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "rsa2any.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/rsa2x509 [-e expireseconds] 'C=US ...CN=xxx' [key]");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int len;
+ uchar *cert;
+ ulong valid[2];
+ RSApriv *key;
+
+ fmtinstall('B', mpfmt);
+ fmtinstall('H', encodefmt);
+
+ valid[0] = time(0);
+ valid[1] = valid[0] + 3*366*24*60*60;
+
+ ARGBEGIN{
+ default:
+ usage();
+ case 'e':
+ valid[1] = valid[0] + strtoul(ARGF(), 0, 10);
+ break;
+ }ARGEND
+
+ if(argc != 1 && argc != 2)
+ usage();
+
+ if((key = getkey(argc-1, argv+1, 1, nil)) == nil)
+ sysfatal("%r");
+
+ cert = X509gen(key, argv[0], valid, &len);
+ if(cert == nil)
+ sysfatal("X509gen: %r");
+
+ write(1, cert, len);
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/rsafill.c b/sys/src/cmd/auth/rsafill.c
new file mode 100755
index 000000000..b9e26d43b
--- /dev/null
+++ b/sys/src/cmd/auth/rsafill.c
@@ -0,0 +1,46 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "rsa2any.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/rsafill [file]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ RSApriv *key;
+ Attr *a;
+ char *s;
+
+ fmtinstall('A', _attrfmt);
+ fmtinstall('B', mpfmt);
+ quotefmtinstall();
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+
+ if((key = getkey(argc, argv, 1, &a)) == nil)
+ sysfatal("%r");
+
+ s = smprint("key %A size=%d ek=%B !dk=%B n=%B !p=%B !q=%B !kp=%B !kq=%B !c2=%B\n",
+ a,
+ mpsignif(key->pub.n), key->pub.ek,
+ key->dk, key->pub.n, key->p, key->q,
+ key->kp, key->kq, key->c2);
+ if(s == nil)
+ sysfatal("smprint: %r");
+ write(1, s, strlen(s));
+ exits(nil);
+}
diff --git a/sys/src/cmd/auth/rsagen.c b/sys/src/cmd/auth/rsagen.c
new file mode 100755
index 000000000..d05f562ef
--- /dev/null
+++ b/sys/src/cmd/auth/rsagen.c
@@ -0,0 +1,60 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/rsagen [-b bits] [-t 'attr=value attr=value ...']\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *s;
+ int bits;
+ char *tag;
+ RSApriv *key;
+
+ bits = 1024;
+ tag = nil;
+ key = nil;
+ fmtinstall('B', mpfmt);
+
+ ARGBEGIN{
+ case 'b':
+ bits = atoi(EARGF(usage()));
+ if(bits == 0)
+ usage();
+ break;
+ case 't':
+ tag = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 0)
+ usage();
+
+ do{
+ if(key)
+ rsaprivfree(key);
+ key = rsagen(bits, 6, 0);
+ }while(mpsignif(key->pub.n) != bits);
+
+ s = smprint("key proto=rsa %s%ssize=%d ek=%B !dk=%B n=%B !p=%B !q=%B !kp=%B !kq=%B !c2=%B\n",
+ tag ? tag : "", tag ? " " : "",
+ mpsignif(key->pub.n), key->pub.ek,
+ key->dk, key->pub.n, key->p, key->q,
+ key->kp, key->kq, key->c2);
+ if(s == nil)
+ sysfatal("smprint: %r");
+
+ if(write(1, s, strlen(s)) != strlen(s))
+ sysfatal("write: %r");
+
+ exits(nil);
+}
diff --git a/sys/src/cmd/auth/secstore/SConn.c b/sys/src/cmd/auth/secstore/SConn.c
new file mode 100755
index 000000000..69ea9de17
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/SConn.c
@@ -0,0 +1,212 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include "SConn.h"
+
+extern int verbose;
+
+typedef struct ConnState {
+ uchar secret[SHA1dlen];
+ ulong seqno;
+ RC4state rc4;
+} ConnState;
+
+typedef struct SS{
+ int fd; // file descriptor for read/write of encrypted data
+ int alg; // if nonzero, "alg sha rc4_128"
+ ConnState in, out;
+} SS;
+
+static int
+SC_secret(SConn *conn, uchar *sigma, int direction)
+{
+ SS *ss = (SS*)(conn->chan);
+ int nsigma = conn->secretlen;
+
+ if(direction != 0){
+ hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->out.secret, nil);
+ hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->in.secret, nil);
+ }else{
+ hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->out.secret, nil);
+ hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->in.secret, nil);
+ }
+ setupRC4state(&ss->in.rc4, ss->in.secret, 16); // restrict to 128 bits
+ setupRC4state(&ss->out.rc4, ss->out.secret, 16);
+ ss->alg = 1;
+ return 0;
+}
+
+static void
+hash(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen])
+{
+ DigestState sha;
+ uchar seq[4];
+
+ seq[0] = seqno>>24;
+ seq[1] = seqno>>16;
+ seq[2] = seqno>>8;
+ seq[3] = seqno;
+ memset(&sha, 0, sizeof sha);
+ sha1(secret, SHA1dlen, nil, &sha);
+ sha1(data, len, nil, &sha);
+ sha1(seq, 4, d, &sha);
+}
+
+static int
+verify(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen])
+{
+ DigestState sha;
+ uchar seq[4];
+ uchar digest[SHA1dlen];
+
+ seq[0] = seqno>>24;
+ seq[1] = seqno>>16;
+ seq[2] = seqno>>8;
+ seq[3] = seqno;
+ memset(&sha, 0, sizeof sha);
+ sha1(secret, SHA1dlen, nil, &sha);
+ sha1(data, len, nil, &sha);
+ sha1(seq, 4, digest, &sha);
+ return memcmp(d, digest, SHA1dlen);
+}
+
+static int
+SC_read(SConn *conn, uchar *buf, int n)
+{
+ SS *ss = (SS*)(conn->chan);
+ uchar count[2], digest[SHA1dlen];
+ int len, nr;
+
+ if(read(ss->fd, count, 2) != 2 || (count[0]&0x80) == 0){
+ snprint((char*)buf,n,"!SC_read invalid count");
+ return -1;
+ }
+ len = (count[0]&0x7f)<<8 | count[1]; // SSL-style count; no pad
+ if(ss->alg){
+ len -= SHA1dlen;
+ if(len <= 0 || readn(ss->fd, digest, SHA1dlen) != SHA1dlen){
+ snprint((char*)buf,n,"!SC_read missing sha1");
+ return -1;
+ }
+ if(len > n || readn(ss->fd, buf, len) != len){
+ snprint((char*)buf,n,"!SC_read missing data");
+ return -1;
+ }
+ rc4(&ss->in.rc4, digest, SHA1dlen);
+ rc4(&ss->in.rc4, buf, len);
+ if(verify(ss->in.secret, buf, len, ss->in.seqno, digest) != 0){
+ snprint((char*)buf,n,"!SC_read integrity check failed");
+ return -1;
+ }
+ }else{
+ if(len <= 0 || len > n){
+ snprint((char*)buf,n,"!SC_read implausible record length");
+ return -1;
+ }
+ if( (nr = readn(ss->fd, buf, len)) != len){
+ snprint((char*)buf,n,"!SC_read expected %d bytes, but got %d", len, nr);
+ return -1;
+ }
+ }
+ ss->in.seqno++;
+ return len;
+}
+
+static int
+SC_write(SConn *conn, uchar *buf, int n)
+{
+ SS *ss = (SS*)(conn->chan);
+ uchar count[2], digest[SHA1dlen], enc[Maxmsg+1];
+ int len;
+
+ if(n <= 0 || n > Maxmsg+1){
+ werrstr("!SC_write invalid n %d", n);
+ return -1;
+ }
+ len = n;
+ if(ss->alg)
+ len += SHA1dlen;
+ count[0] = 0x80 | len>>8;
+ count[1] = len;
+ if(write(ss->fd, count, 2) != 2){
+ werrstr("!SC_write invalid count");
+ return -1;
+ }
+ if(ss->alg){
+ hash(ss->out.secret, buf, n, ss->out.seqno, digest);
+ rc4(&ss->out.rc4, digest, SHA1dlen);
+ memcpy(enc, buf, n);
+ rc4(&ss->out.rc4, enc, n);
+ if(write(ss->fd, digest, SHA1dlen) != SHA1dlen ||
+ write(ss->fd, enc, n) != n){
+ werrstr("!SC_write error on send");
+ return -1;
+ }
+ }else{
+ if(write(ss->fd, buf, n) != n){
+ werrstr("!SC_write error on send");
+ return -1;
+ }
+ }
+ ss->out.seqno++;
+ return n;
+}
+
+static void
+SC_free(SConn *conn)
+{
+ SS *ss = (SS*)(conn->chan);
+
+ close(ss->fd);
+ free(ss);
+ free(conn);
+}
+
+SConn*
+newSConn(int fd)
+{
+ SS *ss;
+ SConn *conn;
+
+ if(fd < 0)
+ return nil;
+ ss = (SS*)emalloc(sizeof(*ss));
+ conn = (SConn*)emalloc(sizeof(*conn));
+ ss->fd = fd;
+ ss->alg = 0;
+ conn->chan = (void*)ss;
+ conn->secretlen = SHA1dlen;
+ conn->free = SC_free;
+ conn->secret = SC_secret;
+ conn->read = SC_read;
+ conn->write = SC_write;
+ return conn;
+}
+
+void
+writerr(SConn *conn, char *s)
+{
+ char buf[Maxmsg];
+
+ snprint(buf, Maxmsg, "!%s", s);
+ conn->write(conn, (uchar*)buf, strlen(buf));
+}
+
+int
+readstr(SConn *conn, char *s)
+{
+ int n;
+
+ n = conn->read(conn, (uchar*)s, Maxmsg);
+ if(n >= 0){
+ s[n] = 0;
+ if(s[0] == '!'){
+ memmove(s, s+1, n);
+ n = -1;
+ }
+ }else{
+ strcpy(s, "connection read error");
+ }
+ return n;
+}
diff --git a/sys/src/cmd/auth/secstore/SConn.h b/sys/src/cmd/auth/secstore/SConn.h
new file mode 100755
index 000000000..1c0f222ff
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/SConn.h
@@ -0,0 +1,35 @@
+/* delimited, authenticated, encrypted connection */
+enum {
+ Maxmsg = 4096, /* messages > Maxmsg bytes are truncated */
+};
+
+typedef struct SConn SConn;
+struct SConn {
+ void *chan;
+ int secretlen;
+ int (*secret)(SConn*, uchar*, int);
+ int (*read)(SConn*, uchar*, int); /* <0 if error; errmess in buffer */
+ int (*write)(SConn*, uchar*, int);
+ void (*free)(SConn*); /* also closes file descriptor */
+};
+
+SConn *newSConn(int); /* arg is open file descriptor */
+
+/*
+ * secret(s,b,dir) sets secret for digest, encrypt, using the secretlen
+ * bytes in b to form keys for the two directions;
+ * set dir=0 in client, dir=1 in server
+ */
+
+/* error convention: write !message in-band */
+void writerr(SConn*, char*);
+
+/*
+ * returns -1 upon error, with error message in buf
+ * call with buf of size Maxmsg+1
+ */
+int readstr(SConn*, char*);
+
+void *emalloc(ulong); /* dies on failure; clears memory */
+void *erealloc(void*, ulong);
+char *estrdup(char*);
diff --git a/sys/src/cmd/auth/secstore/aescbc.c b/sys/src/cmd/auth/secstore/aescbc.c
new file mode 100755
index 000000000..a7a0e984a
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/aescbc.c
@@ -0,0 +1,150 @@
+/* encrypt file by writing
+ v2hdr,
+ 16byte initialization vector,
+ AES-CBC(key, random | file),
+ HMAC_SHA1(md5(key), AES-CBC(random | file))
+*/
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
+#include <authsrv.h>
+
+extern char* getpassm(char*);
+
+enum{ CHK = 16, BUF = 4096 };
+
+uchar v2hdr[AESbsize+1] = "AES CBC SHA1 2\n";
+Biobuf bin;
+Biobuf bout;
+
+void
+safewrite(uchar *buf, int n)
+{
+ if(Bwrite(&bout, buf, n) != n)
+ sysfatal("write error");
+}
+
+void
+saferead(uchar *buf, int n)
+{
+ if(Bread(&bin, buf, n) != n)
+ sysfatal("read error");
+}
+
+int
+main(int argc, char **argv)
+{
+ int encrypt = 0; /* 0=decrypt, 1=encrypt */
+ int n, nkey, pass_stdin = 0, pass_nvram = 0;
+ char *pass;
+ uchar key[AESmaxkey], key2[SHA1dlen];
+ uchar buf[BUF+SHA1dlen]; /* assumption: CHK <= SHA1dlen */
+ AESstate aes;
+ DigestState *dstate;
+ Nvrsafe nvr;
+
+ ARGBEGIN{
+ case 'e':
+ encrypt = 1;
+ break;
+ case 'i':
+ pass_stdin = 1;
+ break;
+ case 'n':
+ pass_nvram = 1;
+ break;
+ }ARGEND;
+ if(argc!=0){
+ fprint(2,"usage: %s -d < cipher.aes > clear.txt\n", argv0);
+ fprint(2," or: %s -e < clear.txt > cipher.aes\n", argv0);
+ exits("usage");
+ }
+ Binit(&bin, 0, OREAD);
+ Binit(&bout, 1, OWRITE);
+
+ if(pass_stdin){
+ n = readn(3, buf, (sizeof buf)-1);
+ if(n < 1)
+ exits("usage: echo password |[3=1] auth/aescbc -i ...");
+ buf[n] = 0;
+ while(buf[n-1] == '\n')
+ buf[--n] = 0;
+ }else if(pass_nvram){
+ if(readnvram(&nvr, 0) < 0)
+ exits("readnvram: %r");
+ strecpy((char*)buf, (char*)buf+sizeof buf, (char*)nvr.config);
+ n = strlen((char*)buf);
+ }else{
+ pass = getpassm("aescbc key:");
+ n = strlen(pass);
+ if(n >= BUF)
+ exits("key too long");
+ strcpy((char*)buf, pass);
+ memset(pass, 0, n);
+ free(pass);
+ }
+ if(n <= 0)
+ sysfatal("no key");
+ dstate = sha1((uchar*)"aescbc file", 11, nil, nil);
+ sha1(buf, n, key2, dstate);
+ memcpy(key, key2, 16);
+ nkey = 16;
+ md5(key, nkey, key2, 0); /* so even if HMAC_SHA1 is broken, encryption key is protected */
+
+ if(encrypt){
+ safewrite(v2hdr, AESbsize);
+ genrandom(buf,2*AESbsize); /* CBC is semantically secure if IV is unpredictable. */
+ setupAESstate(&aes, key, nkey, buf); /* use first AESbsize bytes as IV */
+ aesCBCencrypt(buf+AESbsize, AESbsize, &aes); /* use second AESbsize bytes as initial plaintext */
+ safewrite(buf, 2*AESbsize);
+ dstate = hmac_sha1(buf+AESbsize, AESbsize, key2, MD5dlen, 0, 0);
+ for(;;){
+ n = Bread(&bin, buf, BUF);
+ if(n < 0)
+ sysfatal("read error");
+ aesCBCencrypt(buf, n, &aes);
+ safewrite(buf, n);
+ dstate = hmac_sha1(buf, n, key2, MD5dlen, 0, dstate);
+ if(n < BUF)
+ break; /* EOF */
+ }
+ hmac_sha1(0, 0, key2, MD5dlen, buf, dstate);
+ safewrite(buf, SHA1dlen);
+ }else{ /* decrypt */
+ saferead(buf, AESbsize);
+ if(memcmp(buf, v2hdr, AESbsize) == 0){
+ saferead(buf, 2*AESbsize); /* read IV and random initial plaintext */
+ setupAESstate(&aes, key, nkey, buf);
+ dstate = hmac_sha1(buf+AESbsize, AESbsize, key2, MD5dlen, 0, 0);
+ aesCBCdecrypt(buf+AESbsize, AESbsize, &aes);
+ saferead(buf, SHA1dlen);
+ while((n = Bread(&bin, buf+SHA1dlen, BUF)) > 0){
+ dstate = hmac_sha1(buf, n, key2, MD5dlen, 0, dstate);
+ aesCBCdecrypt(buf, n, &aes);
+ safewrite(buf, n);
+ memmove(buf, buf+n, SHA1dlen); /* these bytes are not yet decrypted */
+ }
+ hmac_sha1(0, 0, key2, MD5dlen, buf+SHA1dlen, dstate);
+ if(memcmp(buf, buf+SHA1dlen, SHA1dlen) != 0)
+ sysfatal("decrypted file failed to authenticate");
+ }else{ /* compatibility with past mistake */
+ // if file was encrypted with bad aescbc use this:
+ // memset(key, 0, AESmaxkey);
+ // else assume we're decrypting secstore files
+ setupAESstate(&aes, key, AESbsize, buf);
+ saferead(buf, CHK);
+ aesCBCdecrypt(buf, CHK, &aes);
+ while((n = Bread(&bin, buf+CHK, BUF)) > 0){
+ aesCBCdecrypt(buf+CHK, n, &aes);
+ safewrite(buf, n);
+ memmove(buf, buf+n, CHK);
+ }
+ if(memcmp(buf, "XXXXXXXXXXXXXXXX", CHK) != 0)
+ sysfatal("decrypted file failed to authenticate");
+ }
+ }
+ exits("");
+ return 1; /* keep other compilers happy */
+}
diff --git a/sys/src/cmd/auth/secstore/dirls.c b/sys/src/cmd/auth/secstore/dirls.c
new file mode 100755
index 000000000..0af9c410e
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/dirls.c
@@ -0,0 +1,87 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include "SConn.h"
+
+static long
+ls(char *p, Dir **dirbuf)
+{
+ int fd;
+ long n;
+ Dir *db;
+
+ if((db = dirstat(p)) == nil ||
+ !(db->qid.type & QTDIR) ||
+ (fd = open(p, OREAD)) < 0 )
+ return -1;
+ free(db);
+ n = dirreadall(fd, dirbuf);
+ close(fd);
+ return n;
+}
+
+static uchar*
+sha1file(char *pfx, char *nm)
+{
+ int n, fd, len;
+ char *tmp;
+ uchar buf[8192];
+ static uchar digest[SHA1dlen];
+ DigestState *s;
+
+ len = strlen(pfx)+1+strlen(nm)+1;
+ tmp = emalloc(len);
+ snprint(tmp, len, "%s/%s", pfx, nm);
+ if((fd = open(tmp, OREAD)) < 0){
+ free(tmp);
+ return nil;
+ }
+ free(tmp);
+ s = nil;
+ while((n = read(fd, buf, sizeof buf)) > 0)
+ s = sha1(buf, n, nil, s);
+ close(fd);
+ sha1(nil, 0, digest, s);
+ return digest;
+}
+
+static int
+compare(Dir *a, Dir *b)
+{
+ return strcmp(a->name, b->name);
+}
+
+/* list the (name mtime size sum) of regular, readable files in path */
+char *
+dirls(char *path)
+{
+ char *list, *date, dig[30], buf[128];
+ int m, nmwid, lenwid;
+ long i, n, ndir, len;
+ Dir *dirbuf;
+
+ if(path==nil || (ndir = ls(path, &dirbuf)) < 0)
+ return nil;
+
+ qsort(dirbuf, ndir, sizeof dirbuf[0], (int (*)(void *, void *))compare);
+ for(nmwid=lenwid=i=0; i<ndir; i++){
+ if((m = strlen(dirbuf[i].name)) > nmwid)
+ nmwid = m;
+ snprint(buf, sizeof(buf), "%ulld", dirbuf[i].length);
+ if((m = strlen(buf)) > lenwid)
+ lenwid = m;
+ }
+ for(list=nil, len=0, i=0; i<ndir; i++){
+ date = ctime(dirbuf[i].mtime);
+ date[28] = 0; // trim newline
+ n = snprint(buf, sizeof buf, "%*ulld %s", lenwid, dirbuf[i].length, date+4);
+ n += enc64(dig, sizeof dig, sha1file(path, dirbuf[i].name), SHA1dlen);
+ n += nmwid+3+strlen(dirbuf[i].name);
+ list = erealloc(list, len+n+1);
+ len += snprint(list+len, n+1, "%-*s\t%s %s\n", nmwid, dirbuf[i].name, buf, dig);
+ }
+ free(dirbuf);
+ return list;
+}
+
diff --git a/sys/src/cmd/auth/secstore/mkfile b/sys/src/cmd/auth/secstore/mkfile
new file mode 100755
index 000000000..b8a3955e4
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/mkfile
@@ -0,0 +1,51 @@
+</$objtype/mkfile
+BIN=/$objtype/bin/auth
+HFILES =\
+ SConn.h\
+ secstore.h\
+
+OFILES =\
+ pak.$O\
+ password.$O\
+ SConn.$O\
+ util.$O\
+
+
+TARG = secstore secstored secuser aescbc # descbc
+DEBUG = secchk
+
+UPDATE=\
+ $HFILES\
+ dirls.c\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=%.c}\
+ ${DEBUG:%=%.c}\
+ mkfile\
+ /sys/man/1/secstore\
+
+default:V: all
+
+$O.aescbc: aescbc.$O util.$O
+ $LD -o $target $prereq
+$O.descbc: descbc.$O util.$O
+ $LD -o $target $prereq
+
+$O.secstore: secstore.$O $OFILES
+ $LD -o $target $prereq
+
+$O.secstored: secstored.$O dirls.$O secureidcheck.$O $OFILES
+ $LD -o $target $prereq
+
+$O.secuser: secuser.$O $OFILES
+ $LD -o $target $prereq
+
+secstore.$O secstored.$O $OFILES: $HFILES
+
+secureidcheck.$O: ../secureidcheck.c
+ $CC $CFLAGS ../secureidcheck.c
+
+# debugging
+$O.secchk: secchk.$O secureidcheck.$O
+ $LD -o $target $prereq
+
+</sys/src/cmd/mkmany
diff --git a/sys/src/cmd/auth/secstore/pak.c b/sys/src/cmd/auth/secstore/pak.c
new file mode 100755
index 000000000..eba814146
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/pak.c
@@ -0,0 +1,344 @@
+// PAK is an encrypted key exchange protocol designed by Philip MacKenzie et al.
+// It is patented and use outside Plan 9 requires you get a license.
+// (All other EKE protocols are patented as well, by Lucent or others.)
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include "SConn.h"
+#include "secstore.h"
+
+extern int verbose;
+
+char VERSION[] = "secstore";
+static char *feedback[] = {"alpha","bravo","charlie","delta","echo","foxtrot","golf","hotel"};
+
+typedef struct PAKparams{
+ mpint *q, *p, *r, *g;
+} PAKparams;
+
+static PAKparams *pak;
+
+// from seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E
+static void
+initPAKparams(void)
+{
+ if(pak)
+ return;
+ pak = (PAKparams*)emalloc(sizeof(*pak));
+ pak->q = strtomp("E0F0EF284E10796C5A2A511E94748BA03C795C13", nil, 16, nil);
+ pak->p = strtomp("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBB"
+ "DB12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86"
+ "3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9"
+ "3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D",
+ nil, 16, nil);
+ pak->r = strtomp("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241"
+ "CEF2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E"
+ "887D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D"
+ "21C4656848614D888A4", nil, 16, nil);
+ pak->g = strtomp("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D23271734"
+ "44ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD"
+ "410E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734"
+ "E3E2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1",
+ nil, 16, nil);
+}
+
+// H = (sha(ver,C,sha(passphrase)))^r mod p,
+// a hash function expensive to attack by brute force.
+static void
+longhash(char *ver, char *C, uchar *passwd, mpint *H)
+{
+ uchar *Cp;
+ int i, n, nver, nC;
+ uchar buf[140], key[1];
+
+ nver = strlen(ver);
+ nC = strlen(C);
+ n = nver + nC + SHA1dlen;
+ Cp = (uchar*)emalloc(n);
+ memmove(Cp, ver, nver);
+ memmove(Cp+nver, C, nC);
+ memmove(Cp+nver+nC, passwd, SHA1dlen);
+ for(i = 0; i < 7; i++){
+ key[0] = 'A'+i;
+ hmac_sha1(Cp, n, key, sizeof key, buf+i*SHA1dlen, nil);
+ }
+ memset(Cp, 0, n);
+ free(Cp);
+ betomp(buf, sizeof buf, H);
+ mpmod(H, pak->p, H);
+ mpexp(H, pak->r, pak->p, H);
+}
+
+// Hi = H^-1 mod p
+char *
+PAK_Hi(char *C, char *passphrase, mpint *H, mpint *Hi)
+{
+ uchar passhash[SHA1dlen];
+
+ sha1((uchar *)passphrase, strlen(passphrase), passhash, nil);
+ initPAKparams();
+ longhash(VERSION, C, passhash, H);
+ mpinvert(H, pak->p, Hi);
+ return mptoa(Hi, 64, nil, 0);
+}
+
+// another, faster, hash function for each party to
+// confirm that the other has the right secrets.
+static void
+shorthash(char *mess, char *C, char *S, char *m, char *mu, char *sigma, char *Hi, uchar *digest)
+{
+ SHA1state *state;
+
+ state = sha1((uchar*)mess, strlen(mess), 0, 0);
+ state = sha1((uchar*)C, strlen(C), 0, state);
+ state = sha1((uchar*)S, strlen(S), 0, state);
+ state = sha1((uchar*)m, strlen(m), 0, state);
+ state = sha1((uchar*)mu, strlen(mu), 0, state);
+ state = sha1((uchar*)sigma, strlen(sigma), 0, state);
+ state = sha1((uchar*)Hi, strlen(Hi), 0, state);
+ state = sha1((uchar*)mess, strlen(mess), 0, state);
+ state = sha1((uchar*)C, strlen(C), 0, state);
+ state = sha1((uchar*)S, strlen(S), 0, state);
+ state = sha1((uchar*)m, strlen(m), 0, state);
+ state = sha1((uchar*)mu, strlen(mu), 0, state);
+ state = sha1((uchar*)sigma, strlen(sigma), 0, state);
+ sha1((uchar*)Hi, strlen(Hi), digest, state);
+}
+
+// On input, conn provides an open channel to the server;
+// C is the name this client calls itself;
+// pass is the user's passphrase
+// On output, session secret has been set in conn
+// (unless return code is negative, which means failure).
+// If pS is not nil, it is set to the (alloc'd) name the server calls itself.
+int
+PAKclient(SConn *conn, char *C, char *pass, char **pS)
+{
+ char *mess, *mess2, *eol, *S, *hexmu, *ks, *hexm, *hexsigma = nil, *hexHi;
+ char kc[2*SHA1dlen+1];
+ uchar digest[SHA1dlen];
+ int rc = -1, n;
+ mpint *x, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0);
+ mpint *H = mpnew(0), *Hi = mpnew(0);
+
+ hexHi = PAK_Hi(C, pass, H, Hi);
+ if(verbose)
+ fprint(2, "%s\n", feedback[H->p[0]&0x7]); // provide a clue to catch typos
+
+ // random 1<=x<=q-1; send C, m=g**x H
+ x = mprand(240, genrandom, nil);
+ mpmod(x, pak->q, x);
+ if(mpcmp(x, mpzero) == 0)
+ mpassign(mpone, x);
+ mpexp(pak->g, x, pak->p, m);
+ mpmul(m, H, m);
+ mpmod(m, pak->p, m);
+ hexm = mptoa(m, 64, nil, 0);
+ mess = (char*)emalloc(2*Maxmsg+2);
+ mess2 = mess+Maxmsg+1;
+ snprint(mess, Maxmsg, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm);
+ conn->write(conn, (uchar*)mess, strlen(mess));
+
+ // recv g**y, S, check hash1(g**xy)
+ if(readstr(conn, mess) < 0){
+ fprint(2, "%s: error: %s\n", argv0, mess);
+ writerr(conn, "couldn't read g**y");
+ goto done;
+ }
+ eol = strchr(mess, '\n');
+ if(strncmp("mu=", mess, 3) != 0 || !eol || strncmp("\nk=", eol, 3) != 0){
+ writerr(conn, "verifier syntax error");
+ goto done;
+ }
+ hexmu = mess+3;
+ *eol = 0;
+ ks = eol+3;
+ eol = strchr(ks, '\n');
+ if(!eol || strncmp("\nS=", eol, 3) != 0){
+ writerr(conn, "verifier syntax error for secstore 1.0");
+ goto done;
+ }
+ *eol = 0;
+ S = eol+3;
+ eol = strchr(S, '\n');
+ if(!eol){
+ writerr(conn, "verifier syntax error for secstore 1.0");
+ goto done;
+ }
+ *eol = 0;
+ if(pS)
+ *pS = estrdup(S);
+ strtomp(hexmu, nil, 64, mu);
+ mpexp(mu, x, pak->p, sigma);
+ hexsigma = mptoa(sigma, 64, nil, 0);
+ shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ enc64(kc, sizeof kc, digest, SHA1dlen);
+ if(strcmp(ks, kc) != 0){
+ writerr(conn, "verifier didn't match");
+ goto done;
+ }
+
+ // send hash2(g**xy)
+ shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ enc64(kc, sizeof kc, digest, SHA1dlen);
+ snprint(mess2, Maxmsg, "k'=%s\n", kc);
+ conn->write(conn, (uchar*)mess2, strlen(mess2));
+
+ // set session key
+ shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ memset(hexsigma, 0, strlen(hexsigma));
+ n = conn->secret(conn, digest, 0);
+ memset(digest, 0, SHA1dlen);
+ if(n < 0){
+ writerr(conn, "can't set secret");
+ goto done;
+ }
+
+ rc = 0;
+done:
+ mpfree(x);
+ mpfree(sigma);
+ mpfree(mu);
+ mpfree(m);
+ mpfree(Hi);
+ mpfree(H);
+ free(hexsigma);
+ free(hexHi);
+ free(hexm);
+ free(mess);
+ return rc;
+}
+
+// On input,
+// mess contains first message;
+// name is name this server should call itself.
+// On output, session secret has been set in conn;
+// if pw!=nil, then *pw points to PW struct for authenticated user.
+// returns -1 if error
+int
+PAKserver(SConn *conn, char *S, char *mess, PW **pwp)
+{
+ int rc = -1, n;
+ char mess2[Maxmsg+1], *eol;
+ char *C, ks[41], *kc, *hexm, *hexmu = nil, *hexsigma = nil, *hexHi = nil;
+ uchar digest[SHA1dlen];
+ mpint *H = mpnew(0), *Hi = mpnew(0);
+ mpint *y = nil, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0);
+ PW *pw = nil;
+
+ // secstore version and algorithm
+ snprint(mess2,Maxmsg,"%s\tPAK\n", VERSION);
+ n = strlen(mess2);
+ if(strncmp(mess,mess2,n) != 0){
+ writerr(conn, "protocol should start with ver alg");
+ return -1;
+ }
+ mess += n;
+ initPAKparams();
+
+ // parse first message into C, m
+ eol = strchr(mess, '\n');
+ if(strncmp("C=", mess, 2) != 0 || !eol){
+ fprint(2, "%s: mess[1]=%s\n", argv0, mess);
+ writerr(conn, "PAK version mismatch");
+ goto done;
+ }
+ C = mess+2;
+ *eol = 0;
+ hexm = eol+3;
+ eol = strchr(hexm, '\n');
+ if(strncmp("m=", hexm-2, 2) != 0 || !eol){
+ writerr(conn, "PAK version mismatch");
+ goto done;
+ }
+ *eol = 0;
+ strtomp(hexm, nil, 64, m);
+ mpmod(m, pak->p, m);
+
+ // lookup client
+ if((pw = getPW(C,0)) == nil) {
+ snprint(mess2, sizeof mess2, "%r");
+ writerr(conn, mess2);
+ goto done;
+ }
+ if(mpcmp(m, mpzero) == 0) {
+ writerr(conn, "account exists");
+ freePW(pw);
+ pw = nil;
+ goto done;
+ }
+ hexHi = mptoa(pw->Hi, 64, nil, 0);
+
+ // random y, mu=g**y, sigma=g**xy
+ y = mprand(240, genrandom, nil);
+ mpmod(y, pak->q, y);
+ if(mpcmp(y, mpzero) == 0){
+ mpassign(mpone, y);
+ }
+ mpexp(pak->g, y, pak->p, mu);
+ mpmul(m, pw->Hi, m);
+ mpmod(m, pak->p, m);
+ mpexp(m, y, pak->p, sigma);
+
+ // send g**y, hash1(g**xy)
+ hexmu = mptoa(mu, 64, nil, 0);
+ hexsigma = mptoa(sigma, 64, nil, 0);
+ shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ enc64(ks, sizeof ks, digest, SHA1dlen);
+ snprint(mess2, sizeof mess2, "mu=%s\nk=%s\nS=%s\n", hexmu, ks, S);
+ conn->write(conn, (uchar*)mess2, strlen(mess2));
+
+ // recv hash2(g**xy)
+ if(readstr(conn, mess2) < 0){
+ writerr(conn, "couldn't read verifier");
+ goto done;
+ }
+ eol = strchr(mess2, '\n');
+ if(strncmp("k'=", mess2, 3) != 0 || !eol){
+ writerr(conn, "verifier syntax error");
+ goto done;
+ }
+ kc = mess2+3;
+ *eol = 0;
+ shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ enc64(ks, sizeof ks, digest, SHA1dlen);
+ if(strcmp(ks, kc) != 0) {
+ rc = -2;
+ goto done;
+ }
+
+ // set session key
+ shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest);
+ n = conn->secret(conn, digest, 1);
+ if(n < 0){
+ writerr(conn, "can't set secret");
+ goto done;
+ }
+
+ rc = 0;
+done:
+ if(rc<0 && pw){
+ pw->failed++;
+ putPW(pw);
+ }
+ if(rc==0 && pw && pw->failed>0){
+ pw->failed = 0;
+ putPW(pw);
+ }
+ if(pwp)
+ *pwp = pw;
+ else
+ freePW(pw);
+ free(hexsigma);
+ free(hexHi);
+ free(hexmu);
+ mpfree(y);
+ mpfree(sigma);
+ mpfree(mu);
+ mpfree(m);
+ mpfree(Hi);
+ mpfree(H);
+ return rc;
+}
+
diff --git a/sys/src/cmd/auth/secstore/password.c b/sys/src/cmd/auth/secstore/password.c
new file mode 100755
index 000000000..58dfec774
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/password.c
@@ -0,0 +1,146 @@
+/* password.c */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
+#include "SConn.h"
+#include "secstore.h"
+
+static Biobuf*
+openPW(char *id, int mode)
+{
+ int nfn = strlen(SECSTORE_DIR)+strlen(id)+20;
+ char *fn;
+ Biobuf *b;
+
+ if(validatefile(id) == nil || strcmp(id,".") == 0)
+ return nil;
+ fn = emalloc(nfn);
+ snprint(fn, nfn, "%s/who/%s", SECSTORE_DIR, id);
+ b = Bopen(fn, mode);
+ free(fn);
+ return b;
+}
+
+static ulong
+mtimePW(char *id)
+{
+ ulong mt;
+ char *fn;
+ Dir *d;
+
+ fn = smprint("%s/who/%s", SECSTORE_DIR, id);
+ d = dirstat(fn);
+ mt = (d? d->mtime: 0);
+ free(d);
+ free(fn);
+ return mt;
+}
+
+PW *
+getPW(char *id, int dead_or_alive)
+{
+ ulong now = time(0);
+ char *f1, *f2, *oid; /* fields 1, 2 = attribute, value */
+ Biobuf *bin;
+ PW *pw;
+
+ oid = id;
+ if((bin = openPW(id, OREAD)) == 0){
+ id = "FICTITIOUS";
+ if((bin = openPW(id, OREAD)) == 0){
+ werrstr("accounts %s and FICTITIOUS do not exist", oid);
+ return nil;
+ }
+ }
+ pw = emalloc(sizeof *pw);
+ pw->id = estrdup(id);
+ pw->status |= Enabled;
+ while( (f1 = Brdline(bin, '\n')) != 0){
+ f1[Blinelen(bin)-1] = 0;
+ for(f2 = f1; *f2 && *f2 != ' ' && *f2 != '\t'; f2++)
+ ;
+ if(*f2)
+ for(*f2++ = 0; *f2 && (*f2==' ' || *f2=='\t'); f2++)
+ ;
+ if(strcmp(f1, "exp") == 0)
+ pw->expire = strtoul(f2, 0, 10);
+ else if(strcmp(f1, "DISABLED") == 0)
+ pw->status &= ~Enabled;
+ else if(strcmp(f1, "STA") == 0)
+ pw->status |= STA;
+ else if(strcmp(f1, "failed") == 0)
+ pw->failed = strtoul(f2, 0, 10);
+ else if(strcmp(f1, "other") == 0)
+ pw->other = estrdup(f2);
+ else if(strcmp(f1, "PAK-Hi") == 0)
+ pw->Hi = strtomp(f2, nil, 64, nil);
+ }
+ Bterm(bin);
+ if(pw->Hi == nil){
+ werrstr("corrupted account file for %s", pw->id);
+ freePW(pw);
+ return nil;
+ }
+ if(dead_or_alive)
+ return pw; /* return for editing, whether valid now or not */
+ if(pw->expire != 0 && pw->expire <= now){
+ /* %.28s excludes ctime's newline */
+ werrstr("account %s expired at %.28s", pw->id,
+ ctime(pw->expire));
+ freePW(pw);
+ return nil;
+ }
+ if((pw->status & Enabled) == 0){
+ werrstr("account %s disabled", pw->id);
+ freePW(pw);
+ return nil;
+ }
+ if(pw->failed < 10)
+ return pw; /* success */
+ if(now < mtimePW(id)+300){
+ werrstr("too many failures; try again in five minutes");
+ freePW(pw);
+ return nil;
+ }
+ pw->failed = 0;
+ putPW(pw); /* reset failed-login-counter after five minutes */
+ return pw;
+}
+
+int
+putPW(PW *pw)
+{
+ Biobuf *bout;
+ char *hexHi;
+
+ if((bout = openPW(pw->id, OWRITE|OTRUNC)) ==0){
+ werrstr("can't open PW file for %s", pw->id);
+ return -1;
+ }
+ Bprint(bout, "exp %lud\n", pw->expire);
+ if(!(pw->status & Enabled))
+ Bprint(bout, "DISABLED\n");
+ if(pw->status & STA)
+ Bprint(bout, "STA\n");
+ if(pw->failed)
+ Bprint(bout, "failed\t%d\n", pw->failed);
+ if(pw->other)
+ Bprint(bout,"other\t%s\n", pw->other);
+ hexHi = mptoa(pw->Hi, 64, nil, 0);
+ Bprint(bout, "PAK-Hi\t%s\n", hexHi);
+ free(hexHi);
+ return 0;
+}
+
+void
+freePW(PW *pw)
+{
+ if(pw == nil)
+ return;
+ free(pw->id);
+ free(pw->other);
+ mpfree(pw->Hi);
+ free(pw);
+}
diff --git a/sys/src/cmd/auth/secstore/secchk.c b/sys/src/cmd/auth/secstore/secchk.c
new file mode 100755
index 000000000..c6f306a56
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/secchk.c
@@ -0,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+extern char* secureidcheck(char *user, char *response);
+
+Ndb *db;
+
+void
+main(int argc, char **argv)
+{
+ Ndb *db2;
+
+ if(argc!=2){
+ fprint(2, "usage: %s pinsecurid\n", argv[0]);
+ exits("usage");
+ }
+
+ db = ndbopen("/lib/ndb/auth");
+ if(db == 0)
+ syslog(0, "secstore", "no /lib/ndb/auth");
+ db2 = ndbopen(0);
+ if(db2 == 0)
+ syslog(0, "secstore", "no /lib/ndb/local");
+ db = ndbcat(db, db2);
+
+ print("user=%s\n", getenv("user"));
+ print("%s\n", secureidcheck(getenv("user"), argv[1]));
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/secstore/secstore.c b/sys/src/cmd/auth/secstore/secstore.c
new file mode 100755
index 000000000..1a50e4535
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/secstore.c
@@ -0,0 +1,570 @@
+/* secstore - network login client */
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include <authsrv.h>
+#include "SConn.h"
+#include "secstore.h"
+
+enum{ CHK = 16, MAXFILES = 100 };
+
+typedef struct AuthConn{
+ SConn *conn;
+ char pass[64];
+ int passlen;
+} AuthConn;
+
+int verbose;
+Nvrsafe nvr;
+
+void
+usage(void)
+{
+ fprint(2, "usage: secstore [-cinv] [-[gG] getfile] [-p putfile] "
+ "[-r rmfile] [-s tcp!server!5356] [-u user]\n");
+ exits("usage");
+}
+
+static int
+getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey)
+{
+ int fd = -1, i, n, nr, nw, len;
+ char s[Maxmsg+1];
+ uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe;
+ AESstate aes;
+ DigestState *sha;
+
+ memset(&aes, 0, sizeof aes);
+
+ snprint(s, Maxmsg, "GET %s", gf);
+ conn->write(conn, (uchar*)s, strlen(s));
+
+ /* get file size */
+ s[0] = '\0';
+ bufw = bufe = nil;
+ if(readstr(conn, s) < 0){
+ fprint(2, "secstore: remote: %s\n", s);
+ return -1;
+ }
+ len = atoi(s);
+ if(len == -1){
+ fprint(2, "secstore: remote file %s does not exist\n", gf);
+ return -1;
+ }else if(len == -3){
+ fprint(2, "secstore: implausible filesize for %s\n", gf);
+ return -1;
+ }else if(len < 0){
+ fprint(2, "secstore: GET refused for %s\n", gf);
+ return -1;
+ }
+ if(buf != nil){
+ *buflen = len - AESbsize - CHK;
+ *buf = bufw = emalloc(len);
+ bufe = bufw + len;
+ }
+
+ /* directory listing */
+ if(strcmp(gf,".")==0){
+ if(buf != nil)
+ *buflen = len;
+ for(i=0; i < len; i += n){
+ if((n = conn->read(conn, (uchar*)s, Maxmsg)) <= 0){
+ fprint(2, "secstore: empty file chunk\n");
+ return -1;
+ }
+ if(buf == nil)
+ write(1, s, n);
+ else
+ memmove(*buf + i, s, n);
+ }
+ return 0;
+ }
+
+ /*
+ * conn is already encrypted against wiretappers, but gf is also
+ * encrypted against server breakin.
+ */
+ if(buf == nil && (fd = create(gf, OWRITE, 0600)) < 0){
+ fprint(2, "secstore: can't open %s: %r\n", gf);
+ return -1;
+ }
+
+ ibr = ibw = ib;
+ for(nr=0; nr < len;){
+ if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
+ fprint(2, "secstore: empty file chunk n=%d nr=%d len=%d: %r\n",
+ n, nr, len);
+ return -1;
+ }
+ nr += n;
+ ibw += n;
+ if(!aes.setup){ /* first time, read 16 byte IV */
+ if(n < AESbsize){
+ fprint(2, "secstore: no IV in file\n");
+ return -1;
+ }
+ sha = sha1((uchar*)"aescbc file", 11, nil, nil);
+ sha1(key, nkey, skey, sha);
+ setupAESstate(&aes, skey, AESbsize, ibr);
+ memset(skey, 0, sizeof skey);
+ ibr += AESbsize;
+ n -= AESbsize;
+ }
+ aesCBCdecrypt(ibw-n, n, &aes);
+ n = ibw - ibr - CHK;
+ if(n > 0){
+ if(buf == nil){
+ nw = write(fd, ibr, n);
+ if(nw != n){
+ fprint(2, "secstore: write error on %s", gf);
+ return -1;
+ }
+ }else{
+ assert(bufw + n <= bufe);
+ memmove(bufw, ibr, n);
+ bufw += n;
+ }
+ ibr += n;
+ }
+ memmove(ib, ibr, ibw-ibr);
+ ibw = ib + (ibw-ibr);
+ ibr = ib;
+ }
+ if(buf == nil)
+ close(fd);
+ n = ibw-ibr;
+ if(n != CHK || memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0){
+ fprint(2, "secstore: decrypted file failed to authenticate!\n");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * This sends a file to the secstore disk that can, in an emergency, be
+ * decrypted by the program aescbc.c.
+ */
+static int
+putfile(SConn *conn, char *pf, uchar *buf, ulong len, uchar *key, int nkey)
+{
+ int i, n, fd, ivo, bufi, done;
+ char s[Maxmsg];
+ uchar skey[SHA1dlen], b[CHK+Maxmsg], IV[AESbsize];
+ AESstate aes;
+ DigestState *sha;
+
+ /* create initialization vector */
+ srand(time(0)); /* doesn't need to be unpredictable */
+ for(i=0; i<AESbsize; i++)
+ IV[i] = 0xff & rand();
+ sha = sha1((uchar*)"aescbc file", 11, nil, nil);
+ sha1(key, nkey, skey, sha);
+ setupAESstate(&aes, skey, AESbsize, IV);
+ memset(skey, 0, sizeof skey);
+
+ snprint(s, Maxmsg, "PUT %s", pf);
+ conn->write(conn, (uchar*)s, strlen(s));
+
+ if(buf == nil){
+ /* get file size */
+ if((fd = open(pf, OREAD)) < 0){
+ fprint(2, "secstore: can't open %s: %r\n", pf);
+ return -1;
+ }
+ len = seek(fd, 0, 2);
+ seek(fd, 0, 0);
+ } else
+ fd = -1;
+ if(len > MAXFILESIZE){
+ fprint(2, "secstore: implausible filesize %ld for %s\n",
+ len, pf);
+ return -1;
+ }
+
+ /* send file size */
+ snprint(s, Maxmsg, "%ld", len + AESbsize + CHK);
+ conn->write(conn, (uchar*)s, strlen(s));
+
+ /* send IV and file+XXXXX in Maxmsg chunks */
+ ivo = AESbsize;
+ bufi = 0;
+ memcpy(b, IV, ivo);
+ for(done = 0; !done; ){
+ if(buf == nil){
+ n = read(fd, b+ivo, Maxmsg-ivo);
+ if(n < 0){
+ fprint(2, "secstore: read error on %s: %r\n",
+ pf);
+ return -1;
+ }
+ }else{
+ if((n = len - bufi) > Maxmsg-ivo)
+ n = Maxmsg-ivo;
+ memcpy(b+ivo, buf+bufi, n);
+ bufi += n;
+ }
+ n += ivo;
+ ivo = 0;
+ if(n < Maxmsg){ /* EOF on input; append XX... */
+ memset(b+n, 'X', CHK);
+ n += CHK; /* might push n>Maxmsg */
+ done = 1;
+ }
+ aesCBCencrypt(b, n, &aes);
+ if(n > Maxmsg){
+ assert(done==1);
+ conn->write(conn, b, Maxmsg);
+ n -= Maxmsg;
+ memmove(b, b+Maxmsg, n);
+ }
+ conn->write(conn, b, n);
+ }
+
+ if(buf == nil)
+ close(fd);
+ fprint(2, "secstore: saved %ld bytes\n", len);
+
+ return 0;
+}
+
+static int
+removefile(SConn *conn, char *rf)
+{
+ char buf[Maxmsg];
+
+ if(strchr(rf, '/') != nil){
+ fprint(2, "secstore: simple filenames, not paths like %s\n", rf);
+ return -1;
+ }
+
+ snprint(buf, Maxmsg, "RM %s", rf);
+ conn->write(conn, (uchar*)buf, strlen(buf));
+
+ return 0;
+}
+
+static int
+cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf)
+{
+ ulong len;
+ int rv = -1;
+ uchar *memfile, *memcur, *memnext;
+
+ while(*gf != nil){
+ if(verbose)
+ fprint(2, "get %s\n", *gf);
+ if(getfile(c->conn, *gf, *Gflag? &memfile: nil, &len,
+ (uchar*)c->pass, c->passlen) < 0)
+ goto Out;
+ if(*Gflag){
+ /* write 1 line at a time, as required by /mnt/factotum/ctl */
+ memcur = memfile;
+ while(len>0){
+ memnext = (uchar*)strchr((char*)memcur, '\n');
+ if(memnext){
+ write(1, memcur, memnext-memcur+1);
+ len -= memnext-memcur+1;
+ memcur = memnext+1;
+ }else{
+ write(1, memcur, len);
+ break;
+ }
+ }
+ free(memfile);
+ }
+ gf++;
+ Gflag++;
+ }
+ while(*pf != nil){
+ if(verbose)
+ fprint(2, "put %s\n", *pf);
+ if(putfile(c->conn, *pf, nil, 0, (uchar*)c->pass, c->passlen) < 0)
+ goto Out;
+ pf++;
+ }
+ while(*rf != nil){
+ if(verbose)
+ fprint(2, "rm %s\n", *rf);
+ if(removefile(c->conn, *rf) < 0)
+ goto Out;
+ rf++;
+ }
+
+ c->conn->write(c->conn, (uchar*)"BYE", 3);
+ rv = 0;
+
+Out:
+ c->conn->free(c->conn);
+ return rv;
+}
+
+static int
+chpasswd(AuthConn *c, char *id)
+{
+ int rv = -1, newpasslen = 0;
+ ulong len;
+ uchar *memfile;
+ char *newpass, *passck, *list, *cur, *next, *hexHi;
+ char *f[8], prompt[128];
+ mpint *H, *Hi;
+
+ H = mpnew(0);
+ Hi = mpnew(0);
+ /* changing our password is vulnerable to connection failure */
+ for(;;){
+ snprint(prompt, sizeof(prompt), "new password for %s: ", id);
+ newpass = getpassm(prompt);
+ if(newpass == nil)
+ goto Out;
+ if(strlen(newpass) >= 7)
+ break;
+ else if(strlen(newpass) == 0){
+ fprint(2, "!password change aborted\n");
+ goto Out;
+ }
+ print("!password must be at least 7 characters\n");
+ }
+ newpasslen = strlen(newpass);
+ snprint(prompt, sizeof(prompt), "retype password: ");
+ passck = getpassm(prompt);
+ if(passck == nil){
+ fprint(2, "secstore: getpassm failed\n");
+ goto Out;
+ }
+ if(strcmp(passck, newpass) != 0){
+ fprint(2, "secstore: passwords didn't match\n");
+ goto Out;
+ }
+
+ c->conn->write(c->conn, (uchar*)"CHPASS", strlen("CHPASS"));
+ hexHi = PAK_Hi(id, newpass, H, Hi);
+ c->conn->write(c->conn, (uchar*)hexHi, strlen(hexHi));
+ free(hexHi);
+ mpfree(H);
+ mpfree(Hi);
+
+ if(getfile(c->conn, ".", (uchar **) &list, &len, nil, 0) < 0){
+ fprint(2, "secstore: directory listing failed.\n");
+ goto Out;
+ }
+
+ /* Loop over files and reencrypt them; try to keep going after error */
+ for(cur=list; (next=strchr(cur, '\n')) != nil; cur=next+1){
+ *next = '\0';
+ if(tokenize(cur, f, nelem(f))< 1)
+ break;
+ fprint(2, "secstore: reencrypting '%s'\n", f[0]);
+ if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass,
+ c->passlen) < 0){
+ fprint(2, "secstore: getfile of '%s' failed\n", f[0]);
+ continue;
+ }
+ if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass,
+ newpasslen) < 0)
+ fprint(2, "secstore: putfile of '%s' failed\n", f[0]);
+ free(memfile);
+ }
+ free(list);
+ c->conn->write(c->conn, (uchar*)"BYE", 3);
+ rv = 0;
+
+Out:
+ if(newpass != nil){
+ memset(newpass, 0, newpasslen);
+ free(newpass);
+ }
+ c->conn->free(c->conn);
+ return rv;
+}
+
+static AuthConn*
+login(char *id, char *dest, int pass_stdin, int pass_nvram)
+{
+ int fd, n, ntry = 0;
+ char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass;
+ AuthConn *c;
+
+ if(dest == nil)
+ sysfatal("tried to login with nil dest");
+ c = emalloc(sizeof(*c));
+ if(pass_nvram){
+ if(readnvram(&nvr, 0) < 0)
+ exits("readnvram: %r");
+ strecpy(c->pass, c->pass+sizeof c->pass, nvr.config);
+ }
+ if(pass_stdin){
+ n = readn(0, s, Maxmsg-2); /* so len(PINSTA)<Maxmsg-3 */
+ if(n < 1)
+ exits("no password on standard input");
+ s[n] = 0;
+ nl = strchr(s, '\n');
+ if(nl){
+ *nl++ = 0;
+ PINSTA = estrdup(nl);
+ nl = strchr(PINSTA, '\n');
+ if(nl)
+ *nl = 0;
+ }
+ strecpy(c->pass, c->pass+sizeof c->pass, s);
+ }
+ for(;;){
+ if(verbose)
+ fprint(2, "dialing %s\n", dest);
+ if((fd = dial(dest, nil, nil, nil)) < 0){
+ fprint(2, "secstore: can't dial %s\n", dest);
+ free(c);
+ return nil;
+ }
+ if((c->conn = newSConn(fd)) == nil){
+ free(c);
+ return nil;
+ }
+ ntry++;
+ if(!pass_stdin && !pass_nvram){
+ pass = getpassm("secstore password: ");
+ if(strlen(pass) >= sizeof c->pass){
+ fprint(2, "secstore: password too long, skipping secstore login\n");
+ exits("password too long");
+ }
+ strcpy(c->pass, pass);
+ memset(pass, 0, strlen(pass));
+ free(pass);
+ }
+ if(c->pass[0]==0){
+ fprint(2, "secstore: null password, skipping secstore login\n");
+ exits("no password");
+ }
+ if(PAKclient(c->conn, id, c->pass, &S) >= 0)
+ break;
+ c->conn->free(c->conn);
+ if(pass_stdin)
+ exits("invalid password on standard input");
+ if(pass_nvram)
+ exits("invalid password in nvram");
+ /* and let user try retyping the password */
+ if(ntry==3)
+ fprint(2, "Enter an empty password to quit.\n");
+ }
+ c->passlen = strlen(c->pass);
+ fprint(2, "%s\n", S);
+ free(S);
+ if(readstr(c->conn, s) < 0){
+ c->conn->free(c->conn);
+ free(c);
+ return nil;
+ }
+ if(strcmp(s, "STA") == 0){
+ long sn;
+
+ if(pass_stdin){
+ if(PINSTA)
+ strncpy(s+3, PINSTA, sizeof s - 3);
+ else
+ exits("missing PIN+SecureID on standard input");
+ free(PINSTA);
+ }else{
+ pass = getpassm("STA PIN+SecureID: ");
+ strncpy(s+3, pass, sizeof s - 4);
+ memset(pass, 0, strlen(pass));
+ free(pass);
+ }
+ sn = strlen(s+3);
+ if(verbose)
+ fprint(2, "%ld\n", sn);
+ c->conn->write(c->conn, (uchar*)s, sn+3);
+ readstr(c->conn, s); /* TODO: check for error? */
+ }
+ if(strcmp(s, "OK") != 0){
+ fprint(2, "%s: %s\n", argv0, s);
+ c->conn->free(c->conn);
+ free(c);
+ return nil;
+ }
+ return c;
+}
+
+void
+main(int argc, char **argv)
+{
+ int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc;
+ int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1];
+ char *serve, *tcpserve, *user;
+ char *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES];
+ AuthConn *c;
+
+ serve = "$auth";
+ user = getuser();
+ memset(Gflag, 0, sizeof Gflag);
+
+ ARGBEGIN{
+ case 'c':
+ chpass = 1;
+ break;
+ case 'G':
+ Gflag[ngfile]++;
+ /* fall through */
+ case 'g':
+ if(ngfile >= MAXFILES)
+ exits("too many gfiles");
+ gfile[ngfile++] = EARGF(usage());
+ break;
+ case 'i':
+ pass_stdin = 1;
+ break;
+ case 'n':
+ pass_nvram = 1;
+ break;
+ case 'p':
+ if(npfile >= MAXFILES)
+ exits("too many pfiles");
+ pfile[npfile++] = EARGF(usage());
+ break;
+ case 'r':
+ if(nrfile >= MAXFILES)
+ exits("too many rfiles");
+ rfile[nrfile++] = EARGF(usage());
+ break;
+ case 's':
+ serve = EARGF(usage());
+ break;
+ case 'u':
+ user = EARGF(usage());
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND;
+ gfile[ngfile] = nil;
+ pfile[npfile] = nil;
+ rfile[nrfile] = nil;
+
+ if(argc!=0 || user==nil)
+ usage();
+
+ if(chpass && (ngfile || npfile || nrfile)){
+ fprint(2, "secstore: Get, put, and remove invalid with password change.\n");
+ exits("usage");
+ }
+
+ rc = strlen(serve) + sizeof "tcp!!99990";
+ tcpserve = emalloc(rc);
+ if(strchr(serve,'!'))
+ strcpy(tcpserve, serve);
+ else
+ snprint(tcpserve, rc, "tcp!%s!5356", serve);
+ c = login(user, tcpserve, pass_stdin, pass_nvram);
+ free(tcpserve);
+ if(c == nil)
+ sysfatal("secstore authentication failed");
+ if(chpass)
+ rc = chpasswd(c, user);
+ else
+ rc = cmd(c, gfile, Gflag, pfile, rfile);
+ if(rc < 0)
+ sysfatal("secstore cmd failed");
+ exits("");
+}
diff --git a/sys/src/cmd/auth/secstore/secstore.h b/sys/src/cmd/auth/secstore/secstore.h
new file mode 100755
index 000000000..3644291bd
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/secstore.h
@@ -0,0 +1,36 @@
+#define LOG "secstore"
+#define SECSTORE_DIR "/adm/secstore"
+
+enum {
+ MAXFILESIZE = 10*1024*1024,
+};
+
+/* PW status bits */
+enum {
+ Enabled = 1<<0,
+ STA = 1<<1, /* extra SecurID step */
+};
+
+typedef struct PW {
+ char *id; /* user id */
+ ulong expire; /* expiration time (epoch seconds) */
+ ushort status; /* Enabled, STA, ... */
+ ushort failed; /* number of failed login attempts */
+ char *other; /* other information, e.g. sponsor */
+ mpint *Hi; /* H(passphrase)^-1 mod p */
+} PW;
+
+void freePW(PW*);
+PW *getPW(char*, int);
+char *getpassm(char*);
+int putPW(PW*);
+char *validatefile(char*f);
+
+/*
+ * *client: SConn, client name, passphrase
+ * *server: SConn, (partial) 1st msg, PW entry
+ * *setpass: Username, hashed passphrase, PW entry
+ */
+int PAKclient(SConn*, char*, char*, char**);
+int PAKserver(SConn*, char*, char*, PW**);
+char* PAK_Hi(char*, char*, mpint*, mpint*);
diff --git a/sys/src/cmd/auth/secstore/secstored.c b/sys/src/cmd/auth/secstore/secstored.c
new file mode 100755
index 000000000..7ca648bdc
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/secstored.c
@@ -0,0 +1,396 @@
+/* secstored - secure store daemon */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <mp.h>
+#include <libsec.h>
+#include "SConn.h"
+#include "secstore.h"
+
+char* secureidcheck(char *, char *); /* from /sys/src/cmd/auth/ */
+extern char* dirls(char *path);
+
+int verbose;
+Ndb *db;
+
+static void
+usage(void)
+{
+ fprint(2, "usage: secstored [-R] [-S servername] [-s tcp!*!5356] "
+ "[-v] [-x netmtpt]\n");
+ exits("usage");
+}
+
+static int
+getdir(SConn *conn, char *id)
+{
+ char *ls, *s;
+ uchar *msg;
+ int n, len;
+
+ s = emalloc(Maxmsg);
+ snprint(s, Maxmsg, "%s/store/%s", SECSTORE_DIR, id);
+
+ if((ls = dirls(s)) == nil)
+ len = 0;
+ else
+ len = strlen(ls);
+
+ /* send file size */
+ snprint(s, Maxmsg, "%d", len);
+ conn->write(conn, (uchar*)s, strlen(s));
+
+ /* send directory listing in Maxmsg chunks */
+ n = Maxmsg;
+ msg = (uchar*)ls;
+ while(len > 0){
+ if(len < Maxmsg)
+ n = len;
+ conn->write(conn, msg, n);
+ msg += n;
+ len -= n;
+ }
+ free(s);
+ free(ls);
+ return 0;
+}
+
+static int
+getfile(SConn *conn, char *id, char *gf)
+{
+ int n, gd, len;
+ ulong mode;
+ char *s;
+ Dir *st;
+
+ if(strcmp(gf,".")==0)
+ return getdir(conn, id);
+
+ /* send file size */
+ s = emalloc(Maxmsg);
+ snprint(s, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, gf);
+ gd = open(s, OREAD);
+ if(gd < 0){
+ syslog(0, LOG, "can't open %s: %r", s);
+ free(s);
+ conn->write(conn, (uchar*)"-1", 2);
+ return -1;
+ }
+ st = dirfstat(gd);
+ if(st == nil){
+ syslog(0, LOG, "can't stat %s: %r", s);
+ free(s);
+ conn->write(conn, (uchar*)"-1", 2);
+ return -1;
+ }
+ mode = st->mode;
+ len = st->length;
+ free(st);
+ if(mode & DMDIR) {
+ syslog(0, LOG, "%s should be a plain file, not a directory", s);
+ free(s);
+ conn->write(conn, (uchar*)"-1", 2);
+ return -1;
+ }
+ if(len < 0 || len > MAXFILESIZE){
+ syslog(0, LOG, "implausible filesize %d for %s", len, gf);
+ free(s);
+ conn->write(conn, (uchar*)"-3", 2);
+ return -1;
+ }
+ snprint(s, Maxmsg, "%d", len);
+ conn->write(conn, (uchar*)s, strlen(s));
+
+ /* send file in Maxmsg chunks */
+ while(len > 0){
+ n = read(gd, s, Maxmsg);
+ if(n <= 0){
+ syslog(0, LOG, "read error on %s: %r", gf);
+ free(s);
+ return -1;
+ }
+ conn->write(conn, (uchar*)s, n);
+ len -= n;
+ }
+ close(gd);
+ free(s);
+ return 0;
+}
+
+static int
+putfile(SConn *conn, char *id, char *pf)
+{
+ int n, nw, pd;
+ long len;
+ char s[Maxmsg+1];
+
+ /* get file size */
+ n = readstr(conn, s);
+ if(n < 0){
+ syslog(0, LOG, "remote: %s: %r", s);
+ return -1;
+ }
+ len = atoi(s);
+ if(len == -1){
+ syslog(0, LOG, "remote file %s does not exist", pf);
+ return -1;
+ }else if(len < 0 || len > MAXFILESIZE){
+ syslog(0, LOG, "implausible filesize %ld for %s", len, pf);
+ return -1;
+ }
+
+ snprint(s, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, pf);
+ pd = create(s, OWRITE, 0660);
+ if(pd < 0){
+ syslog(0, LOG, "can't open %s: %r\n", s);
+ return -1;
+ }
+ while(len > 0){
+ n = conn->read(conn, (uchar*)s, Maxmsg);
+ if(n <= 0){
+ syslog(0, LOG, "empty file chunk");
+ return -1;
+ }
+ nw = write(pd, s, n);
+ if(nw != n){
+ syslog(0, LOG, "write error on %s: %r", pf);
+ return -1;
+ }
+ len -= n;
+ }
+ close(pd);
+ return 0;
+
+}
+
+static int
+removefile(SConn *conn, char *id, char *f)
+{
+ Dir *d;
+ char buf[Maxmsg];
+
+ snprint(buf, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, f);
+
+ if((d = dirstat(buf)) == nil){
+ snprint(buf, sizeof buf, "remove failed: %r");
+ writerr(conn, buf);
+ return -1;
+ }else if(d->mode & DMDIR){
+ snprint(buf, sizeof buf, "can't remove a directory");
+ writerr(conn, buf);
+ free(d);
+ return -1;
+ }
+
+ free(d);
+ if(remove(buf) < 0){
+ snprint(buf, sizeof buf, "remove failed: %r");
+ writerr(conn, buf);
+ return -1;
+ }
+ return 0;
+}
+
+/* given line directory from accept, returns ipaddr!port */
+static char*
+remoteIP(char *ldir)
+{
+ int fd, n;
+ char rp[100], ap[500];
+
+ snprint(rp, sizeof rp, "%s/remote", ldir);
+ fd = open(rp, OREAD);
+ if(fd < 0)
+ return strdup("?!?");
+ n = read(fd, ap, sizeof ap);
+ if(n <= 0 || n == sizeof ap){
+ fprint(2, "secstored: error %d reading %s: %r\n", n, rp);
+ return strdup("?!?");
+ }
+ close(fd);
+ ap[n--] = 0;
+ if(ap[n] == '\n')
+ ap[n] = 0;
+ return strdup(ap);
+}
+
+static int
+dologin(int fd, char *S, int forceSTA)
+{
+ int i, n, rv;
+ char *file, *mess, *nl;
+ char msg[Maxmsg+1];
+ PW *pw;
+ SConn *conn;
+
+ pw = nil;
+ rv = -1;
+
+ /* collect the first message */
+ if((conn = newSConn(fd)) == nil)
+ return -1;
+ if(readstr(conn, msg) < 0){
+ fprint(2, "secstored: remote: %s: %r\n", msg);
+ writerr(conn, "can't read your first message");
+ goto Out;
+ }
+
+ /* authenticate */
+ if(PAKserver(conn, S, msg, &pw) < 0){
+ if(pw != nil)
+ syslog(0, LOG, "secstore denied for %s", pw->id);
+ goto Out;
+ }
+ if((forceSTA || pw->status&STA) != 0){
+ conn->write(conn, (uchar*)"STA", 3);
+ if(readstr(conn, msg) < 10 || strncmp(msg, "STA", 3) != 0){
+ syslog(0, LOG, "no STA from %s", pw->id);
+ goto Out;
+ }
+ mess = secureidcheck(pw->id, msg+3);
+ if(mess != nil){
+ syslog(0, LOG, "secureidcheck denied %s because %s", pw->id, mess);
+ goto Out;
+ }
+ }
+ conn->write(conn, (uchar*)"OK", 2);
+ syslog(0, LOG, "AUTH %s", pw->id);
+
+ /* perform operations as asked */
+ while((n = readstr(conn, msg)) > 0){
+ if(nl = strchr(msg, '\n'))
+ *nl = 0;
+ syslog(0, LOG, "[%s] %s", pw->id, msg);
+
+ if(strncmp(msg, "GET ", 4) == 0){
+ file = validatefile(msg+4);
+ if(file==nil || getfile(conn, pw->id, file) < 0)
+ goto Err;
+
+ }else if(strncmp(msg, "PUT ", 4) == 0){
+ file = validatefile(msg+4);
+ if(file==nil || putfile(conn, pw->id, file) < 0){
+ syslog(0, LOG, "failed PUT %s/%s", pw->id, file);
+ goto Err;
+ }
+
+ }else if(strncmp(msg, "RM ", 3) == 0){
+ file = validatefile(msg+3);
+ if(file==nil || removefile(conn, pw->id, file) < 0){
+ syslog(0, LOG, "failed RM %s/%s", pw->id, file);
+ goto Err;
+ }
+
+ }else if(strncmp(msg, "CHPASS", 6) == 0){
+ if(readstr(conn, msg) < 0){
+ syslog(0, LOG, "protocol botch CHPASS for %s", pw->id);
+ writerr(conn, "protocol botch while setting PAK");
+ goto Out;
+ }
+ pw->Hi = strtomp(msg, nil, 64, pw->Hi);
+ for(i=0; i < 4 && putPW(pw) < 0; i++)
+ syslog(0, LOG, "password change failed for %s (%d): %r", pw->id, i);
+ if(i==4)
+ goto Out;
+
+ }else if(strncmp(msg, "BYE", 3) == 0){
+ rv = 0;
+ break;
+
+ }else{
+ writerr(conn, "unrecognized operation");
+ break;
+ }
+
+ }
+ if(n <= 0)
+ syslog(0, LOG, "%s closed connection without saying goodbye", pw->id);
+
+Out:
+ freePW(pw);
+ conn->free(conn);
+ return rv;
+Err:
+ writerr(conn, "operation failed");
+ goto Out;
+}
+
+void
+main(int argc, char **argv)
+{
+ int afd, dfd, lcfd, forceSTA = 0;
+ char aserve[128], net[128], adir[40], ldir[40];
+ char *remote, *serve = "tcp!*!5356", *S = "secstore";
+ Ndb *db2;
+
+ setnetmtpt(net, sizeof(net), nil);
+ ARGBEGIN{
+ case 'R':
+ forceSTA = 1;
+ break;
+ case 's':
+ serve = EARGF(usage());
+ break;
+ case 'S':
+ S = EARGF(usage());
+ break;
+ case 'x':
+ setnetmtpt(net, sizeof(net), EARGF(usage()));
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ if(!verbose)
+ switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ break;
+ default:
+ exits(0);
+ }
+
+ snprint(aserve, sizeof aserve, "%s/%s", net, serve);
+ afd = announce(aserve, adir);
+ if(afd < 0)
+ sysfatal("%s: %r", aserve);
+ syslog(0, LOG, "ANNOUNCE %s", aserve);
+ for(;;){
+ if((lcfd = listen(adir, ldir)) < 0)
+ exits("can't listen");
+ switch(fork()){
+ case -1:
+ fprint(2, "secstore forking: %r\n");
+ close(lcfd);
+ break;
+ case 0:
+ /*
+ * "/lib/ndb/common.radius does not exist"
+ * if db set before fork.
+ */
+ db = ndbopen("/lib/ndb/auth");
+ if(db == 0)
+ syslog(0, LOG, "no /lib/ndb/auth");
+ db2 = ndbopen(0);
+ if(db2 == 0)
+ syslog(0, LOG, "no /lib/ndb/local");
+ db = ndbcat(db, db2);
+ if((dfd = accept(lcfd, ldir)) < 0)
+ exits("can't accept");
+ alarm(30*60*1000); /* 30 min */
+ remote = remoteIP(ldir);
+ syslog(0, LOG, "secstore from %s", remote);
+ free(remote);
+ dologin(dfd, S, forceSTA);
+ exits(nil);
+ default:
+ close(lcfd);
+ break;
+ }
+ }
+}
diff --git a/sys/src/cmd/auth/secstore/secuser.c b/sys/src/cmd/auth/secstore/secuser.c
new file mode 100755
index 000000000..e4cffb69c
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/secuser.c
@@ -0,0 +1,227 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include "SConn.h"
+#include "secstore.h"
+
+int verbose;
+
+static void userinput(char *, int);
+
+static void
+ensure_exists(char *f, ulong perm)
+{
+ int fd;
+
+ if(access(f, AEXIST) >= 0)
+ return;
+ if(verbose)
+ fprint(2,"first time setup for secstore: create %s %lo\n", f, perm);
+ fd = create(f, OREAD, perm);
+ if(fd < 0)
+ sysfatal("unable to create %s: %r", f);
+ close(fd);
+}
+
+
+void
+main(int argc, char **argv)
+{
+ int isnew;
+ char *id, buf[Maxmsg], home[Maxmsg], prompt[100], *hexHi;
+ char *pass, *passck;
+ long expsecs;
+ mpint *H = mpnew(0), *Hi = mpnew(0);
+ PW *pw;
+ Tm *tm;
+
+ ARGBEGIN{
+ case 'v':
+ verbose++;
+ break;
+ }ARGEND;
+ if(argc!=1){
+ fprint(2, "usage: secuser [-v] <user>\n");
+ exits("usage");
+ }
+
+ ensure_exists(SECSTORE_DIR, DMDIR|0755L);
+ snprint(home, sizeof(home), "%s/who", SECSTORE_DIR);
+ ensure_exists(home, DMDIR|0755L);
+ snprint(home, sizeof(home), "%s/store", SECSTORE_DIR);
+ ensure_exists(home, DMDIR|0700L);
+
+ id = argv[0];
+ if(verbose)
+ fprint(2,"secuser %s\n", id);
+ if((pw = getPW(id,1)) == nil){
+ isnew = 1;
+ print("new account (because %s/%s %r)\n", SECSTORE_DIR, id);
+ pw = emalloc(sizeof(*pw));
+ pw->id = estrdup(id);
+ snprint(home, sizeof(home), "%s/store/%s", SECSTORE_DIR, id);
+ if(access(home, AEXIST) == 0)
+ sysfatal("new user, but directory %s already exists",
+ home);
+ }else{
+ isnew = 0;
+ }
+
+ /* get main password for id */
+ for(;;){
+ if(isnew)
+ snprint(prompt, sizeof(prompt), "%s password: ", id);
+ else
+ snprint(prompt, sizeof(prompt), "%s password [default = don't change]: ", id);
+ pass = getpassm(prompt);
+ if(pass == nil)
+ sysfatal("getpassm failed");
+ if(verbose)
+ print("%ld characters\n", strlen(pass));
+ if(pass[0] == '\0' && isnew == 0)
+ break;
+ if(strlen(pass) >= 7)
+ break;
+ print("password must be at least 7 characters\n");
+ }
+
+ if(pass[0] != '\0'){
+ snprint(prompt, sizeof(prompt), "retype password: ");
+ if(verbose)
+ print("confirming...\n");
+ passck = getpassm(prompt);
+ if(passck == nil)
+ sysfatal("getpassm failed");
+ if(strcmp(pass, passck) != 0)
+ sysfatal("passwords didn't match");
+ memset(passck, 0, strlen(passck));
+ free(passck);
+ hexHi = PAK_Hi(id, pass, H, Hi);
+ memset(pass, 0, strlen(pass));
+ free(pass);
+ free(hexHi);
+ mpfree(H);
+ pw->Hi = Hi;
+ }
+
+ /* get expiration time (midnight of date specified) */
+ if(isnew)
+ expsecs = time(0) + 365*24*60*60;
+ else
+ expsecs = pw->expire;
+
+ for(;;){
+ tm = localtime(expsecs);
+ print("expires [DDMMYYYY, default = %2.2d%2.2d%4.4d]: ",
+ tm->mday, tm->mon+1, tm->year+1900);
+ userinput(buf, sizeof(buf));
+ if(strlen(buf) == 0)
+ break;
+ if(strlen(buf) != 8){
+ print("!bad date format: %s\n", buf);
+ continue;
+ }
+ tm->mday = (buf[0]-'0')*10 + (buf[1]-'0');
+ if(tm->mday > 31 || tm->mday < 1){
+ print("!bad day of month: %d\n", tm->mday);
+ continue;
+ }
+ tm->mon = (buf[2]-'0')*10 + (buf[3]-'0') - 1;
+ if(tm->mon > 11 || tm->mon < 0){
+ print("!bad month: %d\n", tm->mon + 1);
+ continue;
+ }
+ tm->year = atoi(buf+4) - 1900;
+ if(tm->year < 70){
+ print("!bad year: %d\n", tm->year + 1900);
+ continue;
+ }
+ tm->sec = 59;
+ tm->min = 59;
+ tm->hour = 23;
+ tm->yday = 0;
+ expsecs = tm2sec(tm);
+ break;
+ }
+ pw->expire = expsecs;
+
+ /* failed logins */
+ if(pw->failed != 0 )
+ print("clearing %d failed login attempts\n", pw->failed);
+ pw->failed = 0;
+
+ /* status bits */
+ if(isnew)
+ pw->status = Enabled;
+ for(;;){
+ print("Enabled or Disabled [default %s]: ",
+ (pw->status & Enabled) ? "Enabled" : "Disabled" );
+ userinput(buf, sizeof(buf));
+ if(strlen(buf) == 0)
+ break;
+ if(buf[0]=='E' || buf[0]=='e'){
+ pw->status |= Enabled;
+ break;
+ }
+ if(buf[0]=='D' || buf[0]=='d'){
+ pw->status = pw->status & ~Enabled;
+ break;
+ }
+ }
+ for(;;){
+ print("require STA? [default %s]: ",
+ (pw->status & STA) ? "yes" : "no" );
+ userinput(buf, sizeof(buf));
+ if(strlen(buf) == 0)
+ break;
+ if(buf[0]=='Y' || buf[0]=='y'){
+ pw->status |= STA;
+ break;
+ }
+ if(buf[0]=='N' || buf[0]=='n'){
+ pw->status = pw->status & ~STA;
+ break;
+ }
+ }
+
+ /* free form field */
+ if(isnew)
+ pw->other = nil;
+ print("comments [default = %s]: ", (pw->other == nil) ? "" : pw->other);
+ userinput(buf, 72); /* 72 comes from password.h */
+ if(buf[0])
+ if((pw->other = strdup(buf)) == nil)
+ sysfatal("strdup");
+
+ syslog(0, LOG, "CHANGELOGIN for '%s'", pw->id);
+ if(putPW(pw) < 0)
+ sysfatal("can't write password file: %r");
+ else{
+ print("change written\n");
+ if(isnew && create(home, OREAD, DMDIR | 0775L) < 0)
+ sysfatal("unable to create %s: %r", home);
+ }
+
+ exits("");
+}
+
+
+static void
+userinput(char *buf, int blen)
+{
+ int n;
+
+ for(;;){
+ n = read(0, buf, blen);
+ if(n<=0)
+ exits("read error");
+ if(buf[n-1]=='\n'){
+ buf[n-1] = '\0';
+ return;
+ }
+ buf += n; blen -= n;
+ if(blen<=0)
+ exits("input too large");
+ }
+}
diff --git a/sys/src/cmd/auth/secstore/util.c b/sys/src/cmd/auth/secstore/util.c
new file mode 100755
index 000000000..00b4d0909
--- /dev/null
+++ b/sys/src/cmd/auth/secstore/util.c
@@ -0,0 +1,110 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include "SConn.h"
+#include "secstore.h"
+
+void *
+emalloc(ulong n)
+{
+ void *p = malloc(n);
+
+ if(p == nil)
+ sysfatal("emalloc");
+ memset(p, 0, n);
+ return p;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+ if ((p = realloc(p, n)) == nil)
+ sysfatal("erealloc");
+ return p;
+}
+
+char *
+estrdup(char *s)
+{
+ if ((s = strdup(s)) == nil)
+ sysfatal("estrdup");
+ return s;
+}
+
+char*
+getpassm(char *prompt)
+{
+ char *p, line[4096];
+ int n, nr;
+ static int cons, consctl; /* closing & reopening fails in ssh environment */
+
+ if(cons == 0){ /* first time? */
+ cons = open("/dev/cons", ORDWR);
+ if(cons < 0)
+ sysfatal("couldn't open cons");
+ consctl = open("/dev/consctl", OWRITE);
+ if(consctl < 0)
+ sysfatal("couldn't set raw mode via consctl");
+ }
+ fprint(consctl, "rawon");
+ fprint(cons, "%s", prompt);
+ nr = 0;
+ p = line;
+ for(;;){
+ n = read(cons, p, 1);
+ if(n < 0){
+ fprint(consctl, "rawoff");
+ fprint(cons, "\n");
+ return nil;
+ }
+ if(n == 0 || *p == '\n' || *p == '\r' || *p == 0x7f){
+ *p = '\0';
+ fprint(consctl, "rawoff");
+ fprint(cons, "\n");
+ p = strdup(line);
+ memset(line, 0, nr);
+ return p;
+ }
+ if(*p == '\b'){
+ if(nr > 0){
+ nr--;
+ p--;
+ }
+ }else if(*p == ('u' & 037)){ /* cntrl-u */
+ fprint(cons, "\n%s", prompt);
+ nr = 0;
+ p = line;
+ }else{
+ nr++;
+ p++;
+ }
+ if(nr+1 == sizeof line){
+ fprint(cons, "line too long; try again\n%s", prompt);
+ nr = 0;
+ p = line;
+ }
+ }
+}
+
+static char *
+illegal(char *f)
+{
+ syslog(0, LOG, "illegal name: %s", f);
+ return nil;
+}
+
+char *
+validatefile(char *f)
+{
+ char *p;
+
+ if(f == nil || *f == '\0')
+ return nil;
+ if(strcmp(f, "..") == 0 || strlen(f) >= 250)
+ return illegal(f);
+ for(p = f; *p; p++)
+ if(*p < 040 || *p == '/')
+ return illegal(f);
+ return f;
+}
diff --git a/sys/src/cmd/auth/secureidcheck.c b/sys/src/cmd/auth/secureidcheck.c
new file mode 100755
index 000000000..d52689145
--- /dev/null
+++ b/sys/src/cmd/auth/secureidcheck.c
@@ -0,0 +1,466 @@
+/*
+ * This code uses RADIUS as a portable way to validate tokens such as SecurID.
+ * It is relatively simple to send a UDP packet and get a response, but various
+ * things can go wrong. Speaking the proprietary ACE protocol would allow
+ * handling "next token code" and other error messages. More importantly, the
+ * timeout threshold is inherently hard to pick. We observe responses taking
+ * longer than 10 seconds in normal times. That is a long time to wait before
+ * retrying on a second server. Moreover, if the UDP response is lost, retrying
+ * on a second server will also fail because the valid token code may be
+ * presented only once. This whole approach is flawed, but best we can do.
+ */
+/* RFC2138 */
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <ctype.h>
+#include <mp.h>
+#include <libsec.h>
+#include <bio.h>
+#include <ndb.h>
+
+#define AUTHLOG "auth"
+
+enum{
+ R_AccessRequest =1, /* Packet code */
+ R_AccessAccept =2,
+ R_AccessReject =3,
+ R_AccessChallenge=11,
+ R_UserName =1,
+ R_UserPassword =2,
+ R_NASIPAddress =4,
+ R_ReplyMessage =18,
+ R_State =24,
+ R_NASIdentifier =32,
+};
+
+typedef struct Secret{
+ uchar *s;
+ int len;
+} Secret;
+
+typedef struct Attribute{
+ struct Attribute *next;
+ uchar type;
+ uchar len; /* number of bytes in value */
+ uchar val[256];
+} Attribute;
+
+typedef struct Packet{
+ uchar code, ID;
+ uchar authenticator[16];
+ Attribute first;
+} Packet;
+
+/* assumes pass is at most 16 chars */
+void
+hide(Secret *shared, uchar *auth, Secret *pass, uchar *x)
+{
+ DigestState *M;
+ int i, n = pass->len;
+
+ M = md5(shared->s, shared->len, nil, nil);
+ md5(auth, 16, x, M);
+ if(n > 16)
+ n = 16;
+ for(i = 0; i < n; i++)
+ x[i] ^= pass->s[i];
+}
+
+int
+authcmp(Secret *shared, uchar *buf, int m, uchar *auth)
+{
+ DigestState *M;
+ uchar x[16];
+
+ M = md5(buf, 4, nil, nil); /* Code+ID+Length */
+ M = md5(auth, 16, nil, M); /* RequestAuth */
+ M = md5(buf+20, m-20, nil, M); /* Attributes */
+ md5(shared->s, shared->len, x, M);
+ return memcmp(x, buf+4, 16);
+}
+
+Packet*
+newRequest(uchar *auth)
+{
+ static uchar ID = 0;
+ Packet *p;
+
+ p = (Packet*)malloc(sizeof(*p));
+ if(p == nil)
+ return nil;
+ p->code = R_AccessRequest;
+ p->ID = ++ID;
+ memmove(p->authenticator, auth, 16);
+ p->first.next = nil;
+ p->first.type = 0;
+ return p;
+}
+
+void
+freePacket(Packet *p)
+{
+ Attribute *a, *x;
+
+ if(!p)
+ return;
+ a = p->first.next;
+ while(a){
+ x = a;
+ a = a->next;
+ free(x);
+ }
+ free(p);
+}
+
+int
+ding(void*, char *msg)
+{
+ syslog(0, AUTHLOG, "ding %s", msg);
+ if(strstr(msg, "alarm"))
+ return 1;
+ return 0;
+}
+
+Packet *
+rpc(char *dest, Secret *shared, Packet *req)
+{
+ uchar buf[4096], buf2[4096], *b, *e;
+ Packet *resp;
+ Attribute *a;
+ int m, n, fd, try;
+
+ /* marshal request */
+ e = buf + sizeof buf;
+ buf[0] = req->code;
+ buf[1] = req->ID;
+ memmove(buf+4, req->authenticator, 16);
+ b = buf+20;
+ for(a = &req->first; a; a = a->next){
+ if(b + 2 + a->len > e)
+ return nil;
+ *b++ = a->type;
+ *b++ = 2 + a->len;
+ memmove(b, a->val, a->len);
+ b += a->len;
+ }
+ n = b-buf;
+ buf[2] = n>>8;
+ buf[3] = n;
+
+ /* send request, wait for reply */
+ fd = dial(dest, 0, 0, 0);
+ if(fd < 0){
+ syslog(0, AUTHLOG, "%s: rpc can't get udp channel", dest);
+ return nil;
+ }
+ atnotify(ding, 1);
+ m = -1;
+ for(try = 0; try < 2; try++){
+ /*
+ * increased timeout from 4sec to 15sec because
+ * corporate server really takes that long.
+ */
+ alarm(15000);
+ m = write(fd, buf, n);
+ if(m != n){
+ syslog(0, AUTHLOG, "%s: rpc write err %d %d: %r",
+ dest, m, n);
+ m = -1;
+ break;
+ }
+ m = read(fd, buf2, sizeof buf2);
+ alarm(0);
+ if(m < 0){
+ syslog(0, AUTHLOG, "%s rpc read err %d: %r", dest, m);
+ break; /* failure */
+ }
+ if(m == 0 || buf2[1] != buf[1]){ /* need matching ID */
+ syslog(0, AUTHLOG, "%s unmatched reply %d", dest, m);
+ continue;
+ }
+ if(authcmp(shared, buf2, m, buf+4) == 0)
+ break;
+ syslog(0, AUTHLOG, "%s bad rpc chksum", dest);
+ }
+ close(fd);
+ if(m <= 0)
+ return nil;
+
+ /* unmarshal reply */
+ b = buf2;
+ e = buf2+m;
+ resp = (Packet*)malloc(sizeof(*resp));
+ if(resp == nil)
+ return nil;
+ resp->code = *b++;
+ resp->ID = *b++;
+ n = *b++;
+ n = (n<<8) | *b++;
+ if(m != n){
+ syslog(0, AUTHLOG, "rpc got %d bytes, length said %d", m, n);
+ if(m > n)
+ e = buf2+n;
+ }
+ memmove(resp->authenticator, b, 16);
+ b += 16;
+ a = &resp->first;
+ a->type = 0;
+ for(;;){
+ if(b >= e){
+ a->next = nil;
+ break; /* exit loop */
+ }
+ a->type = *b++;
+ a->len = (*b++) - 2;
+ if(b + a->len > e){ /* corrupt packet */
+ a->next = nil;
+ freePacket(resp);
+ return nil;
+ }
+ memmove(a->val, b, a->len);
+ b += a->len;
+ if(b < e){ /* any more attributes? */
+ a->next = (Attribute*)malloc(sizeof(*a));
+ if(a->next == nil){
+ free(req);
+ return nil;
+ }
+ a = a->next;
+ }
+ }
+ return resp;
+}
+
+int
+setAttribute(Packet *p, uchar type, uchar *s, int n)
+{
+ Attribute *a;
+
+ a = &p->first;
+ if(a->type != 0){
+ a = (Attribute*)malloc(sizeof(*a));
+ if(a == nil)
+ return -1;
+ a->next = p->first.next;
+ p->first.next = a;
+ }
+ a->type = type;
+ a->len = n;
+ if(a->len > 253) /* RFC2138, section 5 */
+ a->len = 253;
+ memmove(a->val, s, a->len);
+ return 0;
+}
+
+/* return a reply message attribute string */
+char*
+replymsg(Packet *p)
+{
+ Attribute *a;
+ static char buf[255];
+
+ for(a = &p->first; a; a = a->next)
+ if(a->type == R_ReplyMessage){
+ if(a->len >= sizeof buf)
+ a->len = sizeof(buf)-1;
+ memmove(buf, a->val, a->len);
+ buf[a->len] = 0;
+ }
+ return buf;
+}
+
+/* for convenience while debugging */
+char *replymess;
+Attribute *stateattr;
+
+void
+logPacket(Packet *p)
+{
+ int i;
+ char *np, *e;
+ char buf[255], pbuf[4*1024];
+ uchar *au = p->authenticator;
+ Attribute *a;
+
+ e = pbuf + sizeof(pbuf);
+
+ np = seprint(pbuf, e, "Packet ID=%d auth=%x %x %x... ",
+ p->ID, au[0], au[1], au[2]);
+ switch(p->code){
+ case R_AccessRequest:
+ np = seprint(np, e, "request\n");
+ break;
+ case R_AccessAccept:
+ np = seprint(np, e, "accept\n");
+ break;
+ case R_AccessReject:
+ np = seprint(np, e, "reject\n");
+ break;
+ case R_AccessChallenge:
+ np = seprint(np, e, "challenge\n");
+ break;
+ default:
+ np = seprint(np, e, "code=%d\n", p->code);
+ break;
+ }
+ replymess = "0000000";
+ for(a = &p->first; a; a = a->next){
+ if(a->len > 253 )
+ a->len = 253;
+ memmove(buf, a->val, a->len);
+ np = seprint(np, e, " [%d]", a->type);
+ for(i = 0; i < a->len; i++)
+ if(isprint(a->val[i]))
+ np = seprint(np, e, "%c", a->val[i]);
+ else
+ np = seprint(np, e, "\\%o", a->val[i]);
+ np = seprint(np, e, "\n");
+ buf[a->len] = 0;
+ if(a->type == R_ReplyMessage)
+ replymess = strdup(buf);
+ else if(a->type == R_State)
+ stateattr = a;
+ }
+
+ syslog(0, AUTHLOG, "%s", pbuf);
+}
+
+static uchar*
+getipv4addr(void)
+{
+ Ipifc *nifc;
+ Iplifc *lifc;
+ static Ipifc *ifc;
+
+ ifc = readipifc("/net", ifc, -1);
+ for(nifc = ifc; nifc; nifc = nifc->next)
+ for(lifc = nifc->lifc; lifc; lifc = lifc->next)
+ if (ipcmp(lifc->ip, IPnoaddr) != 0 &&
+ ipcmp(lifc->ip, v4prefix) != 0)
+ return lifc->ip;
+ return nil;
+}
+
+extern Ndb *db;
+
+/* returns 0 on success, error message on failure */
+char*
+secureidcheck(char *user, char *response)
+{
+ char *radiussecret = nil;
+ char *rv = "authentication failed";
+ char dest[3*IPaddrlen+20], ruser[64];
+ uchar *ip;
+ uchar x[16];
+ ulong u[4];
+ Ndbs s;
+ Ndbtuple *t = nil, *nt, *tt;
+ Packet *req = nil, *resp = nil;
+ Secret shared, pass;
+ static Ndb *netdb;
+
+ if(netdb == nil)
+ netdb = ndbopen(0);
+
+ /* bad responses make them disable the fob, avoid silly checks */
+ if(strlen(response) < 4 || strpbrk(response,"abcdefABCDEF") != nil)
+ goto out;
+
+ /* get radius secret */
+ radiussecret = ndbgetvalue(db, &s, "radius", "lra-radius", "secret", &t);
+ if(radiussecret == nil){
+ syslog(0, AUTHLOG, "secureidcheck: nil radius secret: %r");
+ goto out;
+ }
+
+ /* translate user name if we have to */
+ strcpy(ruser, user);
+ for(nt = t; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "uid") == 0 && strcmp(nt->val, user) == 0)
+ for(tt = nt->line; tt != nt; tt = tt->line)
+ if(strcmp(tt->attr, "rid") == 0){
+ strcpy(ruser, tt->val);
+ break;
+ }
+ ndbfree(t);
+ t = nil;
+
+ u[0] = fastrand();
+ u[1] = fastrand();
+ u[2] = fastrand();
+ u[3] = fastrand();
+ req = newRequest((uchar*)u);
+ if(req == nil)
+ goto out;
+ shared.s = (uchar*)radiussecret;
+ shared.len = strlen(radiussecret);
+ ip = getipv4addr();
+ if(ip == nil){
+ syslog(0, AUTHLOG, "no interfaces: %r\n");
+ goto out;
+ }
+ if(setAttribute(req, R_NASIPAddress, ip + IPv4off, 4) < 0)
+ goto out;
+
+ if(setAttribute(req, R_UserName, (uchar*)ruser, strlen(ruser)) < 0)
+ goto out;
+ pass.s = (uchar*)response;
+ pass.len = strlen(response);
+ hide(&shared, req->authenticator, &pass, x);
+ if(setAttribute(req, R_UserPassword, x, 16) < 0)
+ goto out;
+
+ t = ndbsearch(netdb, &s, "sys", "lra-radius");
+ if(t == nil){
+ syslog(0, AUTHLOG, "secureidcheck: nil radius sys search: %r\n");
+ goto out;
+ }
+ for(nt = t; nt; nt = nt->entry){
+ if(strcmp(nt->attr, "ip") != 0)
+ continue;
+
+ snprint(dest, sizeof dest, "udp!%s!radius", nt->val);
+ resp = rpc(dest, &shared, req);
+ if(resp == nil){
+ syslog(0, AUTHLOG, "%s nil response", dest);
+ continue;
+ }
+ if(resp->ID != req->ID){
+ syslog(0, AUTHLOG, "%s mismatched ID req=%d resp=%d",
+ dest, req->ID, resp->ID);
+ freePacket(resp);
+ resp = nil;
+ continue;
+ }
+
+ switch(resp->code){
+ case R_AccessAccept:
+ syslog(0, AUTHLOG, "%s accepted ruser=%s", dest, ruser);
+ rv = nil;
+ break;
+ case R_AccessReject:
+ syslog(0, AUTHLOG, "%s rejected ruser=%s %s",
+ dest, ruser, replymsg(resp));
+ rv = "secureid failed";
+ break;
+ case R_AccessChallenge:
+ syslog(0, AUTHLOG, "%s challenge ruser=%s %s",
+ dest, ruser, replymsg(resp));
+ rv = "secureid out of sync";
+ break;
+ default:
+ syslog(0, AUTHLOG, "%s code=%d ruser=%s %s",
+ dest, resp->code, ruser, replymsg(resp));
+ break;
+ }
+ break; /* we have a proper reply, no need to ask again */
+ }
+out:
+ if (t)
+ ndbfree(t);
+ free(radiussecret);
+ freePacket(req);
+ freePacket(resp);
+ return rv;
+}
diff --git a/sys/src/cmd/auth/status b/sys/src/cmd/auth/status
new file mode 100755
index 000000000..4dc0323ca
--- /dev/null
+++ b/sys/src/cmd/auth/status
@@ -0,0 +1,36 @@
+#!/bin/rc
+cd /mnt/keys/$1 > /dev/null >[2=1] && {
+ stat=`{cat status}
+ exp=`{cat expire}
+ switch($exp){
+ case never 0
+ exp='never expires'
+ case *
+ exp=(expires on `{date $exp})
+ }
+ switch($stat){
+ case expired
+ echo user $1: plan 9 key has expired
+ case *
+ echo user $1: plan 9 key status is $stat and $exp
+ }
+ grep '^'$1'[ ]' /adm/keys.who | tail -1
+}
+cd /mnt/netkeys/$1 > /dev/null >[2=1] && {
+ stat=`{cat status}
+ exp=`{cat expire}
+ switch($exp){
+ case never 0
+ exp='never expires'
+ case *
+ exp=(expires on `{date $exp})
+ }
+ switch($stat){
+ case expired
+ echo user $1: network key has expired
+ case *
+ echo user $1: network key status is $stat and $exp
+ auth/printnetkey $1
+ }
+ grep '^'$1'[ ]' /adm/netkeys.who | tail -1
+}
diff --git a/sys/src/cmd/auth/uniq.c b/sys/src/cmd/auth/uniq.c
new file mode 100755
index 000000000..a3fa53cda
--- /dev/null
+++ b/sys/src/cmd/auth/uniq.c
@@ -0,0 +1,92 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+typedef struct Who Who;
+struct Who
+{
+ Who *next;
+ char *line;
+ char *name;
+};
+
+int cmp(void *arg1, void *arg2)
+{
+ Who **a = arg1, **b = arg2;
+
+ return strcmp((*a)->name, (*b)->name);
+}
+
+void
+main(int argc, char **argv)
+{
+ int changed, i, n;
+ Biobuf *b;
+ char *p, *name;
+ Who *first, *last, *w, *nw, **l;
+
+ if(argc != 2){
+ fprint(2, "usage: auth/uniq file\n");
+ exits(0);
+ }
+
+ last = first = 0;
+ b = Bopen(argv[1], OREAD);
+ if(b == 0)
+ exits(0);
+
+ n = 0;
+ changed = 0;
+ while(p = Brdline(b, '\n')){
+ p[Blinelen(b)-1] = 0;
+ name = p;
+ while(*p && *p != '|')
+ p++;
+ if(*p)
+ *p++ = 0;
+
+ for(nw = first; nw; nw = nw->next){
+ if(strcmp(nw->name, name) == 0){
+ free(nw->line);
+ nw->line = strdup(p);
+ changed = 1;
+ break;
+ }
+ }
+ if(nw)
+ continue;
+
+ w = malloc(sizeof(Who));
+ if(w == 0){
+ fprint(2, "auth/uniq: out of memory\n");
+ exits(0);
+ }
+ memset(w, 0, sizeof(Who));
+ w->name = strdup(name);
+ w->line = strdup(p);
+ if(first == 0)
+ first = w;
+ else
+ last->next = w;
+ last = w;
+ n++;
+ }
+ Bterm(b);
+
+ l = malloc(n*sizeof(Who*));
+ for(i = 0, nw = first; nw; nw = nw->next, i++)
+ l[i] = nw;
+ qsort(l, n, sizeof(Who*), cmp);
+
+ if(!changed)
+ exits(0);
+
+ b = Bopen(argv[1], OWRITE);
+ if(b == 0){
+ fprint(2, "auth/uniq: can't open %s\n", argv[1]);
+ exits(0);
+ }
+ for(i = 0; i < n; i++)
+ Bprint(b, "%s|%s\n", l[i]->name, l[i]->line);
+ Bterm(b);
+}
diff --git a/sys/src/cmd/auth/userpasswd.c b/sys/src/cmd/auth/userpasswd.c
new file mode 100755
index 000000000..ec9741132
--- /dev/null
+++ b/sys/src/cmd/auth/userpasswd.c
@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: auth/userpasswd fmt\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ UserPasswd *up;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ up = auth_getuserpasswd(auth_getkey, "proto=pass %s", argv[0]);
+ if(up == nil) /* bug in factotum, fixed but need to reboot servers -rsc, 2/10/2002 */
+ up = auth_getuserpasswd(nil, "proto=pass %s", argv[0]);
+ if(up == nil)
+ sysfatal("getuserpasswd: %r");
+
+ quotefmtinstall();
+ print("%s\n%s\n", up->user, up->passwd);
+ exits(0);
+}
diff --git a/sys/src/cmd/auth/warning.c b/sys/src/cmd/auth/warning.c
new file mode 100755
index 000000000..7aae9c137
--- /dev/null
+++ b/sys/src/cmd/auth/warning.c
@@ -0,0 +1,322 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include "authcmdlib.h"
+
+/* working directory */
+Dir *dirbuf;
+long ndirbuf = 0;
+
+int debug;
+
+long readdirect(int);
+void douser(Fs*, char*);
+void dodir(Fs*);
+int mail(Fs*, char*, char*, long);
+int mailin(Fs*, char*, long, char*, char*);
+void complain(char*, ...);
+long readnumfile(char*);
+void writenumfile(char*, long);
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-n] [-p]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int which;
+
+ which = 0;
+ ARGBEGIN{
+ case 'p':
+ which |= Plan9;
+ break;
+ case 'n':
+ which |= Securenet;
+ break;
+ case 'd':
+ debug++;
+ break;
+ default:
+ usage();
+ }ARGEND
+ argv0 = "warning";
+
+ if(!which)
+ which |= Plan9 | Securenet;
+ if(which & Plan9)
+ dodir(&fs[Plan9]);
+ if(which & Securenet)
+ dodir(&fs[Securenet]);
+}
+
+void
+dodir(Fs *f)
+{
+ int nfiles;
+ int i, fd;
+
+ if(chdir(f->keys) < 0){
+ complain("can't chdir to %s: %r", f->keys);
+ return;
+ }
+ fd = open(".", OREAD);
+ if(fd < 0){
+ complain("can't open %s: %r\n", f->keys);
+ return;
+ }
+ nfiles = dirreadall(fd, &dirbuf);
+ close(fd);
+ for(i = 0; i < nfiles; i++)
+ douser(f, dirbuf[i].name);
+}
+
+/*
+ * check for expiration
+ */
+void
+douser(Fs *f, char *user)
+{
+ int n, nwarn;
+ char buf[128];
+ long rcvrs, et, now;
+ char *l;
+
+ sprint(buf, "%s/expire", user);
+ et = readnumfile(buf);
+ now = time(0);
+
+ /* start warning 2 weeks ahead of time */
+ if(et <= now || et > now+14*24*60*60)
+ return;
+
+ sprint(buf, "%s/warnings", user);
+ nwarn = readnumfile(buf);
+ if(et <= now+14*24*60*60 && et > now+7*24*60*60){
+ /* one warning 2 weeks before expiration */
+ if(nwarn > 0)
+ return;
+ nwarn = 1;
+ } else {
+ /* one warning 1 week before expiration */
+ if(nwarn > 1)
+ return;
+ nwarn = 2;
+ }
+
+ /*
+ * if we can't open the who file, just mail to the user and hope
+ * for it makes it.
+ */
+ if(f->b){
+ if(Bseek(f->b, 0, 0) < 0){
+ Bterm(f->b);
+ f->b = 0;
+ }
+ }
+ if(f->b == 0){
+ f->b = Bopen(f->who, OREAD);
+ if(f->b == 0){
+ if(mail(f, user, user, et) > 0)
+ writenumfile(buf, nwarn);
+ return;
+ }
+ }
+
+ /*
+ * look for matches in the who file and mail to every address on
+ * matching lines
+ */
+ rcvrs = 0;
+ while(l = Brdline(f->b, '\n')){
+ n = strlen(user);
+ if(strncmp(l, user, n) == 0 && (l[n] == ' ' || l[n] == '\t'))
+ rcvrs += mailin(f, user, et, l, l+Blinelen(f->b));
+ }
+
+ /*
+ * if no matches, try the user directly
+ */
+ if(rcvrs == 0)
+ rcvrs = mail(f, user, user, et);
+ rcvrs += mail(f, "netkeys", user, et);
+ if(rcvrs)
+ writenumfile(buf, nwarn);
+}
+
+/*
+ * anything in <>'s is an address
+ */
+int
+mailin(Fs *f, char *user, long et, char *l, char *e)
+{
+ int n;
+ int rcvrs;
+ char *p;
+ char addr[256];
+
+ p = 0;
+ rcvrs = 0;
+ while(l < e){
+ switch(*l){
+ case '<':
+ p = l + 1;
+ break;
+ case '>':
+ if(p == 0)
+ break;
+ n = l - p;
+ if(n > 0 && n <= sizeof(addr) - 2){
+ memmove(addr, p, n);
+ addr[n] = 0;
+ rcvrs += mail(f, addr, user, et);
+ }
+ p = 0;
+ break;
+ }
+ l++;
+ }
+ return rcvrs;
+}
+
+/*
+ * send mail
+ */
+int
+mail(Fs *f, char *rcvr, char *user, long et)
+{
+ int pid, i, fd;
+ int pfd[2];
+ char *ct, *p;
+ Waitmsg *w;
+ char buf[128];
+
+ if(pipe(pfd) < 0){
+ complain("out of pipes: %r");
+ return 0;
+ }
+
+ switch(pid = fork()){
+ case -1:
+ complain("can't fork: %r");
+ return 0;
+ case 0:
+ break;
+ default:
+ if(debug)
+ fprint(2, "started %d\n", pid);
+ close(pfd[0]);
+ ct = ctime(et);
+ p = strchr(ct, '\n');
+ *p = '.';
+ fprint(pfd[1], "User '%s's %s expires on %s\n", user, f->msg, ct);
+ if(f != fs)
+ fprint(pfd[1], "If you wish to renew contact your local administrator.\n");
+ p = strrchr(f->keys, '/');
+ if(p)
+ p++;
+ else
+ p = f->keys;
+ sprint(buf, "/adm/warn.%s", p);
+ fd = open(buf, OREAD);
+ if(fd >= 0){
+ while((i = read(fd, buf, sizeof(buf))) > 0)
+ write(pfd[1], buf, i);
+ close(fd);
+ }
+ close(pfd[1]);
+
+ /* wait for warning to be mailed */
+ for(;;){
+ w = wait();
+ if(w == nil)
+ break;
+ if(w->pid == pid){
+ if(debug)
+ fprint(2, "%d terminated: %s\n", pid, w->msg);
+ if(w->msg[0] == 0){
+ free(w);
+ break;
+ }else{
+ free(w);
+ return 0;
+ }
+ }else
+ free(w);
+ }
+ return 1;
+ }
+
+ /* get out of the current namespace */
+ newns("none", 0);
+
+ dup(pfd[0], 0);
+ close(pfd[0]);
+ close(pfd[1]);
+ putenv("upasname", "netkeys");
+ if(debug){
+ print("\nto %s\n", rcvr);
+ execl("/bin/cat", "cat", nil);
+ }
+ execl("/bin/upas/send", "send", "-r", rcvr, nil);
+
+ /* just in case */
+ sysfatal("can't exec send: %r");
+
+ return 0; /* for compiler */
+}
+
+void
+complain(char *fmt, ...)
+{
+ char buf[8192], *s;
+ va_list arg;
+
+ s = buf;
+ s += sprint(s, "%s: ", argv0);
+ va_start(arg, fmt);
+ s = vseprint(s, buf + sizeof(buf) / sizeof(*buf), fmt, arg);
+ va_end(arg);
+ *s++ = '\n';
+ write(2, buf, s - buf);
+}
+
+long
+readnumfile(char *file)
+{
+ int fd, n;
+ char buf[64];
+
+ fd = open(file, OREAD);
+ if(fd < 0){
+ complain("can't open %s: %r", file);
+ return 0;
+ }
+ n = read(fd, buf, sizeof(buf)-1);
+ close(fd);
+ if(n < 0){
+ complain("can't read %s: %r", file);
+ return 0;
+ }
+ buf[n] = 0;
+ return atol(buf);
+}
+
+void
+writenumfile(char *file, long num)
+{
+ int fd;
+
+ fd = open(file, OWRITE);
+ if(fd < 0){
+ complain("can't open %s: %r", file);
+ return;
+ }
+ fprint(fd, "%ld", num);
+ close(fd);
+}
diff --git a/sys/src/cmd/auth/wrkey.c b/sys/src/cmd/auth/wrkey.c
new file mode 100755
index 000000000..e383d37db
--- /dev/null
+++ b/sys/src/cmd/auth/wrkey.c
@@ -0,0 +1,13 @@
+#include <u.h>
+#include <libc.h>
+#include <authsrv.h>
+
+void
+main(void)
+{
+ Nvrsafe safe;
+
+ if(readnvram(&safe, NVwrite) < 0)
+ sysfatal("error writing nvram: %r");
+ exits(0);
+}