diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/auth |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/auth')
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); +} |