commit 6e527fbc4d8f404a7eec934e5c9efaaaa92ffdff from: rsc date: Sun Feb 13 05:59:29 2005 UTC new auth commit - 0f8ec41b0ae522b73085fa1662461e6351ba7e54 commit + 6e527fbc4d8f404a7eec934e5c9efaaaa92ffdff blob - /dev/null blob + 8e340d474a68450d0100bf1e39525098f61ea3b7 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/apop.c @@ -0,0 +1,350 @@ +/* + * APOP, CRAM - MD5 challenge/response authentication + * + * The client does not authenticate the server, hence no CAI. + * + * Protocol: + * + * S -> C: random@domain + * C -> S: hex-response + * S -> C: ok + * + * Note that this is the protocol between factotum and the local + * program, not between the two factotums. The information + * exchanged here is wrapped in the APOP protocol by the local + * programs. + * + * If S sends "bad [msg]" instead of "ok", that is a hint that the key is bad. + * The protocol goes back to "C -> S: user". + */ + +#include "std.h" +#include "dat.h" + +extern Proto apop, cram; + +static int +apopcheck(Key *k) +{ + if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){ + werrstr("need user and !password attributes"); + return -1; + } + return 0; +} + +static int +apopclient(Conv *c) +{ + char *chal, *pw, *res; + int astype, nchal, npw, ntry, ret; + uchar resp[MD5dlen]; + Attr *attr; + DigestState *ds; + Key *k; + + chal = nil; + k = nil; + res = nil; + ret = -1; + attr = c->attr; + + if(c->proto == &apop) + astype = AuthApop; + else if(c->proto == &cram) + astype = AuthCram; + else{ + werrstr("bad proto"); + goto out; + } + + c->state = "find key"; + k = keyfetch(c, "%A %s", attr, c->proto->keyprompt); + if(k == nil) + goto out; + + c->state = "read challenge"; + if((nchal = convreadm(c, &chal)) < 0) + goto out; + + for(ntry=1;; ntry++){ + if(c->attr != attr) + freeattr(c->attr); + c->attr = addattrs(copyattr(attr), k->attr); + if((pw = strfindattr(k->privattr, "!password")) == nil){ + werrstr("key has no password (cannot happen?)"); + goto out; + } + npw = strlen(pw); + + switch(astype){ + case AuthApop: + ds = md5((uchar*)chal, nchal, nil, nil); + md5((uchar*)pw, npw, resp, ds); + break; + case AuthCram: + hmac_md5((uchar*)chal, nchal, (uchar*)pw, npw, resp, nil); + break; + } + + /* C->S: APOP user hex-response\n */ + if(ntry == 1) + c->state = "write user"; + else{ + sprint(c->statebuf, "write user (auth attempt #%d)", ntry); + c->state = c->statebuf; + } + if(convprint(c, "%s", strfindattr(k->attr, "user")) < 0) + goto out; + + c->state = "write response"; + if(convprint(c, "%.*H", sizeof resp, resp) < 0) + goto out; + + c->state = "read result"; + if(convreadm(c, &res) < 0) + goto out; + + if(strcmp(res, "ok") == 0) + break; + + if(strncmp(res, "bad ", 4) != 0){ + werrstr("bad result: %s", res); + goto out; + } + + c->state = "replace key"; + if((k = keyreplace(c, k, "%s", res+4)) == nil){ + c->state = "auth failed"; + werrstr("%s", res+4); + goto out; + } + free(res); + res = nil; + } + + werrstr("succeeded"); + ret = 0; + +out: + keyclose(k); + free(chal); + if(c->attr != attr) + freeattr(attr); + return ret; +} + +/* shared with auth dialing routines */ +typedef struct ServerState ServerState; +struct ServerState +{ + int asfd; + Key *k; + Ticketreq tr; + Ticket t; + char *dom; + char *hostid; +}; + +enum +{ + APOPCHALLEN = 128, +}; + +static int apopchal(ServerState*, int, char[APOPCHALLEN]); +static int apopresp(ServerState*, char*, char*); + +static int +apopserver(Conv *c) +{ + char chal[APOPCHALLEN], *user, *resp; + ServerState s; + int astype, ret; + Attr *a; + + ret = -1; + user = nil; + resp = nil; + memset(&s, 0, sizeof s); + s.asfd = -1; + + if(c->proto == &apop) + astype = AuthApop; + else if(c->proto == &cram) + astype = AuthCram; + else{ + werrstr("bad proto"); + goto out; + } + + c->state = "find key"; + if((s.k = plan9authkey(c->attr)) == nil) + goto out; + + a = copyattr(s.k->attr); + a = delattr(a, "proto"); + c->attr = addattrs(c->attr, a); + freeattr(a); + + c->state = "authdial"; + s.hostid = strfindattr(s.k->attr, "user"); + s.dom = strfindattr(s.k->attr, "dom"); + if((s.asfd = xioauthdial(nil, s.dom)) < 0){ + werrstr("authdial %s: %r", s.dom); + goto out; + } + + c->state = "authchal"; + if(apopchal(&s, astype, chal) < 0) + goto out; + + c->state = "write challenge"; + if(convprint(c, "%s", chal) < 0) + goto out; + + for(;;){ + c->state = "read user"; + if(convreadm(c, &user) < 0) + goto out; + + c->state = "read response"; + if(convreadm(c, &resp) < 0) + goto out; + + c->state = "authwrite"; + switch(apopresp(&s, user, resp)){ + case -1: + goto out; + case 0: + c->state = "write status"; + if(convprint(c, "bad authentication failed") < 0) + goto out; + break; + case 1: + c->state = "write status"; + if(convprint(c, "ok") < 0) + goto out; + goto ok; + } + free(user); + free(resp); + user = nil; + resp = nil; + } + +ok: + ret = 0; + c->attr = addcap(c->attr, c->sysuser, &s.t); + +out: + keyclose(s.k); + free(user); + free(resp); +// xioclose(s.asfd); + return ret; +} + +static int +apopchal(ServerState *s, int astype, char chal[APOPCHALLEN]) +{ + char trbuf[TICKREQLEN]; + Ticketreq tr; + + memset(&tr, 0, sizeof tr); + + tr.type = astype; + + if(strlen(s->hostid) >= sizeof tr.hostid){ + werrstr("hostid too long"); + return -1; + } + strcpy(tr.hostid, s->hostid); + + if(strlen(s->dom) >= sizeof tr.authdom){ + werrstr("domain too long"); + return -1; + } + strcpy(tr.authdom, s->dom); + + convTR2M(&tr, trbuf); + if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) + return -1; + + if(xioasrdresp(s->asfd, chal, APOPCHALLEN) <= 5) + return -1; + + s->tr = tr; + return 0; +} + +static int +apopresp(ServerState *s, char *user, char *resp) +{ + char tabuf[TICKETLEN+AUTHENTLEN]; + char trbuf[TICKREQLEN]; + int len; + Authenticator a; + Ticket t; + Ticketreq tr; + + tr = s->tr; + if(memrandom(tr.chal, CHALLEN) < 0) + return -1; + + if(strlen(user) >= sizeof tr.uid){ + werrstr("uid too long"); + return -1; + } + strcpy(tr.uid, user); + + convTR2M(&tr, trbuf); + if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) + return -1; + + len = strlen(resp); + if(xiowrite(s->asfd, resp, len) != len) + return -1; + + if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN) + return 0; + + convM2T(tabuf, &t, s->k->priv); + if(t.num != AuthTs + || memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){ + werrstr("key mismatch with auth server"); + return -1; + } + + convM2A(tabuf+TICKETLEN, &a, t.key); + if(a.num != AuthAc + || memcmp(a.chal, tr.chal, sizeof a.chal) != 0 + || a.id != 0){ + werrstr("key2 mismatch with auth server"); + return -1; + } + + s->t = t; + return 1; +} + +static Role +apoproles[] = +{ + "client", apopclient, + "server", apopserver, + 0 +}; + +Proto apop = { +.name= "apop", +.roles= apoproles, +.checkkey= apopcheck, +.keyprompt= "user? !password?", +}; + +Proto cram = { +.name= "cram", +.roles= apoproles, +.checkkey= apopcheck, +.keyprompt= "user? !password?", +}; blob - /dev/null blob + 2f2511b9748f42a4020cc3df038b88d7631903fa (mode 644) --- /dev/null +++ src/cmd/auth/factotum/attr.c @@ -0,0 +1,231 @@ +#include "std.h" +#include "dat.h" + +Attr* +addattr(Attr *a, char *fmt, ...) +{ + char buf[8192]; + va_list arg; + Attr *b; + + va_start(arg, fmt); + vseprint(buf, buf+sizeof buf, fmt, arg); + va_end(arg); + b = _parseattr(buf); + a = addattrs(a, b); + setmalloctag(a, getcallerpc(&a)); + _freeattr(b); + return a; +} + +/* + * add attributes in list b to list a. If any attributes are in + * both lists, replace those in a by those in b. + */ +Attr* +addattrs(Attr *a, Attr *b) +{ + int found; + Attr **l, *aa; + + for(; b; b=b->next){ + switch(b->type){ + case AttrNameval: + for(l=&a; *l; ){ + if(strcmp((*l)->name, b->name) != 0){ + l=&(*l)->next; + continue; + } + aa = *l; + *l = aa->next; + aa->next = nil; + freeattr(aa); + } + *l = mkattr(AttrNameval, b->name, b->val, nil); + break; + case AttrQuery: + found = 0; + for(l=&a; *l; l=&(*l)->next) + if((*l)->type==AttrNameval && strcmp((*l)->name, b->name) == 0) + found++; + if(!found) + *l = mkattr(AttrQuery, b->name, b->val, nil); + break; + } + } + return a; +} + +void +setmalloctaghere(void *v) +{ + setmalloctag(v, getcallerpc(&v)); +} + +Attr* +sortattr(Attr *a) +{ + int i; + Attr *anext, *a0, *a1, **l; + + if(a == nil || a->next == nil) + return a; + + /* cut list in halves */ + a0 = nil; + a1 = nil; + i = 0; + for(; a; a=anext){ + anext = a->next; + if(i++%2){ + a->next = a0; + a0 = a; + }else{ + a->next = a1; + a1 = a; + } + } + + /* sort */ + a0 = sortattr(a0); + a1 = sortattr(a1); + + /* merge */ + l = &a; + while(a0 || a1){ + if(a1==nil){ + anext = a0; + a0 = a0->next; + }else if(a0==nil){ + anext = a1; + a1 = a1->next; + }else if(strcmp(a0->name, a1->name) < 0){ + anext = a0; + a0 = a0->next; + }else{ + anext = a1; + a1 = a1->next; + } + *l = anext; + l = &(*l)->next; + } + *l = nil; + return a; +} + +int +attrnamefmt(Fmt *fmt) +{ + char *b, buf[8192], *ebuf; + Attr *a; + + ebuf = buf+sizeof buf; + b = buf; + strcpy(buf, " "); + for(a=va_arg(fmt->args, Attr*); a; a=a->next){ + if(a->name == nil) + continue; + b = seprint(b, ebuf, " %q?", a->name); + } + return fmtstrcpy(fmt, buf+1); +} + +/* +static int +hasqueries(Attr *a) +{ + for(; a; a=a->next) + if(a->type == AttrQuery) + return 1; + return 0; +} +*/ + +char *ignored[] = { + "role", + "disabled", +}; + +static int +ignoreattr(char *s) +{ + int i; + + for(i=0; inext, name)) + if(strcmp(a->val, val) == 0) + return 1; + for(a=_findattr(a1, name); a; a=_findattr(a->next, name)) + if(strcmp(a->val, val) == 0) + return 1; + return 0; +} + +int +matchattr(Attr *pat, Attr *a0, Attr *a1) +{ + int type; + + for(; pat; pat=pat->next){ + type = pat->type; + if(ignoreattr(pat->name)) + type = AttrDefault; + switch(type){ + case AttrQuery: /* name=something be present */ + if(!hasname(a0, a1, pat->name)) + return 0; + break; + case AttrNameval: /* name=val must be present */ + if(!hasnameval(a0, a1, pat->name, pat->val)) + return 0; + break; + case AttrDefault: /* name=val must be present if name=anything is present */ + if(hasname(a0, a1, pat->name) && !hasnameval(a0, a1, pat->name, pat->val)) + return 0; + break; + } + } + return 1; +} + +Attr* +parseattrfmtv(char *fmt, va_list arg) +{ + char *s; + Attr *a; + + s = vsmprint(fmt, arg); + if(s == nil) + sysfatal("vsmprint: out of memory"); + a = parseattr(s); + free(s); + return a; +} + +Attr* +parseattrfmt(char *fmt, ...) +{ + va_list arg; + Attr *a; + + va_start(arg, fmt); + a = parseattrfmtv(fmt, arg); + va_end(arg); + return a; +} blob - /dev/null blob + 2b2589022ebf1e0261babf88bb5704c6ae46e612 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/chap.c @@ -0,0 +1,426 @@ +/* + * CHAP, MSCHAP + * + * The client does not authenticate the server, hence no CAI + * + * Protocol: + * + * S -> C: random 8-byte challenge + * C -> S: user in UTF-8 + * C -> S: Chapreply or MSchapreply structure + * S -> C: ok or 'bad why' + * + * The chap protocol requires the client to give it id=%d, the id of + * the PPP message containing the challenge, which is used + * as part of the response. Because the client protocol is message-id + * specific, there is no point in looping to try multiple keys. + * + * The MS chap protocol actually uses two different hashes, an + * older insecure one called the LM (Lan Manager) hash, and a newer + * more secure one called the NT hash. By default we send back only + * the NT hash, because the LM hash can help an eavesdropper run + * a brute force attack. If the key has an lm attribute, then we send only the + * LM hash. + */ + +#include "std.h" +#include "dat.h" + +extern Proto chap, mschap; + +enum { + ChapChallen = 8, + + MShashlen = 16, + MSchallen = 8, + MSresplen = 24, +}; + +static int +chapcheck(Key *k) +{ + if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){ + werrstr("need user and !password attributes"); + return -1; + } + return 0; +} + +static void +nthash(uchar hash[MShashlen], char *passwd) +{ + uchar buf[512]; + int i; + + for(i=0; *passwd && i= 'a' && buf[i] <= 'z') + buf[i] += 'A' - 'a'; + + memset(hash, 0, 16); + memcpy(hash, stdtext, 8); + memcpy(hash+8, stdtext, 8); + + desencrypt(hash, buf); + desencrypt(hash+8, buf+7); +} + +static void +mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen]) +{ + int i; + uchar buf[21]; + + memset(buf, 0, sizeof(buf)); + memcpy(buf, hash, MShashlen); + + for(i=0; i<3; i++) { + memmove(resp+i*MSchallen, chal, MSchallen); + desencrypt(resp+i*MSchallen, buf+i*7); + } +} + +static int +chapclient(Conv *c) +{ + int id, astype, nchal, npw, ret; + uchar *chal; + char *s, *pw, *user, *res; + Attr *attr; + Key *k; + Chapreply cr; + MSchapreply mscr; + DigestState *ds; + + ret = -1; + chal = nil; + k = nil; + attr = c->attr; + + if(c->proto == &chap){ + astype = AuthChap; + s = strfindattr(attr, "id"); + if(s == nil || *s == 0){ + werrstr("need id=n attr in start message"); + goto out; + } + id = strtol(s, &s, 10); + if(*s != 0 || id < 0 || id >= 256){ + werrstr("bad id=n attr in start message"); + goto out; + } + cr.id = id; + }else if(c->proto == &mschap) + astype = AuthMSchap; + else{ + werrstr("bad proto"); + goto out; + } + + c->state = "find key"; + k = keyfetch(c, "%A %s", attr, c->proto->keyprompt); + if(k == nil) + goto out; + + c->attr = addattrs(copyattr(attr), k->attr); + + c->state = "read challenge"; + if((nchal = convreadm(c, (char**)(void*)&chal)) < 0) + goto out; + if(astype == AuthMSchap && nchal != MSchallen) + c->state = "write user"; + if((user = strfindattr(k->attr, "user")) == nil){ + werrstr("key has no user (cannot happen?)"); + goto out; + } + if(convprint(c, "%s", user) < 0) + goto out; + + c->state = "write response"; + if((pw = strfindattr(k->privattr, "!password")) == nil){ + werrstr("key has no password (cannot happen?)"); + goto out; + } + npw = strlen(pw); + + if(astype == AuthChap){ + ds = md5(&cr.id, 1, 0, 0); + md5((uchar*)pw, npw, 0, ds); + md5(chal, nchal, (uchar*)cr.resp, ds); + if(convwrite(c, &cr, sizeof cr) < 0) + goto out; + }else{ + uchar hash[MShashlen]; + + memset(&mscr, 0, sizeof mscr); + if(strfindattr(k->attr, "lm")){ + lmhash(hash, pw); + mschalresp((uchar*)mscr.LMresp, hash, chal); + }else{ + nthash(hash, pw); + mschalresp((uchar*)mscr.NTresp, hash, chal); + } + if(convwrite(c, &mscr, sizeof mscr) < 0) + goto out; + } + + c->state = "read result"; + if(convreadm(c, &res) < 0) + goto out; + if(strcmp(res, "ok") == 0){ + ret = 0; + werrstr("succeeded"); + goto out; + } + if(strncmp(res, "bad ", 4) != 0){ + werrstr("bad result: %s", res); + goto out; + } + + c->state = "replace key"; + keyevict(c, k, "%s", res+4); + werrstr("%s", res+4); + +out: + free(res); + keyclose(k); + free(chal); + if(c->attr != attr) + freeattr(attr); + return ret; +} + +/* shared with auth dialing routines */ +typedef struct ServerState ServerState; +struct ServerState +{ + int asfd; + Key *k; + Ticketreq tr; + Ticket t; + char *dom; + char *hostid; +}; + +static int chapchal(ServerState*, int, char[ChapChallen]); +static int chapresp(ServerState*, char*, char*); + +static int +chapserver(Conv *c) +{ + char chal[ChapChallen], *user, *resp; + ServerState s; + int astype, ret; + Attr *a; + + ret = -1; + user = nil; + resp = nil; + memset(&s, 0, sizeof s); + s.asfd = -1; + + if(c->proto == &chap) + astype = AuthChap; + else if(c->proto == &mschap) + astype = AuthMSchap; + else{ + werrstr("bad proto"); + goto out; + } + + c->state = "find key"; + if((s.k = plan9authkey(c->attr)) == nil) + goto out; + + a = copyattr(s.k->attr); + a = delattr(a, "proto"); + c->attr = addattrs(c->attr, a); + freeattr(a); + + c->state = "authdial"; + s.hostid = strfindattr(s.k->attr, "user"); + s.dom = strfindattr(s.k->attr, "dom"); + if((s.asfd = xioauthdial(nil, s.dom)) < 0){ + werrstr("authdial %s: %r", s.dom); + goto out; + } + + c->state = "authchal"; + if(chapchal(&s, astype, chal) < 0) + goto out; + + c->state = "write challenge"; + if(convprint(c, "%s", chal) < 0) + goto out; + + c->state = "read user"; + if(convreadm(c, &user) < 0) + goto out; + + c->state = "read response"; + if(convreadm(c, &resp) < 0) + goto out; + + c->state = "authwrite"; + switch(chapresp(&s, user, resp)){ + default: + fprint(2, "factotum: bad result from chapresp\n"); + goto out; + case -1: + goto out; + case 0: + c->state = "write status"; + if(convprint(c, "bad authentication failed") < 0) + goto out; + goto out; + + case 1: + c->state = "write status"; + if(convprint(c, "ok") < 0) + goto out; + goto ok; + } + +ok: + ret = 0; + c->attr = addcap(c->attr, c->sysuser, &s.t); + +out: + keyclose(s.k); + free(user); + free(resp); +// xioclose(s.asfd); + return ret; +} + +static int +chapchal(ServerState *s, int astype, char chal[ChapChallen]) +{ + char trbuf[TICKREQLEN]; + Ticketreq tr; + + memset(&tr, 0, sizeof tr); + + tr.type = astype; + + if(strlen(s->hostid) >= sizeof tr.hostid){ + werrstr("hostid too long"); + return -1; + } + strcpy(tr.hostid, s->hostid); + + if(strlen(s->dom) >= sizeof tr.authdom){ + werrstr("domain too long"); + return -1; + } + strcpy(tr.authdom, s->dom); + + convTR2M(&tr, trbuf); + if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) + return -1; + + if(xioasrdresp(s->asfd, chal, ChapChallen) <= 5) + return -1; + + s->tr = tr; + return 0; +} + +static int +chapresp(ServerState *s, char *user, char *resp) +{ + char tabuf[TICKETLEN+AUTHENTLEN]; + char trbuf[TICKREQLEN]; + int len; + Authenticator a; + Ticket t; + Ticketreq tr; + + tr = s->tr; + if(memrandom(tr.chal, CHALLEN) < 0) + return -1; + + if(strlen(user) >= sizeof tr.uid){ + werrstr("uid too long"); + return -1; + } + strcpy(tr.uid, user); + + convTR2M(&tr, trbuf); + if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) + return -1; + + len = strlen(resp); + if(xiowrite(s->asfd, resp, len) != len) + return -1; + + if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN) + return 0; + + convM2T(tabuf, &t, s->k->priv); + if(t.num != AuthTs + || memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){ + werrstr("key mismatch with auth server"); + return -1; + } + + convM2A(tabuf+TICKETLEN, &a, t.key); + if(a.num != AuthAc + || memcmp(a.chal, tr.chal, sizeof a.chal) != 0 + || a.id != 0){ + werrstr("key2 mismatch with auth server"); + return -1; + } + + s->t = t; + return 1; +} + +static Role +chaproles[] = +{ + "client", chapclient, + "server", chapserver, + 0 +}; + +Proto chap = { +.name= "chap", +.roles= chaproles, +.checkkey= chapcheck, +.keyprompt= "user? !password?", +}; + +Proto mschap = { +.name= "mschap", +.roles= chaproles, +.checkkey= chapcheck, +.keyprompt= "user? !password?", +}; + blob - /dev/null blob + 8f49245043b90a6fed49bd1f90336db5d1ac1b16 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/confirm.c @@ -0,0 +1,139 @@ +#include "std.h" +#include "dat.h" + +Logbuf confbuf; + +void +confirmread(Req *r) +{ + lbread(&confbuf, r); +} + +void +confirmflush(Req *r) +{ + lbflush(&confbuf, r); +} + +int +confirmwrite(char *s) +{ + char *t, *ans; + int allow; + ulong tag; + Attr *a; + Conv *c; + + a = _parseattr(s); + if(a == nil){ + werrstr("bad attr"); + return -1; + } + if((t = _strfindattr(a, "tag")) == nil){ + werrstr("no tag"); + return -1; + } + tag = strtoul(t, 0, 0); + if((ans = _strfindattr(a, "answer")) == nil){ + werrstr("no answer"); + return -1; + } + if(strcmp(ans, "yes") == 0) + allow = 1; + else if(strcmp(ans, "no") == 0) + allow = 0; + else{ + werrstr("bad answer"); + return -1; + } + for(c=conv; c; c=c->next){ + if(tag == c->tag){ + nbsendul(c->keywait, allow); + break; + } + } + if(c == nil){ + werrstr("tag not found"); + return -1; + } + return 0; +} + +int +confirmkey(Conv *c, Key *k) +{ + if(*confirminuse == 0) + return -1; + + lbappend(&confbuf, "confirm tag=%lud %A %N", c->tag, k->attr, k->privattr); + c->state = "keyconfirm"; + return recvul(c->keywait); +} + +Logbuf needkeybuf; + +void +needkeyread(Req *r) +{ + lbread(&needkeybuf, r); +} + +void +needkeyflush(Req *r) +{ + lbflush(&needkeybuf, r); +} + +int +needkeywrite(char *s) +{ + char *t; + ulong tag; + Attr *a; + Conv *c; + + a = _parseattr(s); + if(a == nil){ + werrstr("empty write"); + return -1; + } + if((t = _strfindattr(a, "tag")) == nil){ + werrstr("no tag"); + freeattr(a); + return -1; + } + tag = strtoul(t, 0, 0); + for(c=conv; c; c=c->next) + if(c->tag == tag){ + nbsendul(c->keywait, 0); + break; + } + if(c == nil){ + werrstr("tag not found"); + freeattr(a); + return -1; + } + freeattr(a); + return 0; +} + +int +needkey(Conv *c, Attr *a) +{ + if(c == nil || *needkeyinuse == 0) + return -1; + + lbappend(&needkeybuf, "needkey tag=%lud %A", c->tag, a); + return nbrecvul(c->keywait); +} + +int +badkey(Conv *c, Key *k, char *msg, Attr *a) +{ + if(c == nil || *needkeyinuse == 0) + return -1; + + lbappend(&needkeybuf, "badkey tag=%lud %A %N\n%s\n%A", + c->tag, k->attr, k->privattr, msg, a); + return nbrecvul(c->keywait); +} blob - /dev/null blob + 862993f9a5d0ba879789de766123e5c1bda715e9 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/conv.c @@ -0,0 +1,254 @@ +#include "std.h" +#include "dat.h" + +Conv *conv; + +ulong taggen = 1; + +Conv* +convalloc(char *sysuser) +{ + Conv *c; + + c = mallocz(sizeof(Conv), 1); + if(c == nil) + return nil; + c->ref = 1; + c->tag = taggen++; + c->next = conv; + c->sysuser = estrdup(sysuser); + c->state = "nascent"; + c->rpcwait = chancreate(sizeof(void*), 0); + c->keywait = chancreate(sizeof(void*), 0); + strcpy(c->err, "protocol has not started"); + conv = c; + convreset(c); + return c; +} + +void +convreset(Conv *c) +{ + if(c->ref != 1){ + c->hangup = 1; + nbsendp(c->rpcwait, 0); + while(c->ref > 1) + yield(); + c->hangup = 0; + } + c->state = "nascent"; + c->err[0] = '\0'; + freeattr(c->attr); + c->attr = nil; + c->proto = nil; + c->rpc.op = 0; + c->active = 0; + c->done = 0; + c->hangup = 0; +} + +void +convhangup(Conv *c) +{ + c->hangup = 1; + c->rpc.op = 0; + (*c->kickreply)(c); + nbsendp(c->rpcwait, 0); +} + +void +convclose(Conv *c) +{ + Conv *p; + + if(c == nil) + return; + + if(--c->ref > 0) + return; + + if(c == conv){ + conv = c->next; + goto free; + } + for(p=conv; p && p->next!=c; p=p->next) + ; + if(p == nil){ + print("cannot find conv in list\n"); + return; + } + p->next = c->next; + +free: + c->next = nil; + free(c); +} + +static Rpc* +convgetrpc(Conv *c, int want) +{ + for(;;){ + if(c->hangup){ + werrstr("hangup"); + return nil; + } + if(c->rpc.op == RpcUnknown){ + recvp(c->rpcwait); + if(c->hangup){ + werrstr("hangup"); + return nil; + } + if(c->rpc.op == RpcUnknown) + continue; + } + if(want < 0 || c->rpc.op == want) + return &c->rpc; + rpcrespond(c, "phase in state '%s' want '%s'", c->state, rpcname[want]); + } + return nil; /* not reached */ +} + +/* read until the done function tells us that's enough */ +int +convreadfn(Conv *c, int (*done)(void*, int), char **ps) +{ + int n; + Rpc *r; + char *s; + + for(;;){ + r = convgetrpc(c, RpcWrite); + if(r == nil) + return -1; + n = (*done)(r->data, r->count); + if(n == r->count) + break; + rpcrespond(c, "toosmall %d", n); + } + + s = emalloc(r->count+1); + memmove(s, r->data, r->count); + s[r->count] = 0; + *ps = s; + rpcrespond(c, "ok"); + return r->count; +} + +/* + * read until we get a non-zero write. assumes remote side + * knows something about the protocol (is not auth_proxy). + * the remote side typically won't bother with the zero-length + * write to find out the length -- the loop is there only so the + * test program can call auth_proxy on both sides of a pipe + * to play a conversation. + */ +int +convreadm(Conv *c, char **ps) +{ + char *s; + Rpc *r; + + for(;;){ + r = convgetrpc(c, RpcWrite); + if(r == nil) + return -1; + if(r->count > 0) + break; + rpcrespond(c, "toosmall %d", AuthRpcMax); + } + s = emalloc(r->count+1); + memmove(s, r->data, r->count); + s[r->count] = 0; + *ps = s; + rpcrespond(c, "ok"); + return r->count; +} + +/* read exactly count bytes */ +int +convread(Conv *c, void *data, int count) +{ + Rpc *r; + + for(;;){ + r = convgetrpc(c, RpcWrite); + if(r == nil) + return -1; + if(r->count == count) + break; + if(r->count < count) + rpcrespond(c, "toosmall %d", count); + else + rpcrespond(c, "error too much data; want %d got %d", count, r->count); + } + memmove(data, r->data, count); + rpcrespond(c, "ok"); + return 0; +} + +/* write exactly count bytes */ +int +convwrite(Conv *c, void *data, int count) +{ + Rpc *r; + + for(;;){ + r = convgetrpc(c, RpcRead); + if(r == nil) + return -1; + break; + } + rpcrespondn(c, "ok", data, count); + return 0; +} + +/* print to the conversation */ +int +convprint(Conv *c, char *fmt, ...) +{ + char *s; + va_list arg; + int ret; + + va_start(arg, fmt); + s = vsmprint(fmt, arg); + va_end(arg); + if(s == nil) + return -1; + ret = convwrite(c, s, strlen(s)); + free(s); + return ret; +} + +/* ask for a key */ +int +convneedkey(Conv *c, Attr *a) +{ + /* + * Piggyback key requests in the usual RPC channel. + * Wait for the next RPC and then send a key request + * in response. The keys get added out-of-band (via the + * ctl file), so assume the key has been added when the + * next request comes in. + */ + if(convgetrpc(c, -1) == nil) + return -1; + rpcrespond(c, "needkey %A", a); + if(convgetrpc(c, -1) == nil) + return -1; + return 0; +} + +/* ask for a replacement for a bad key*/ +int +convbadkey(Conv *c, Key *k, char *msg, Attr *a) +{ + if(convgetrpc(c, -1) == nil) + return -1; + rpcrespond(c, "badkey %A %N\n%s\n%A", + k->attr, k->privattr, msg, a); + if(convgetrpc(c, -1) == nil) + return -1; + return 0; +} + blob - /dev/null blob + da8280ada929d6305234c49dc6c57fadea6876e7 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/cpu.c @@ -0,0 +1,1117 @@ +/* + * cpu.c - Make a connection to a cpu server + * + * Invoked by listen as 'cpu -R | -N service net netdir' + * by users as 'cpu [-h system] [-c cmd args ...]' + */ + +#include +#include +#include +#include +#include +#include + +#define Maxfdata 8192 + +void remoteside(int); +void fatal(int, char*, ...); +void lclnoteproc(int); +void rmtnoteproc(void); +void catcher(void*, char*); +void usage(void); +void writestr(int, char*, char*, int); +int readstr(int, char*, int); +char *rexcall(int*, char*, char*); +int setamalg(char*); + +int notechan; +char system[32]; +int cflag; +int hflag; +int dbg; +char *user; + +char *srvname = "ncpu"; +char *exportfs = "/bin/exportfs"; +char *ealgs = "rc4_256 sha1"; + +/* message size for exportfs; may be larger so we can do big graphics in CPU window */ +int msgsize = 8192+IOHDRSZ; + +/* authentication mechanisms */ +static int netkeyauth(int); +static int netkeysrvauth(int, char*); +static int p9auth(int); +static int srvp9auth(int, char*); +static int noauth(int); +static int srvnoauth(int, char*); + +typedef struct AuthMethod AuthMethod; +struct AuthMethod { + char *name; /* name of method */ + int (*cf)(int); /* client side authentication */ + int (*sf)(int, char*); /* server side authentication */ +} authmethod[] = +{ + { "p9", p9auth, srvp9auth,}, + { "netkey", netkeyauth, netkeysrvauth,}, +// { "none", noauth, srvnoauth,}, + { nil, nil} +}; +AuthMethod *am = authmethod; /* default is p9 */ + +char *p9authproto = "p9any"; + +int setam(char*); + +void +usage(void) +{ + fprint(2, "usage: cpu [-h system] [-a authmethod] [-e 'crypt hash'] [-c cmd args ...]\n"); + exits("usage"); +} +int fdd; + +void +main(int argc, char **argv) +{ + char dat[128], buf[128], cmd[128], *p, *err; + int fd, ms, kms, data; + + /* see if we should use a larger message size */ + fd = open("/dev/draw", OREAD); + if(fd > 0){ + ms = iounit(fd); + if(ms != 0 && ms < ms+IOHDRSZ) + msgsize = ms+IOHDRSZ; + close(fd); + } + kms = kiounit(); + if(msgsize > kms-IOHDRSZ-100) /* 100 for network packets, etc. */ + msgsize = kms-IOHDRSZ-100; + + user = getuser(); + if(user == nil) + fatal(1, "can't read user name"); + ARGBEGIN{ + case 'a': + p = EARGF(usage()); + if(setam(p) < 0) + fatal(0, "unknown auth method %s", p); + break; + case 'e': + ealgs = EARGF(usage()); + if(*ealgs == 0 || strcmp(ealgs, "clear") == 0) + ealgs = nil; + break; + case 'd': + dbg++; + break; + case 'f': + /* ignored but accepted for compatibility */ + break; + case 'O': + p9authproto = "p9sk2"; + remoteside(1); /* From listen */ + break; + case 'R': /* From listen */ + remoteside(0); + break; + case 'h': + hflag++; + p = EARGF(usage()); + strcpy(system, p); + break; + case 'c': + cflag++; + cmd[0] = '!'; + cmd[1] = '\0'; + while(p = ARGF()) { + strcat(cmd, " "); + strcat(cmd, p); + } + break; + case 'o': + p9authproto = "p9sk2"; + srvname = "cpu"; + break; + case 'u': + user = EARGF(usage()); + break; + default: + usage(); + }ARGEND; + + + if(argc != 0) + usage(); + + if(hflag == 0) { + p = getenv("cpu"); + if(p == 0) + fatal(0, "set $cpu"); + strcpy(system, p); + } + + if(err = rexcall(&data, system, srvname)) + fatal(1, "%s: %s", err, system); + + /* Tell the remote side the command to execute and where our working directory is */ + if(cflag) + writestr(data, cmd, "command", 0); + if(getwd(dat, sizeof(dat)) == 0) + writestr(data, "NO", "dir", 0); + else + writestr(data, dat, "dir", 0); + + /* start up a process to pass along notes */ + lclnoteproc(data); + + /* + * Wait for the other end to execute and start our file service + * of /mnt/term + */ + if(readstr(data, buf, sizeof(buf)) < 0) + fatal(1, "waiting for FS"); + if(strncmp("FS", buf, 2) != 0) { + print("remote cpu: %s", buf); + exits(buf); + } + + /* Begin serving the gnot namespace */ + close(0); + dup(data, 0); + close(data); + sprint(buf, "%d", msgsize); + if(dbg) + execl(exportfs, exportfs, "-dm", buf, 0); + else + execl(exportfs, exportfs, "-m", buf, 0); + fatal(1, "starting exportfs"); +} + +void +fatal(int syserr, char *fmt, ...) +{ + char buf[ERRMAX]; + va_list arg; + + va_start(arg, fmt); + doprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + if(syserr) + fprint(2, "cpu: %s: %r\n", buf); + else + fprint(2, "cpu: %s\n", buf); + exits(buf); +} + +char *negstr = "negotiating authentication method"; + +char bug[256]; + +int +old9p(int fd) +{ + int p[2]; + + if(pipe(p) < 0) + fatal(1, "pipe"); + + switch(rfork(RFPROC|RFFDG|RFNAMEG)) { + case -1: + fatal(1, "rfork srvold9p"); + case 0: + if(fd != 1){ + dup(fd, 1); + close(fd); + } + if(p[0] != 0){ + dup(p[0], 0); + close(p[0]); + } + close(p[1]); + if(0){ + fd = open("/sys/log/cpu", OWRITE); + if(fd != 2){ + dup(fd, 2); + close(fd); + } + execl("/bin/srvold9p", "srvold9p", "-ds", 0); + } else + execl("/bin/srvold9p", "srvold9p", "-s", 0); + fatal(1, "exec srvold9p"); + default: + close(fd); + close(p[0]); + } + return p[1]; +} + +/* Invoked with stdin, stdout and stderr connected to the network connection */ +void +remoteside(int old) +{ + char user[128], home[128], buf[128], xdir[128], cmd[128]; + int i, n, fd, badchdir, gotcmd; + + fd = 0; + + /* negotiate authentication mechanism */ + n = readstr(fd, cmd, sizeof(cmd)); + if(n < 0) + fatal(1, "authenticating"); + if(setamalg(cmd) < 0){ + writestr(fd, "unsupported auth method", nil, 0); + fatal(1, "bad auth method %s", cmd); + } else + writestr(fd, "", "", 1); + + fd = (*am->sf)(fd, user); + if(fd < 0) + fatal(1, "srvauth"); + + /* Set environment values for the user */ + putenv("user", user); + sprint(home, "/usr/%s", user); + putenv("home", home); + + /* Now collect invoking cpu's current directory or possibly a command */ + gotcmd = 0; + if(readstr(fd, xdir, sizeof(xdir)) < 0) + fatal(1, "dir/cmd"); + if(xdir[0] == '!') { + strcpy(cmd, &xdir[1]); + gotcmd = 1; + if(readstr(fd, xdir, sizeof(xdir)) < 0) + fatal(1, "dir"); + } + + /* Establish the new process at the current working directory of the + * gnot */ + badchdir = 0; + if(strcmp(xdir, "NO") == 0) + chdir(home); + else if(chdir(xdir) < 0) { + badchdir = 1; + chdir(home); + } + + /* Start the gnot serving its namespace */ + writestr(fd, "FS", "FS", 0); + writestr(fd, "/", "exportfs dir", 0); + + n = read(fd, buf, sizeof(buf)); + if(n != 2 || buf[0] != 'O' || buf[1] != 'K') + exits("remote tree"); + + if(old) + fd = old9p(fd); + + /* make sure buffers are big by doing fversion explicitly; pick a huge number; other side will trim */ + strcpy(buf, VERSION9P); + if(fversion(fd, 64*1024, buf, sizeof buf) < 0) + exits("fversion failed"); + if(mount(fd, -1, "/mnt/term", MCREATE|MREPL, "") < 0) + exits("mount failed"); + + close(fd); + + /* the remote noteproc uses the mount so it must follow it */ + rmtnoteproc(); + + for(i = 0; i < 3; i++) + close(i); + + if(open("/mnt/term/dev/cons", OREAD) != 0) + exits("open stdin"); + if(open("/mnt/term/dev/cons", OWRITE) != 1) + exits("open stdout"); + dup(1, 2); + + if(badchdir) + print("cpu: failed to chdir to '%s'\n", xdir); + + if(gotcmd) + execl("/bin/rc", "rc", "-lc", cmd, 0); + else + execl("/bin/rc", "rc", "-li", 0); + fatal(1, "exec shell"); +} + +char* +rexcall(int *fd, char *host, char *service) +{ + char *na; + char dir[128]; + char err[ERRMAX]; + char msg[128]; + int n; + + na = netmkaddr(host, 0, service); + if((*fd = dial(na, 0, dir, 0)) < 0) + return "can't dial"; + + /* negotiate authentication mechanism */ + if(ealgs != nil) + snprint(msg, sizeof(msg), "%s %s", am->name, ealgs); + else + snprint(msg, sizeof(msg), "%s", am->name); + writestr(*fd, msg, negstr, 0); + n = readstr(*fd, err, sizeof err); + if(n < 0) + return negstr; + if(*err){ + werrstr(err); + return negstr; + } + + /* authenticate */ + *fd = (*am->cf)(*fd); + if(*fd < 0) + return "can't authenticate"; + return 0; +} + +void +writestr(int fd, char *str, char *thing, int ignore) +{ + int l, n; + + l = strlen(str); + n = write(fd, str, l+1); + if(!ignore && n < 0) + fatal(1, "writing network: %s", thing); +} + +int +readstr(int fd, char *str, int len) +{ + int n; + + while(len) { + n = read(fd, str, 1); + if(n < 0) + return -1; + if(*str == '\0') + return 0; + str++; + len--; + } + return -1; +} + +static int +readln(char *buf, int n) +{ + char *p = buf; + + n--; + while(n > 0){ + if(read(0, p, 1) != 1) + break; + if(*p == '\n' || *p == '\r'){ + *p = 0; + return p-buf; + } + p++; + } + *p = 0; + return p-buf; +} + +/* + * user level challenge/response + */ +static int +netkeyauth(int fd) +{ + char chall[32]; + char resp[32]; + + strcpy(chall, getuser()); + print("user[%s]: ", chall); + if(readln(resp, sizeof(resp)) < 0) + return -1; + if(*resp != 0) + strcpy(chall, resp); + writestr(fd, chall, "challenge/response", 1); + + for(;;){ + if(readstr(fd, chall, sizeof chall) < 0) + break; + if(*chall == 0) + return fd; + print("challenge: %s\nresponse: ", chall); + if(readln(resp, sizeof(resp)) < 0) + break; + writestr(fd, resp, "challenge/response", 1); + } + return -1; +} + +static int +netkeysrvauth(int fd, char *user) +{ + char response[32]; + Chalstate *ch; + int tries; + AuthInfo *ai; + + if(readstr(fd, user, 32) < 0) + return -1; + + ai = nil; + ch = nil; + for(tries = 0; tries < 10; tries++){ + if((ch = auth_challenge("p9cr", user, nil)) == nil) + return -1; + writestr(fd, ch->chal, "challenge", 1); + if(readstr(fd, response, sizeof response) < 0) + return -1; + ch->resp = response; + ch->nresp = strlen(response); + if((ai = auth_response(ch)) != nil) + break; + } + auth_freechal(ch); + if(ai == nil) + return -1; + writestr(fd, "", "challenge", 1); + if(auth_chuid(ai, 0) < 0) + fatal(1, "newns"); + auth_freeAI(ai); + return fd; +} + +static void +mksecret(char *t, uchar *f) +{ + sprint(t, "%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux", + f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9]); +} + +/* + * plan9 authentication followed by rc4 encryption + */ +static int +p9auth(int fd) +{ + uchar key[16]; + uchar digest[SHA1dlen]; + char fromclientsecret[21]; + char fromserversecret[21]; + int i; + AuthInfo *ai; + + ai = auth_proxy(fd, auth_getkey, "proto=%q user=%q role=client", p9authproto, user); + if(ai == nil) + return -1; + memmove(key+4, ai->secret, ai->nsecret); + if(ealgs == nil) + return fd; + + /* exchange random numbers */ + srand(truerand()); + for(i = 0; i < 4; i++) + key[i] = rand(); + if(write(fd, key, 4) != 4) + return -1; + if(readn(fd, key+12, 4) != 4) + return -1; + + /* scramble into two secrets */ + sha1(key, sizeof(key), digest, nil); + mksecret(fromclientsecret, digest); + mksecret(fromserversecret, digest+10); + + /* set up encryption */ + i = pushssl(fd, ealgs, fromclientsecret, fromserversecret, nil); + if(i < 0) + werrstr("can't establish ssl connection: %r"); + return i; +} + +static int +noauth(int fd) +{ + ealgs = nil; + return fd; +} + +static int +srvnoauth(int fd, char *user) +{ + strcpy(user, getuser()); + ealgs = nil; + return fd; +} + +void +loghex(uchar *p, int n) +{ + char buf[100]; + int i; + + for(i = 0; i < n; i++) + sprint(buf+2*i, "%2.2ux", p[i]); + syslog(0, "cpu", buf); +} + +static int +srvp9auth(int fd, char *user) +{ + uchar key[16]; + uchar digest[SHA1dlen]; + char fromclientsecret[21]; + char fromserversecret[21]; + int i; + AuthInfo *ai; + + ai = auth_proxy(0, nil, "proto=%q role=server", p9authproto); + if(ai == nil) + return -1; + if(auth_chuid(ai, nil) < 0) + return -1; + strcpy(user, ai->cuid); + memmove(key+4, ai->secret, ai->nsecret); + + if(ealgs == nil) + return fd; + + /* exchange random numbers */ + srand(truerand()); + for(i = 0; i < 4; i++) + key[i+12] = rand(); + if(readn(fd, key, 4) != 4) + return -1; + if(write(fd, key+12, 4) != 4) + return -1; + + /* scramble into two secrets */ + sha1(key, sizeof(key), digest, nil); + mksecret(fromclientsecret, digest); + mksecret(fromserversecret, digest+10); + + /* set up encryption */ + i = pushssl(fd, ealgs, fromserversecret, fromclientsecret, nil); + if(i < 0) + werrstr("can't establish ssl connection: %r"); + return i; +} + +/* + * set authentication mechanism + */ +int +setam(char *name) +{ + for(am = authmethod; am->name != nil; am++) + if(strcmp(am->name, name) == 0) + return 0; + am = authmethod; + return -1; +} + +/* + * set authentication mechanism and encryption/hash algs + */ +int +setamalg(char *s) +{ + ealgs = strchr(s, ' '); + if(ealgs != nil) + *ealgs++ = 0; + return setam(s); +} + +char *rmtnotefile = "/mnt/term/dev/cpunote"; + +/* + * loop reading /mnt/term/dev/note looking for notes. + * The child returns to start the shell. + */ +void +rmtnoteproc(void) +{ + int n, fd, pid, notepid; + char buf[256]; + + /* new proc returns to start shell */ + pid = rfork(RFPROC|RFFDG|RFNOTEG|RFNAMEG|RFMEM); + switch(pid){ + case -1: + syslog(0, "cpu", "cpu -R: can't start noteproc: %r"); + return; + case 0: + return; + } + + /* new proc reads notes from other side and posts them to shell */ + switch(notepid = rfork(RFPROC|RFFDG|RFMEM)){ + case -1: + syslog(0, "cpu", "cpu -R: can't start wait proc: %r"); + _exits(0); + case 0: + fd = open(rmtnotefile, OREAD); + if(fd < 0){ + syslog(0, "cpu", "cpu -R: can't open %s", rmtnotefile); + _exits(0); + } + + for(;;){ + n = read(fd, buf, sizeof(buf)-1); + if(n <= 0){ + postnote(PNGROUP, pid, "hangup"); + _exits(0); + } + buf[n] = 0; + postnote(PNGROUP, pid, buf); + } + break; + } + + /* original proc waits for shell proc to die and kills note proc */ + for(;;){ + n = waitpid(); + if(n < 0 || n == pid) + break; + } + postnote(PNPROC, notepid, "kill"); + _exits(0); +} + +enum +{ + Qdir, + Qcpunote, + + Nfid = 32, +}; + +struct { + char *name; + Qid qid; + ulong perm; +} fstab[] = +{ + [Qdir] { ".", {Qdir, 0, QTDIR}, DMDIR|0555 }, + [Qcpunote] { "cpunote", {Qcpunote, 0}, 0444 }, +}; + +typedef struct Note Note; +struct Note +{ + Note *next; + char msg[ERRMAX]; +}; + +typedef struct Request Request; +struct Request +{ + Request *next; + Fcall f; +}; + +typedef struct Fid Fid; +struct Fid +{ + int fid; + int file; +}; +Fid fids[Nfid]; + +struct { + Lock; + Note *nfirst, *nlast; + Request *rfirst, *rlast; +} nfs; + +int +fsreply(int fd, Fcall *f) +{ + uchar buf[IOHDRSZ+Maxfdata]; + int n; + + if(dbg) + fprint(2, "<-%F\n", f); + n = convS2M(f, buf, sizeof buf); + if(n > 0){ + if(write(fd, buf, n) != n){ + close(fd); + return -1; + } + } + return 0; +} + +/* match a note read request with a note, reply to the request */ +int +kick(int fd) +{ + Request *rp; + Note *np; + int rv; + + for(;;){ + lock(&nfs); + rp = nfs.rfirst; + np = nfs.nfirst; + if(rp == nil || np == nil){ + unlock(&nfs); + break; + } + nfs.rfirst = rp->next; + nfs.nfirst = np->next; + unlock(&nfs); + + rp->f.type = Rread; + rp->f.count = strlen(np->msg); + rp->f.data = np->msg; + rv = fsreply(fd, &rp->f); + free(rp); + free(np); + if(rv < 0) + return -1; + } + return 0; +} + +void +flushreq(int tag) +{ + Request **l, *rp; + + lock(&nfs); + for(l = &nfs.rfirst; *l != nil; l = &(*l)->next){ + rp = *l; + if(rp->f.tag == tag){ + *l = rp->next; + unlock(&nfs); + free(rp); + return; + } + } + unlock(&nfs); +} + +Fid* +getfid(int fid) +{ + int i, freefid; + + freefid = -1; + for(i = 0; i < Nfid; i++){ + if(freefid < 0 && fids[i].file < 0) + freefid = i; + if(fids[i].fid == fid) + return &fids[i]; + } + if(freefid >= 0){ + fids[freefid].fid = fid; + return &fids[freefid]; + } + return nil; +} + +int +fsstat(int fd, Fid *fid, Fcall *f) +{ + Dir d; + uchar statbuf[256]; + + memset(&d, 0, sizeof(d)); + d.name = fstab[fid->file].name; + d.uid = user; + d.gid = user; + d.muid = user; + d.qid = fstab[fid->file].qid; + d.mode = fstab[fid->file].perm; + d.atime = d.mtime = time(0); + f->stat = statbuf; + f->nstat = convD2M(&d, statbuf, sizeof statbuf); + return fsreply(fd, f); +} + +int +fsread(int fd, Fid *fid, Fcall *f) +{ + Dir d; + uchar buf[256]; + Request *rp; + + switch(fid->file){ + default: + return -1; + case Qdir: + if(f->offset == 0 && f->count >0){ + memset(&d, 0, sizeof(d)); + d.name = fstab[Qcpunote].name; + d.uid = user; + d.gid = user; + d.muid = user; + d.qid = fstab[Qcpunote].qid; + d.mode = fstab[Qcpunote].perm; + d.atime = d.mtime = time(0); + f->count = convD2M(&d, buf, sizeof buf); + f->data = (char*)buf; + } else + f->count = 0; + return fsreply(fd, f); + case Qcpunote: + rp = mallocz(sizeof(*rp), 1); + if(rp == nil) + return -1; + rp->f = *f; + lock(&nfs); + if(nfs.rfirst == nil) + nfs.rfirst = rp; + else + nfs.rlast->next = rp; + nfs.rlast = rp; + unlock(&nfs); + return kick(fd);; + } +} + +char Eperm[] = "permission denied"; +char Enofile[] = "out of files"; +char Enotdir[] = "not a directory"; + +void +notefs(int fd) +{ + uchar buf[IOHDRSZ+Maxfdata]; + int i, j, n; + char err[ERRMAX]; + Fcall f; + Fid *fid, *nfid; + int doreply; + + rfork(RFNOTEG); + fmtinstall('F', fcallconv); + + for(n = 0; n < Nfid; n++) + fids[n].file = -1; + + for(;;){ + n = read9pmsg(fd, buf, sizeof(buf)); + if(n <= 0){ + if(dbg) + fprint(2, "read9pmsg(%d) returns %d: %r\n", fd, n); + break; + } + if(convM2S(buf, n, &f) < 0) + break; + if(dbg) + fprint(2, "->%F\n", &f); + doreply = 1; + fid = getfid(f.fid); + if(fid == nil){ +nofids: + f.type = Rerror; + f.ename = Enofile; + fsreply(fd, &f); + continue; + } + switch(f.type++){ + default: + f.type = Rerror; + f.ename = "unknown type"; + break; + case Tflush: + flushreq(f.oldtag); + break; + case Tversion: + if(f.msize > IOHDRSZ+Maxfdata) + f.msize = IOHDRSZ+Maxfdata; + break; + case Tauth: + f.type = Rerror; + f.ename = "cpu: authentication not required"; + break; + case Tattach: + f.qid = fstab[Qdir].qid; + fid->file = Qdir; + break; + case Twalk: + nfid = nil; + if(f.newfid != f.fid){ + nfid = getfid(f.newfid); + if(nfid == nil) + goto nofids; + nfid->file = fid->file; + fid = nfid; + } + + f.ename = nil; + for(i=0; i MAXWELEM){ + f.type = Rerror; + f.ename = "too many name elements"; + break; + } + if(fid->file != Qdir){ + f.type = Rerror; + f.ename = Enotdir; + break; + } + if(strcmp(f.wname[i], "cpunote") == 0){ + fid->file = Qcpunote; + f.wqid[i] = fstab[Qcpunote].qid; + continue; + } + f.type = Rerror; + f.ename = err; + strcpy(err, "cpu: file \""); + for(j=0; j<=i; j++){ + if(strlen(err)+1+strlen(f.wname[j])+32 > sizeof err) + break; + if(j != 0) + strcat(err, "/"); + strcat(err, f.wname[j]); + } + strcat(err, "\" does not exist"); + break; + } + if(nfid != nil && (f.ename != nil || i < f.nwname)) + nfid ->file = -1; + if(f.type != Rerror) + f.nwqid = i; + break; + case Topen: + if(f.mode != OREAD){ + f.type = Rerror; + f.ename = Eperm; + } + f.qid = fstab[fid->file].qid; + break; + case Tcreate: + f.type = Rerror; + f.ename = Eperm; + break; + case Tread: + if(fsread(fd, fid, &f) < 0) + goto err; + doreply = 0; + break; + case Twrite: + f.type = Rerror; + f.ename = Eperm; + break; + case Tclunk: + fid->file = -1; + break; + case Tremove: + f.type = Rerror; + f.ename = Eperm; + break; + case Tstat: + if(fsstat(fd, fid, &f) < 0) + goto err; + doreply = 0; + break; + case Twstat: + f.type = Rerror; + f.ename = Eperm; + break; + } + if(doreply) + if(fsreply(fd, &f) < 0) + break; + } +err: + if(dbg) + fprint(2, "notefs exiting: %r\n"); + close(fd); +} + +char notebuf[ERRMAX]; + +void +catcher(void*, char *text) +{ + int n; + + n = strlen(text); + if(n >= sizeof(notebuf)) + n = sizeof(notebuf)-1; + memmove(notebuf, text, n); + notebuf[n] = '\0'; + noted(NCONT); +} + +/* + * mount in /dev a note file for the remote side to read. + */ +void +lclnoteproc(int netfd) +{ + int exportfspid; + Waitmsg *w; + Note *np; + int pfd[2]; + + if(pipe(pfd) < 0){ + fprint(2, "cpu: can't start note proc: pipe: %r\n"); + return; + } + + /* new proc mounts and returns to start exportfs */ + switch(exportfspid = rfork(RFPROC|RFNAMEG|RFFDG|RFMEM)){ + case -1: + fprint(2, "cpu: can't start note proc: rfork: %r\n"); + return; + case 0: + close(pfd[0]); + if(mount(pfd[1], -1, "/dev", MBEFORE, "") < 0) + fprint(2, "cpu: can't mount note proc: %r\n"); + close(pfd[1]); + return; + } + + close(netfd); + close(pfd[1]); + + /* new proc listens for note file system rpc's */ + switch(rfork(RFPROC|RFNAMEG|RFMEM)){ + case -1: + fprint(2, "cpu: can't start note proc: rfork1: %r\n"); + _exits(0); + case 0: + notefs(pfd[0]); + _exits(0); + } + + /* original proc waits for notes */ + notify(catcher); + w = nil; + for(;;) { + *notebuf = 0; + free(w); + w = wait(); + if(w == nil) { + if(*notebuf == 0) + break; + np = mallocz(sizeof(Note), 1); + if(np != nil){ + strcpy(np->msg, notebuf); + lock(&nfs); + if(nfs.nfirst == nil) + nfs.nfirst = np; + else + nfs.nlast->next = np; + nfs.nlast = np; + unlock(&nfs); + kick(pfd[0]); + } + unlock(&nfs); + } else if(w->pid == exportfspid) + break; + } + + if(w == nil) + exits(nil); + exits(w->msg); +} blob - /dev/null blob + df44b97da1a66add949dfc0478761c60fa4a2895 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/ctl.c @@ -0,0 +1,158 @@ +#include "std.h" +#include "dat.h" + +/* + * key attr=val... - add a key + * the attr=val pairs are protocol-specific. + * for example, both of these are valid: + * key p9sk1 gre cs.bell-labs.com mysecret + * key p9sk1 gre cs.bell-labs.com 11223344556677 fmt=des7hex + * delkey ... - delete a key + * if given, the attr=val pairs are used to narrow the search + * [maybe should require a password?] + * + * debug - toggle debugging + */ + +static char *msg[] = { + "key", + "delkey", + "debug", +}; + +static int +classify(char *s) +{ + int i; + + for(i=0; i/mnt/factotum/ctl" + * and writes that (incorrectly) contain multiple key lines. + */ + if(p = strchr(a, '\n')){ + if(p[1] != '\0'){ + werrstr("multiline write not allowed"); + return -1; + } + *p = '\0'; + } + + if((p = strchr(a, ' ')) == nil) + p = ""; + else + *p++ = '\0'; + switch(classify(a)){ + default: + werrstr("unknown verb"); + return -1; + case 0: /* key */ + attr = parseattr(p); + /* separate out proto= attributes */ + lprotos = &protos; + for(l=&attr; (*l); ){ + if(strcmp((*l)->name, "proto") == 0){ + *lprotos = *l; + lprotos = &(*l)->next; + *l = (*l)->next; + }else + l = &(*l)->next; + } + *lprotos = nil; + if(protos == nil){ + werrstr("key without protos"); + freeattr(attr); + return -1; + } + + /* separate out private attributes */ + lpriv = &priv; + for(l=&attr; (*l); ){ + if((*l)->name[0] == '!'){ + *lpriv = *l; + lpriv = &(*l)->next; + *l = (*l)->next; + }else + l = &(*l)->next; + } + *lpriv = nil; + + /* add keys */ + ret = 0; + for(pa=protos; pa; pa=pa->next){ + if((proto = protolookup(pa->val)) == nil){ + werrstr("unknown proto %s", pa->val); + ret = -1; + continue; + } + if(proto->checkkey == nil){ + werrstr("proto %s does not accept keys", proto->name); + ret = -1; + continue; + } + k = emalloc(sizeof(Key)); + k->attr = mkattr(AttrNameval, "proto", proto->name, copyattr(attr)); + k->privattr = copyattr(priv); + k->ref = 1; + k->proto = proto; + if((*proto->checkkey)(k) < 0){ + ret = -1; + keyclose(k); + continue; + } + keyadd(k); + keyclose(k); + } + freeattr(attr); + freeattr(priv); + freeattr(protos); + return ret; + case 1: /* delkey */ + nmatch = 0; + attr = parseattr(p); + for(pa=attr; pa; pa=pa->next){ + if(pa->type != AttrQuery && pa->name[0]=='!'){ + werrstr("only !private? patterns are allowed for private fields"); + freeattr(attr); + return -1; + } + } + for(i=0; iattr, ring.key[i]->privattr)){ + nmatch++; + keyclose(ring.key[i]); + ring.nkey--; + memmove(&ring.key[i], &ring.key[i+1], (ring.nkey-i)*sizeof(ring.key[0])); + }else + i++; + } + freeattr(attr); + if(nmatch == 0){ + werrstr("found no keys to delete"); + return -1; + } + return 0; + case 2: /* debug */ + debug ^= 1; + return 0; + } +} blob - /dev/null blob + 1164832824bc0bfc1f2a4f3effa3821e305ef756 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/dat.h @@ -0,0 +1,226 @@ +enum +{ + MaxRpc = 2048, /* max size of any protocol message */ + + /* keep in sync with rpc.c:/rpcname */ + RpcUnknown = 0, /* Rpc.op */ + RpcAuthinfo, + RpcAttr, + RpcRead, + RpcStart, + RpcWrite, + + /* thread stack size - big buffers for printing */ + STACK = 65536, +}; + +typedef struct Conv Conv; +typedef struct Key Key; +typedef struct Logbuf Logbuf; +typedef struct Proto Proto; +typedef struct Ring Ring; +typedef struct Role Role; +typedef struct Rpc Rpc; + +struct Rpc +{ + int op; + void *data; + int count; +}; + +struct Conv +{ + int ref; /* ref count */ + int hangup; /* flag: please hang up */ + int active; /* flag: there is an active thread */ + int done; /* flag: conversation finished successfully */ + ulong tag; /* identifying tag */ + Conv *next; /* in linked list */ + char *sysuser; /* system name for user speaking to us */ + char *state; /* for debugging */ + char statebuf[128]; /* for formatted states */ + char err[ERRMAX]; /* last error */ + + Attr *attr; /* current attributes */ + Proto *proto; /* protocol */ + + Channel *rpcwait; /* wait here for an rpc */ + Rpc rpc; /* current rpc. op==RpcUnknown means none */ + char rpcbuf[MaxRpc]; /* buffer for rpc */ + char reply[MaxRpc]; /* buffer for response */ + int nreply; /* count of response */ + void (*kickreply)(Conv*); /* call to send response */ + Req *req; /* 9P call to read response */ + + Channel *keywait; /* wait here for key confirmation */ + +}; + +struct Key +{ + int ref; /* ref count */ + ulong tag; /* identifying tag: sequence number */ + Attr *attr; /* public attributes */ + Attr *privattr; /* private attributes, like !password */ + Proto *proto; /* protocol owner of key */ + void *priv; /* protocol-specific storage */ +}; + +struct Logbuf +{ + Req *wait; + Req **waitlast; + int rp; + int wp; + char *msg[128]; +}; + +struct Ring +{ + Key **key; + int nkey; +}; + +struct Proto +{ + char *name; /* name of protocol */ + Role *roles; /* list of roles and service functions */ + char *keyprompt; /* required attributes for key proto=name */ + int (*checkkey)(Key*); /* initialize k->priv or reject key */ + void (*closekey)(Key*); /* free k->priv */ +}; + +struct Role +{ + char *name; /* name of role */ + int (*fn)(Conv*); /* service function */ +}; + +extern char *authaddr; /* plan9.c */ +extern int *confirminuse; /* fs.c */ +extern Conv* conv; /* conv.c */ +extern int debug; /* main.c */ +extern char *factname; /* main.c */ +extern Srv fs; /* fs.c */ +extern int *needkeyinuse; /* fs.c */ +extern char *owner; /* main.c */ +extern Proto *prototab[]; /* main.c */ +extern Ring ring; /* key.c */ +extern char *rpcname[]; /* rpc.c */ + +extern char Easproto[]; /* err.c */ + +/* provided by lib9p */ +#define emalloc emalloc9p +#define erealloc erealloc9p +#define estrdup estrdup9p + +/* hidden in libauth */ +#define attrfmt _attrfmt +#define copyattr _copyattr +#define delattr _delattr +#define findattr _findattr +#define freeattr _freeattr +#define mkattr _mkattr +#define parseattr _parseattr +#define strfindattr _strfindattr + +extern Attr* addattr(Attr*, char*, ...); +/* #pragma varargck argpos addattr 2 */ +extern Attr* addattrs(Attr*, Attr*); +extern Attr* sortattr(Attr*); +extern int attrnamefmt(Fmt*); +/* #pragma varargck type "N" Attr* */ +extern int matchattr(Attr*, Attr*, Attr*); +extern Attr* parseattrfmt(char*, ...); +/* #pragma varargck argpos parseattrfmt 1 */ +extern Attr* parseattrfmtv(char*, va_list); + +extern void confirmflush(Req*); +extern void confirmread(Req*); +extern int confirmwrite(char*); +extern int needkey(Conv*, Attr*); +extern int badkey(Conv*, Key*, char*, Attr*); +extern int confirmkey(Conv*, Key*); + +extern Conv* convalloc(char*); +extern void convclose(Conv*); +extern void convhangup(Conv*); +extern int convneedkey(Conv*, Attr*); +extern int convbadkey(Conv*, Key*, char*, Attr*); +extern int convread(Conv*, void*, int); +extern int convreadm(Conv*, char**); +extern int convprint(Conv*, char*, ...); +/* #pragma varargck argpos convprint 2 */ +extern int convreadfn(Conv*, int(*)(void*, int), char**); +extern void convreset(Conv*); +extern int convwrite(Conv*, void*, int); + +extern int ctlwrite(char*); + +extern char* estrappend(char*, char*, ...); +/* #pragma varargck argpos estrappend 2 */ +extern int hexparse(char*, uchar*, int); + +extern void keyadd(Key*); +extern Key* keylookup(char*, ...); +extern Key* keyiterate(int, char*, ...); +/* #pragma varargck argpos keylookup 1 */ +extern Key* keyfetch(Conv*, char*, ...); +/* #pragma varargck argpos keyfetch 2 */ +extern void keyclose(Key*); +extern void keyevict(Conv*, Key*, char*, ...); +/* #pragma varargck argpos keyevict 3 */ +extern Key* keyreplace(Conv*, Key*, char*, ...); +/* #pragma varargck argpos keyreplace 3 */ + +extern void lbkick(Logbuf*); +extern void lbappend(Logbuf*, char*, ...); +extern void lbvappend(Logbuf*, char*, va_list); +/* #pragma varargck argpos lbappend 2 */ +extern void lbread(Logbuf*, Req*); +extern void lbflush(Logbuf*, Req*); +extern void flog(char*, ...); +/* #pragma varargck argpos flog 1 */ + +extern void logflush(Req*); +extern void logread(Req*); +extern void logwrite(Req*); + +extern void needkeyread(Req*); +extern void needkeyflush(Req*); +extern int needkeywrite(char*); +extern int needkeyqueue(void); + +extern Attr* addcap(Attr*, char*, Ticket*); +extern Key* plan9authkey(Attr*); +extern int _authdial(char*, char*); + +extern int memrandom(void*, int); + +extern Proto* protolookup(char*); + +extern int rpcwrite(Conv*, void*, int); +extern void rpcrespond(Conv*, char*, ...); +/* #pragma varargck argpos rpcrespond 2 */ +extern void rpcrespondn(Conv*, char*, void*, int); +extern void rpcexec(Conv*); + +extern int xioauthdial(char*, char*); +extern void xioclose(int); +extern int xiodial(char*, char*, char*, int*); +extern int xiowrite(int, void*, int); +extern int xioasrdresp(int, void*, int); +extern int xioasgetticket(int, char*, char*); + +/* pkcs1.c */ +typedef DigestState *DigestAlg(uchar*, ulong, uchar*, DigestState*); +int rsasign(RSApriv*, DigestAlg*, uchar*, uint, uchar*, uint); +void mptoberjust(mpint*, uchar*, uint); + + +extern int extrafactotumdir; + +int havesecstore(void); +int secstorefetch(void); blob - /dev/null blob + 73f8d2961b17ed74593dc44c768e8b3ba62b6edd (mode 644) --- /dev/null +++ src/cmd/auth/factotum/dsa.c @@ -0,0 +1,140 @@ +#include "std.h" +#include "dat.h" + +/* + * DSA signing and verification + * + * Sign: + * start p=xxx q=xxx alpha=xxx key=xxx + * write msg + * read signature(msg) + * + * Verify: (not implemented) + * start p=xxx q=xxx alpha=xxx key=xxx + * write msg + * write signature(msg) + * read ok or fail + * + * all numbers are hexadecimal bigints parsable with strtomp. + */ + +static int +xdsasign(Conv *c) +{ + int n; + mpint *m; + uchar digest[SHA1dlen]; + DSAsig *sig; + Key *k; + + k = keylookup("%A", c->attr); + if(k == nil) + return -1; + + c->state = "read data"; + if((n=convread(c, digest, SHA1dlen)) < 0){ + keyclose(k); + return -1; + } + m = betomp(digest, SHA1dlen, nil); + if(m == nil){ + keyclose(k); + return -1; + } + sig = dsasign(k->priv, m); + keyclose(k); + mpfree(m); + if(sig == nil) + return -1; + convprint(c, "%B %B", sig->r, sig->s); + dsasigfree(sig); + return 0; +} + +/* + * convert to canonical form (lower case) + * for use in attribute matches. + */ +static void +strlwr(char *a) +{ + for(; *a; a++){ + if('A' <= *a && *a <= 'Z') + *a += 'a' - 'A'; + } +} + +static DSApriv* +readdsapriv(Key *k) +{ + char *a; + DSApriv *priv; + + priv = dsaprivalloc(); + + if((a=strfindattr(k->attr, "p"))==nil + || (priv->pub.p=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->attr, "q"))==nil + || (priv->pub.q=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "alpha"))==nil + || (priv->pub.alpha=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "key"))==nil + || (priv->pub.key=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!secret"))==nil + || (priv->secret=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + return priv; + +Error: + dsaprivfree(priv); + return nil; +} + +static int +dsacheck(Key *k) +{ + static int first = 1; + + if(first){ + fmtinstall('B', mpfmt); + first = 0; + } + + if((k->priv = readdsapriv(k)) == nil){ + werrstr("malformed key data"); + return -1; + } + return 0; +} + +static void +dsaclose(Key *k) +{ + dsaprivfree(k->priv); + k->priv = nil; +} + +static Role +dsaroles[] = +{ + "sign", xdsasign, + 0 +}; + +Proto dsa = { + "dsa", + dsaroles, + nil, + dsacheck, + dsaclose +}; + blob - /dev/null blob + f9ad785be85abca7d31c939827ad8b9617240f97 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/fs.c @@ -0,0 +1,531 @@ +#include "std.h" +#include "dat.h" + +enum +{ + Qroot, + Qfactotum, + Qrpc, + Qkeylist, + Qprotolist, + Qconfirm, + Qlog, + Qctl, + Qneedkey, + Qconv, +}; + +static int qtop; + +Qid +mkqid(int type, int path) +{ + Qid q; + + q.type = type; + q.path = path; + q.vers = 0; + return q; +} + +static struct +{ + char *name; + int qidpath; + ulong perm; +} dirtab[] = { + /* positions of confirm and needkey known below */ + "confirm", Qconfirm, 0600|DMEXCL, + "needkey", Qneedkey, 0600|DMEXCL, + "ctl", Qctl, 0600, + "rpc", Qrpc, 0666, + "proto", Qprotolist, 0444, + "log", Qlog, 0600|DMEXCL, + "conv", Qconv, 0400, +}; + +static void +fillstat(Dir *dir, char *name, int type, int path, ulong perm) +{ + dir->name = estrdup(name); + dir->uid = estrdup(owner); + dir->gid = estrdup(owner); + dir->mode = perm; + dir->length = 0; + dir->qid = mkqid(type, path); + dir->atime = time(0); + dir->mtime = time(0); + dir->muid = estrdup(""); +} + +static int +rootdirgen(int n, Dir *dir, void *v) +{ + USED(v); + + if(n > 0) + return -1; + + fillstat(dir, factname, QTDIR, Qfactotum, DMDIR|0555); + return 0; +} + +static int +fsdirgen(int n, Dir *dir, void *v) +{ + USED(v); + + if(n >= nelem(dirtab)) + return -1; + fillstat(dir, dirtab[n].name, 0, dirtab[n].qidpath, dirtab[n].perm); + return 0; +} + +static char* +fswalk1(Fid *fid, char *name, Qid *qid) +{ + int i; + + switch((int)fid->qid.path){ + default: + return "fswalk1: cannot happen"; + case Qroot: + if(strcmp(name, factname) == 0){ + *qid = mkqid(QTDIR, Qfactotum); + fid->qid = *qid; + return nil; + } + if(strcmp(name, "..") == 0){ + *qid = fid->qid; + return nil; + } + return "not found"; + case Qfactotum: + for(i=0; iqid = *qid; + return nil; + } + if(strcmp(name, "..") == 0){ + *qid = mkqid(QTDIR, qtop); + fid->qid = *qid; + return nil; + } + return "not found"; + } +} + +static void +fsstat(Req *r) +{ + int i, path; + + path = r->fid->qid.path; + switch(path){ + case Qroot: + fillstat(&r->d, "/", QTDIR, Qroot, 0555|DMDIR); + break; + case Qfactotum: + fillstat(&r->d, "factotum", QTDIR, Qfactotum, 0555|DMDIR); + break; + default: + for(i=0; id, dirtab[i].name, 0, dirtab[i].qidpath, dirtab[i].perm); + goto Break2; + } + respond(r, "file not found"); + break; + } + Break2: + respond(r, nil); +} + +static int +readlist(int off, int (*gen)(int, char*, uint), Req *r) +{ + char *a, *ea; + int n; + + a = r->ofcall.data; + ea = a+r->ifcall.count; + for(;;){ + n = (*gen)(off, a, ea-a); + if(n == 0){ + r->ofcall.count = a - (char*)r->ofcall.data; + return off; + } + a += n; + off++; + } + return -1; /* not reached */ +} + +static int +keylist(int i, char *a, uint nn) +{ + int n; + char buf[512]; + Key *k; + + if(i >= ring.nkey) + return 0; + + k = ring.key[i]; + k->attr = sortattr(k->attr); + n = snprint(buf, sizeof buf, "key %A %N\n", k->attr, k->privattr); + if(n >= sizeof(buf)-5) + strcpy(buf+sizeof(buf)-5, "...\n"); + n = strlen(buf); + if(n > nn) + return 0; + memmove(a, buf, n); + return n; +} + +static int +protolist(int i, char *a, uint n) +{ + if(prototab[i] == nil) + return 0; + if(strlen(prototab[i]->name)+1 > n) + return 0; + n = strlen(prototab[i]->name)+1; + memmove(a, prototab[i]->name, n-1); + a[n-1] = '\n'; + return n; +} + +/* BUG this is O(n^2) to fill in the list */ +static int +convlist(int i, char *a, uint nn) +{ + Conv *c; + char buf[512]; + int n; + + for(c=conv; c && i-- > 0; c=c->next) + ; + + if(c == nil) + return 0; + + if(c->state) + n = snprint(buf, sizeof buf, "conv state=%q %A\n", c->state, c->attr); + else + n = snprint(buf, sizeof buf, "conv state=closed err=%q\n", c->err); + + if(n >= sizeof(buf)-5) + strcpy(buf+sizeof(buf)-5, "...\n"); + n = strlen(buf); + if(n > nn) + return 0; + memmove(a, buf, n); + return n; +} + +static void +fskickreply(Conv *c) +{ + Req *r; + + if(c->hangup){ + if(c->req){ + respond(c->req, "hangup"); + c->req = nil; + } + return; + } + + if(!c->req || !c->nreply) + return; + + r = c->req; + r->ofcall.count = c->nreply; + r->ofcall.data = c->reply; + if(r->ofcall.count > r->ifcall.count) + r->ofcall.count = r->ifcall.count; + respond(r, nil); + c->req = nil; + c->nreply = 0; +} + +/* + * Some of the file system work happens in the fs proc, but + * fsopen, fsread, fswrite, fsdestroyfid, and fsflush happen in + * the main proc so that they can access the various shared + * data structures without worrying about locking. + */ +static int inuse[nelem(dirtab)]; +int *confirminuse = &inuse[0]; +int *needkeyinuse = &inuse[1]; +static void +fsopen(Req *r) +{ + int i, *inusep, perm; + static int need[4] = { 4, 2, 6, 1 }; + Conv *c; + + inusep = nil; + perm = 5; /* directory */ + for(i=0; ifid->qid.path){ + if(dirtab[i].perm & DMEXCL) + inusep = &inuse[i]; + if(strcmp(r->fid->uid, owner) == 0) + perm = dirtab[i].perm>>6; + else + perm = dirtab[i].perm; + break; + } + + if((r->ifcall.mode&~(OMASK|OTRUNC)) + || (need[r->ifcall.mode&3] & ~perm)){ + respond(r, "permission denied"); + return; + } + + if(inusep){ + if(*inusep){ + respond(r, "file in use"); + return; + } + *inusep = 1; + } + + if(r->fid->qid.path == Qrpc){ + if((c = convalloc(r->fid->uid)) == nil){ + char e[ERRMAX]; + + rerrstr(e, sizeof e); + respond(r, e); + return; + } + c->kickreply = fskickreply; + r->fid->aux = c; + } + + respond(r, nil); +} + +static void +fsread(Req *r) +{ + Conv *c; + + switch((int)r->fid->qid.path){ + default: + respond(r, "fsread: cannot happen"); + break; + case Qroot: + dirread9p(r, rootdirgen, nil); + respond(r, nil); + break; + case Qfactotum: + dirread9p(r, fsdirgen, nil); + respond(r, nil); + break; + case Qrpc: + c = r->fid->aux; + if(c->rpc.op == RpcUnknown){ + respond(r, "no rpc pending"); + break; + } + if(c->req){ + respond(r, "read already pending"); + break; + } + c->req = r; + if(c->nreply) + (*c->kickreply)(c); + else + rpcexec(c); + break; + case Qconfirm: + confirmread(r); + break; + case Qlog: + logread(r); + break; + case Qctl: + r->fid->aux = (void*)readlist((int)r->fid->aux, keylist, r); + respond(r, nil); + break; + case Qneedkey: + needkeyread(r); + break; + case Qprotolist: + r->fid->aux = (void*)readlist((int)r->fid->aux, protolist, r); + respond(r, nil); + break; + case Qconv: + r->fid->aux = (void*)readlist((int)r->fid->aux, convlist, r); + respond(r, nil); + break; + } +} + +static void +fswrite(Req *r) +{ + int ret; + char err[ERRMAX], *s; + int (*strfn)(char*); + + switch((int)r->fid->qid.path){ + default: + respond(r, "fswrite: cannot happen"); + break; + case Qrpc: + if(rpcwrite(r->fid->aux, r->ifcall.data, r->ifcall.count) < 0){ + rerrstr(err, sizeof err); + respond(r, err); + }else{ + r->ofcall.count = r->ifcall.count; + respond(r, nil); + } + break; + case Qneedkey: + strfn = needkeywrite; + goto string; + case Qctl: + strfn = ctlwrite; + goto string; + case Qconfirm: + strfn = confirmwrite; + string: + s = emalloc(r->ifcall.count+1); + memmove(s, r->ifcall.data, r->ifcall.count); + s[r->ifcall.count] = '\0'; + ret = (*strfn)(s); + free(s); + if(ret < 0){ + rerrstr(err, sizeof err); + respond(r, err); + }else{ + r->ofcall.count = r->ifcall.count; + respond(r, nil); + } + break; + } +} + +static void +fsflush(Req *r) +{ + confirmflush(r); + logflush(r); +} + +static void +fsdestroyfid(Fid *fid) +{ + if(fid->qid.path == Qrpc && fid->aux){ + convhangup(fid->aux); + convclose(fid->aux); + } +} + +static Channel *creq; +static Channel *cfid, *cfidr; + +static void +fsreqthread(void *v) +{ + Req *r; + + USED(v); + + while((r = recvp(creq)) != nil){ + switch(r->ifcall.type){ + default: + respond(r, "bug in fsreqthread"); + break; + case Topen: + fsopen(r); + break; + case Tread: + fsread(r); + break; + case Twrite: + fswrite(r); + break; + case Tflush: + fsflush(r); + break; + } + } +} + +static void +fsclunkthread(void *v) +{ + Fid *f; + + USED(v); + + while((f = recvp(cfid)) != nil){ + fsdestroyfid(f); + sendp(cfidr, 0); + } +} + +static void +fsproc(void *v) +{ + USED(v); + + threadcreate(fsreqthread, nil, STACK); + threadcreate(fsclunkthread, nil, STACK); + threadexits(nil); +} + +static void +fsattach(Req *r) +{ + r->fid->qid = mkqid(QTDIR, qtop); + r->ofcall.qid = r->fid->qid; + respond(r, nil); +} + +static void +fssend(Req *r) +{ + sendp(creq, r); +} + +static void +fssendclunk(Fid *f) +{ + sendp(cfid, f); + recvp(cfidr); +} + +void +fsstart(Srv *s) +{ + USED(s); + + if(extrafactotumdir) + qtop = Qroot; + else + qtop = Qfactotum; + creq = chancreate(sizeof(Req*), 0); + cfid = chancreate(sizeof(Fid*), 0); + cfidr = chancreate(sizeof(Fid*), 0); + proccreate(fsproc, nil, STACK); +} + +Srv fs = { +.attach= fsattach, +.walk1= fswalk1, +.open= fssend, +.read= fssend, +.write= fssend, +.stat= fsstat, +.flush= fssend, +.destroyfid= fssendclunk, +.start= fsstart, +}; + blob - /dev/null blob + e2299b846c0448c2efbe758f5898e95b23b32e2c (mode 644) --- /dev/null +++ src/cmd/auth/factotum/key.c @@ -0,0 +1,217 @@ +#include "std.h" +#include "dat.h" + +Ring ring; + +Key* +keyiterate(int skip, char *fmt, ...) +{ + int i; + Attr *a; + Key *k; + va_list arg; + + va_start(arg, fmt); + a = parseattrfmtv(fmt, arg); + va_end(arg); + + for(i=0; iattr, k->privattr)){ + if(skip-- > 0) + continue; + k->ref++; + freeattr(a); + return k; + } + } + freeattr(a); + werrstr("no key found"); + return nil; +} + +Key* +keylookup(char *fmt, ...) +{ + int i; + Attr *a; + Key *k; + va_list arg; + + va_start(arg, fmt); + a = parseattrfmtv(fmt, arg); + va_end(arg); + + for(i=0; iattr, k->privattr)){ + k->ref++; + freeattr(a); + return k; + } + } + freeattr(a); + werrstr("no key found"); + return nil; +} + +Key* +keyfetch(Conv *c, char *fmt, ...) +{ + int i, tag; + Attr *a; + Key *k; + va_list arg; + + va_start(arg, fmt); + a = parseattrfmtv(fmt, arg); + va_end(arg); + + tag = 0; + + for(i=0; itag) + tag = k->tag; + if(matchattr(a, k->attr, k->privattr)){ + k->ref++; + if(strfindattr(k->attr, "confirm") && confirmkey(c, k) != 1){ + k->ref--; + continue; + } + freeattr(a); + return k; + } + } + + if(needkey(c, a) < 0) + convneedkey(c, a); + + for(i=0; itag <= tag) + continue; + if(matchattr(a, k->attr, k->privattr)){ + k->ref++; + if(strfindattr(k->attr, "confirm") && confirmkey(c, k) != 1){ + k->ref--; + continue; + } + freeattr(a); + return k; + } + } + freeattr(a); + werrstr("no key found"); + return nil; +} + +static int taggen; + +void +keyadd(Key *k) +{ + int i; + + k->ref++; + k->tag = ++taggen; + for(i=0; iattr, ring.key[i]->attr, nil) + && matchattr(ring.key[i]->attr, k->attr, nil)){ + keyclose(ring.key[i]); + ring.key[i] = k; + return; + } + } + + ring.key = erealloc(ring.key, (ring.nkey+1)*sizeof(ring.key[0])); + ring.key[ring.nkey++] = k; +} + +void +keyclose(Key *k) +{ + if(k == nil) + return; + + if(--k->ref > 0) + return; + + if(k->proto->closekey) + (*k->proto->closekey)(k); + + freeattr(k->attr); + freeattr(k->privattr); + free(k); +} + +Key* +keyreplace(Conv *c, Key *k, char *fmt, ...) +{ + Key *kk; + char *msg; + Attr *a, *b, *bp; + va_list arg; + + va_start(arg, fmt); + msg = vsmprint(fmt, arg); + if(msg == nil) + sysfatal("out of memory"); + va_end(arg); + + /* replace prompted values with prompts */ + a = copyattr(k->attr); + bp = parseattr(k->proto->keyprompt); + for(b=bp; b; b=b->next){ + a = delattr(a, b->name); + a = addattr(a, "%q?", b->name); + } + freeattr(bp); + + if(badkey(c, k, msg, a) < 0) + convbadkey(c, k, msg, a); + kk = keylookup("%A", a); + freeattr(a); + keyclose(k); + if(kk == k){ + keyclose(kk); + werrstr("%s", msg); + return nil; + } + + if(strfindattr(kk->attr, "confirm")){ + if(confirmkey(c, kk) != 1){ + werrstr("key use not confirmed"); + keyclose(kk); + return nil; + } + } + return kk; +} + +void +keyevict(Conv *c, Key *k, char *fmt, ...) +{ + char *msg; + Attr *a, *b, *bp; + va_list arg; + + va_start(arg, fmt); + msg = vsmprint(fmt, arg); + if(msg == nil) + sysfatal("out of memory"); + va_end(arg); + + /* replace prompted values with prompts */ + a = copyattr(k->attr); + bp = parseattr(k->proto->keyprompt); + for(b=bp; b; b=b->next){ + a = delattr(a, b->name); + a = addattr(a, "%q?", b->name); + } + freeattr(bp); + + if(badkey(c, k, msg, nil) < 0) + convbadkey(c, k, msg, nil); + keyclose(k); +} blob - /dev/null blob + 6c2d69ddbf6a5d152737ebbb1ef680f8a42eab2f (mode 644) --- /dev/null +++ src/cmd/auth/factotum/log.c @@ -0,0 +1,121 @@ +#include "std.h" +#include "dat.h" + +void +lbkick(Logbuf *lb) +{ + char *s; + int n; + Req *r; + + while(lb->wait && lb->rp != lb->wp){ + r = lb->wait; + lb->wait = r->aux; + if(lb->wait == nil) + lb->waitlast = &lb->wait; + r->aux = nil; + if(r->ifcall.count < 5){ + respond(r, "factotum: read request count too short"); + continue; + } + s = lb->msg[lb->rp]; + lb->msg[lb->rp] = nil; + if(++lb->rp == nelem(lb->msg)) + lb->rp = 0; + n = r->ifcall.count; + if(n < strlen(s)+1+1){ + memmove(r->ofcall.data, s, n-5); + n -= 5; + r->ofcall.data[n] = '\0'; + /* look for first byte of UTF-8 sequence by skipping continuation bytes */ + while(n>0 && (r->ofcall.data[--n]&0xC0)==0x80) + ; + strcpy(r->ofcall.data+n, "...\n"); + }else{ + strcpy(r->ofcall.data, s); + strcat(r->ofcall.data, "\n"); + } + r->ofcall.count = strlen(r->ofcall.data); + free(s); + respond(r, nil); + } +} + +void +lbread(Logbuf *lb, Req *r) +{ + if(lb->waitlast == nil) + lb->waitlast = &lb->wait; + *(lb->waitlast) = r; + lb->waitlast = (Req**)&r->aux; + r->aux = nil; + lbkick(lb); +} + +void +lbflush(Logbuf *lb, Req *r) +{ + Req **l; + + for(l=&lb->wait; *l; l=(Req**)&(*l)->aux){ + if(*l == r){ + *l = r->aux; + r->aux = nil; + if(*l == nil) + lb->waitlast = l; + closereq(r); + break; + } + } +} + +void +lbappend(Logbuf *lb, char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + lbvappend(lb, fmt, arg); + va_end(arg); +} + +void +lbvappend(Logbuf *lb, char *fmt, va_list arg) +{ + char *s; + + s = smprint(fmt, arg); + if(s == nil) + sysfatal("out of memory"); + if(lb->msg[lb->wp]) + free(lb->msg[lb->wp]); + lb->msg[lb->wp] = s; + if(++lb->wp == nelem(lb->msg)) + lb->wp = 0; + lbkick(lb); +} + +Logbuf logbuf; + +void +logread(Req *r) +{ + lbread(&logbuf, r); +} + +void +logflush(Req *r) +{ + lbflush(&logbuf, r); +} + +void +flog(char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + lbvappend(&logbuf, fmt, arg); + va_end(arg); +} + blob - /dev/null blob + 1a8c4ffc6fabbb0f7178a5fcecc42661f48204bf (mode 644) --- /dev/null +++ src/cmd/auth/factotum/main.c @@ -0,0 +1,185 @@ +#include "std.h" +#include "dat.h" +#include <9pclient.h> + +int extrafactotumdir; +int debug; +int trysecstore = 1; +char *factname = "factotum"; +char *service = "factotum"; +char *owner; +char *authaddr; +void gflag(char*); + +void +usage(void) +{ + fprint(2, "usage: factotum [-Dd] [-a authaddr] [-m mtpt] [-s service]\n"); + fprint(2, " or factotum -g keypattern\n"); + fprint(2, " or factotum -g 'badkeyattr\\nmsg\\nkeypattern'\n"); + threadexitsall("usage"); +} + +void +threadmain(int argc, char *argv[]) +{ + char *mtpt; + char err[ERRMAX]; + +// mtpt = "/mnt"; + mtpt = nil; + owner = getuser(); + quotefmtinstall(); + fmtinstall('A', attrfmt); + fmtinstall('H', encodefmt); + fmtinstall('N', attrnamefmt); + + if(argc == 3 && strcmp(argv[1], "-g") == 0){ + gflag(argv[2]); + threadexitsall(nil); + } + + ARGBEGIN{ + default: + usage(); + case 'D': + chatty9p++; + break; + case 'a': + authaddr = EARGF(usage()); + break; + case 'g': + usage(); + case 'm': + mtpt = EARGF(usage()); + break; + case 's': + service = EARGF(usage()); + break; + case 'n': + trysecstore = 0; + break; + case 'x': + extrafactotumdir = 1; + break; + }ARGEND + + if(argc != 0) + usage(); + + if(trysecstore && havesecstore()){ + while(secstorefetch() < 0){ + rerrstr(err, sizeof err); + if(strcmp(err, "cancel") == 0) + break; + fprint(2, "secstorefetch: %r\n"); + fprint(2, "Enter an empty password to quit.\n"); + } + } + + threadpostmountsrv(&fs, service, mtpt, MBEFORE); + threadexits(nil); +} + +/* + * prompt user for a key. don't care about memory leaks, runs standalone + */ +static Attr* +promptforkey(int fd, char *params) +{ + char *v; + Attr *a, *attr; + char *def; + + attr = _parseattr(params); + fprint(fd, "!adding key:"); + for(a=attr; a; a=a->next) + if(a->type != AttrQuery && a->name[0] != '!') + fprint(fd, " %q=%q", a->name, a->val); + fprint(fd, "\n"); + + for(a=attr; a; a=a->next){ + v = a->name; + if(a->type != AttrQuery || v[0]=='!') + continue; + def = nil; + if(strcmp(v, "user") == 0) + def = getuser(); + a->val = readcons(v, def, 0); + if(a->val == nil) + sysfatal("user terminated key input"); + a->type = AttrNameval; + } + for(a=attr; a; a=a->next){ + v = a->name; + if(a->type != AttrQuery || v[0]!='!') + continue; + def = nil; + if(strcmp(v+1, "user") == 0) + def = getuser(); + a->val = readcons(v+1, def, 1); + if(a->val == nil) + sysfatal("user terminated key input"); + a->type = AttrNameval; + } + fprint(fd, "!\n"); + close(fd); + return attr; +} + +/* + * send a key to the mounted factotum + */ +static int +sendkey(Attr *attr) +{ + int rv; + char buf[8192]; + CFid *fid; + + fid = nsopen("factotum", nil, "ctl", OWRITE); + if(fid == nil) + sysfatal("opening factotum/ctl: %r"); + snprint(buf, sizeof buf, "key %A\n", attr); + rv = fswrite(fid, buf, strlen(buf)); + fsclose(fid); + return rv; +} + +static void +askuser(int fd, char *params) +{ + Attr *attr; + + attr = promptforkey(fd, params); + if(attr == nil) + sysfatal("no key supplied"); + if(sendkey(attr) < 0) + sysfatal("sending key to factotum: %r"); +} + +void +gflag(char *s) +{ + char *f[4]; + int nf; + int fd; + + if((fd = open("/dev/tty", ORDWR)) < 0) + sysfatal("open /dev/tty: %r"); + + nf = getfields(s, f, nelem(f), 0, "\n"); + if(nf == 1){ /* needkey or old badkey */ + fprint(fd, "\n"); + askuser(fd, s); + threadexitsall(nil); + } + if(nf == 3){ /* new badkey */ + fprint(fd, "\n"); + fprint(fd, "!replace: %s\n", f[0]); + fprint(fd, "!because: %s\n", f[1]); + askuser(fd, f[2]); + threadexitsall(nil); + } + usage(); +} blob - /dev/null blob + 7c716021f32a308b36bb452c687649c513c5a323 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/mkfile @@ -0,0 +1,36 @@ +<$PLAN9/src/mkhdr + +TARG=factotum +PROTO=\ + apop.$O\ + chap.$O\ + p9any.$O\ + p9sk1.$O\ + rsa.$O\ + +OFILES=\ + $PROTO\ + attr.$O\ + confirm.$O\ + conv.$O\ + ctl.$O\ + dsa.$O\ + fs.$O\ + key.$O\ + log.$O\ + main.$O\ + plan9.$O\ + pkcs1.$O\ + proto.$O\ + rpc.$O\ + util.$O\ + xio.$O\ + secstore.$O\ + +HFILES=dat.h + +<$PLAN9/src/mkone + +$O.test: test.$O + $LD -o $target $prereq + blob - /dev/null blob + 694d4cbc30ef89eefbf6ba34ede217fcf5d989fb (mode 644) --- /dev/null +++ src/cmd/auth/factotum/p9any.c @@ -0,0 +1,272 @@ +#include "std.h" +#include "dat.h" + +/* + * p9any - protocol negotiator + * + * Protocol: + * S->C: v.2 proto@dom proto@dom proto@dom... NUL + * C->S: proto dom NUL + * [negotiated proto continues] + */ + +extern Proto p9sk1, p9sk2, p9cr; + +static Proto* okproto[] = +{ + &p9sk1, + nil, +}; + +static int +rolecall(Role *r, char *name, Conv *c) +{ + for(; r->name; r++) + if(strcmp(r->name, name) == 0) + return (*r->fn)(c); + werrstr("unknown role"); + return -1; +} + +static int +hasnul(void *v, int n) +{ + char *c; + + c = v; + if(n > 0 && c[n-1] == '\0') + return n; + else + return AuthRpcMax; +} + +static int +p9anyserver(Conv *c) +{ + char *s, *dom; + int i, j, n, m, ret; + char *tok[3]; + Attr *attr; + Key *k; + + ret = -1; + s = estrdup("v.2"); + n = 0; + attr = delattr(copyattr(c->attr), "proto"); + + for(i=0; iproto == okproto[j] + && (dom = strfindattr(k->attr, "dom")) != nil + && matchattr(attr, k->attr, k->privattr)){ + s = estrappend(s, " %s@%s", k->proto->name, dom); + n++; + } + } + + if(n == 0){ + werrstr("no valid keys"); + goto out; + } + + c->state = "write offer"; + if(convwrite(c, s, strlen(s)+1) < 0) + goto out; + free(s); + s = nil; + + c->state = "read choice"; + if(convreadfn(c, hasnul, &s) < 0) + goto out; + + m = tokenize(s, tok, nelem(tok)); + if(m != 2){ + werrstr("bad protocol message"); + goto out; + } + + for(i=0; okproto[i]; i++) + if(strcmp(okproto[i]->name, tok[0]) == 0) + break; + if(!okproto[i]){ + werrstr("bad chosen protocol %q", tok[0]); + goto out; + } + + c->state = "write ok"; + if(convwrite(c, "OK\0", 3) < 0) + goto out; + + c->state = "start choice"; + attr = addattr(attr, "proto=%q dom=%q", tok[0], tok[1]); + free(c->attr); + c->attr = attr; + attr = nil; + c->proto = okproto[i]; + + if(rolecall(c->proto->roles, "server", c) < 0){ + werrstr("%s: %r", tok[0]); + goto out; + } + + ret = 0; + +out: + free(s); + freeattr(attr); + return ret; +} + +static int +p9anyclient(Conv *c) +{ + char *s, **f, *tok[20], ok[3], *q, *user, *dom, *choice; + int i, n, ret, version; + Key *k; + Attr *attr; + Proto *p; + + ret = -1; + s = nil; + k = nil; + + user = strfindattr(c->attr, "user"); + dom = strfindattr(c->attr, "dom"); + + /* + * if the user is the factotum owner, any key will do. + * if not, then if we have a speakfor key, + * we will only vouch for the user's local identity. + * + * this logic is duplicated in p9sk1.c + */ + attr = delattr(copyattr(c->attr), "role"); + attr = delattr(attr, "proto"); + if(strcmp(c->sysuser, owner) == 0) + attr = addattr(attr, "role=client"); + else if(user==nil || strcmp(c->sysuser, user)==0){ + attr = delattr(attr, "user"); + attr = addattr(attr, "role=speakfor"); + }else{ + werrstr("will not authenticate for %q as %q", c->sysuser, user); + goto out; + } + + c->state = "read offer"; + if(convreadfn(c, hasnul, &s) < 0) + goto out; + + c->state = "look for keys"; + n = tokenize(s, tok, nelem(tok)); + f = tok; + version = 1; + if(n > 0 && memcmp(f[0], "v.", 2) == 0){ + version = atoi(f[0]+2); + if(version != 2){ + werrstr("unknown p9any version: %s", f[0]); + goto out; + } + f++; + n--; + } + + /* look for keys that don't need confirmation */ + for(i=0; iattr, "confirm") == nil) + goto found; + *--q = '@'; + } + + /* look for any keys at all */ + for(i=0; istate = "ask for keys"; + for(i=0; ikeyprompt == nil){ + *--q = '@'; + continue; + } + if(k = keyfetch(c, "%A proto=%q dom=%q %s", attr, f[i], q, p->keyprompt)) + goto found; + *--q = '@'; + } + + /* nothing worked */ + werrstr("unable to find common key"); + goto out; + +found: + /* f[i] is the chosen protocol, q the chosen domain */ + attr = addattr(attr, "proto=%q dom=%q", f[i], q); + c->state = "write choice"; + + /* have a key: go for it */ + choice = estrappend(nil, "%q %q", f[i], q); + if(convwrite(c, choice, strlen(choice)+1) < 0){ + free(choice); + goto out; + } + free(choice); + + if(version == 2){ + c->state = "read ok"; + if(convread(c, ok, 3) < 0 || memcmp(ok, "OK\0", 3) != 0) + goto out; + } + + c->state = "start choice"; + c->proto = protolookup(f[i]); + freeattr(c->attr); + c->attr = attr; + attr = nil; + + if(rolecall(c->proto->roles, "client", c) < 0){ + werrstr("%s: %r", c->proto->name); + goto out; + } + + ret = 0; + +out: + keyclose(k); + freeattr(attr); + free(s); + return ret; +} + +static Role +p9anyroles[] = +{ + "client", p9anyclient, + "server", p9anyserver, + 0 +}; + +Proto p9any = { +.name= "p9any", +.roles= p9anyroles, +}; + blob - /dev/null blob + 7f53e44782057f8f4aa67ba923ab8530b8490e38 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/p9cr.c @@ -0,0 +1,545 @@ +/* + * p9cr, vnc - one-sided challenge/response authentication + * + * Protocol: + * + * C -> S: user + * S -> C: challenge + * C -> S: response + * S -> C: ok or bad + * + * Note that this is the protocol between factotum and the local + * program, not between the two factotums. The information + * exchanged here is wrapped in other protocols by the local + * programs. + */ + +#include "std.h" +#include "dat.h" + +static int +p9crcheck(Key *k) +{ + if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){ + werrstr("need user and !password attributes"); + return -1; + } + return 0; +} + +static int +p9crclient(Conv *c) +{ + char *chal, *pw, *res, *user; + int astype, nchal, npw, ntry, ret; + uchar resp[MD5dlen]; + Attr *attr; + DigestState *ds; + Key *k; + + chal = nil; + k = nil; + res = nil; + ret = -1; + attr = c->attr; + + if(c->proto == &p9cr){ + astype = AuthChal; + challen = NETCHLEN; + }else if(c->proto == &vnc){ + astype = AuthVnc; + challen = MAXCHAL; + }else{ + werrstr("bad proto"); + goto out; + } + + c->state = "find key"; + k = keyfetch(c, "%A %s", attr, c->proto->keyprompt); + if(k == nil) + goto out; + + for(ntry=1;; ntry++){ + if(c->attr != attr) + freeattr(c->attr); + c->attr = addattrs(copyattr(attr), k->attr); + if((pw = strfindattr(k->privattr, "!password")) == nil){ + werrstr("key has no !password (cannot happen)"); + goto out; + } + npw = strlen(pw); + + if((user = strfindattr(k->attr, "user")) == nil){ + werrstr("key has no user (cannot happen)"); + goto out; + } + + if(convprint(c, "%s", user) < 0) + goto out; + + if(convreadm(c, &chal) < 0) + goto out; + + if((nresp = (*response)(chal, resp)) < 0) + goto out; + + if(convwrite(c, resp, nresp) < 0) + goto out; + + if(convreadm(c, &res) < 0) + goto out; + + if(strcmp(res, "ok") == 0) + break; + + if((k = keyreplace(c, k, "%s", res)) == nil){ + c->state = "auth failed"; + werrstr("%s", res); + goto out; + } + } + + werrstr("succeeded"); + ret = 0; + +out: + keyclose(k); + free(chal); + if(c->attr != attr) + freeattr(attr); + return ret; +} + +static int +p9crserver(Conv *c) +{ + char chal[APOPCHALLEN], *user, *resp; + ServerState s; + int astype, ret; + Attr *a; + + ret = -1; + user = nil; + resp = nil; + memset(&s, 0, sizeof s); + s.asfd = -1; + + if(c->proto == &apop) + astype = AuthApop; + else if(c->proto == &cram) + astype = AuthCram; + else{ + werrstr("bad proto"); + goto out; + } + + c->state = "find key"; + if((s.k = plan9authkey(c->attr)) == nil) + goto out; + + a = copyattr(s.k->attr); + a = delattr(a, "proto"); + c->attr = addattrs(c->attr, a); + freeattr(a); + + c->state = "authdial"; + s.hostid = strfindattr(s.k->attr, "user"); + s.dom = strfindattr(s.k->attr, "dom"); + if((s.asfd = xioauthdial(nil, s.dom)) < 0){ + werrstr("authdial %s: %r", s.dom); + goto out; + } + + c->state = "authchal"; + if(p9crchal(&s, astype, chal) < 0) + goto out; + + c->state = "write challenge"; + if(convprint(c, "%s", chal) < 0) + goto out; + + for(;;){ + c->state = "read user"; + if(convreadm(c, &user) < 0) + goto out; + + c->state = "read response"; + if(convreadm(c, &resp) < 0) + goto out; + + c->state = "authwrite"; + switch(apopresp(&s, user, resp)){ + case -1: + goto out; + case 0: + c->state = "write status"; + if(convprint(c, "bad authentication failed") < 0) + goto out; + break; + case 1: + c->state = "write status"; + if(convprint(c, "ok") < 0) + goto out; + goto ok; + } + free(user); + free(resp); + user = nil; + resp = nil; + } + +ok: + ret = 0; + c->attr = addcap(c->attr, c->sysuser, &s.t); + +out: + keyclose(s.k); + free(user); + free(resp); +// xioclose(s.asfd); + return ret; +} + +enum +{ + MAXCHAL = 64, +}; + +typedef struct State State; +struct State +{ + Key *key; + int astype; + int asfd; + Ticket t; + Ticketreq tr; + char chal[MAXCHAL]; + int challen; + char resp[MAXCHAL]; + int resplen; +}; + +enum +{ + CNeedChal, + CHaveResp, + + SHaveChal, + SNeedResp, + + Maxphase, +}; + +static char *phasenames[Maxphase] = +{ +[CNeedChal] "CNeedChal", +[CHaveResp] "CHaveResp", + +[SHaveChal] "SHaveChal", +[SNeedResp] "SNeedResp", +}; + +static void +p9crclose(Fsstate *fss) +{ + State *s; + + s = fss->ps; + if(s->asfd >= 0){ + close(s->asfd); + s->asfd = -1; + } + free(s); +} + +static int getchal(State*, Fsstate*); + +static int +p9crinit(Proto *p, Fsstate *fss) +{ + int iscli, ret; + char *user; + State *s; + Attr *attr; + + if((iscli = isclient(_str_findattr(fss->attr, "role"))) < 0) + return failure(fss, nil); + + s = emalloc(sizeof(*s)); + s->asfd = -1; + if(p == &p9cr){ + s->astype = AuthChal; + s->challen = NETCHLEN; + }else if(p == &vnc){ + s->astype = AuthVNC; + s->challen = Maxchal; + }else + abort(); + + if(iscli){ + fss->phase = CNeedChal; + if(p == &p9cr) + attr = setattr(_copyattr(fss->attr), "proto=p9sk1"); + else + attr = nil; + ret = findkey(&s->key, fss, Kuser, 0, attr ? attr : fss->attr, + "role=client %s", p->keyprompt); + _freeattr(attr); + if(ret != RpcOk){ + free(s); + return ret; + } + fss->ps = s; + }else{ + if((ret = findp9authkey(&s->key, fss)) != RpcOk){ + free(s); + return ret; + } + if((user = _str_findattr(fss->attr, "user")) == nil){ + free(s); + return failure(fss, "no user name specified in start msg"); + } + if(strlen(user) >= sizeof s->tr.uid){ + free(s); + return failure(fss, "user name too long"); + } + fss->ps = s; + strcpy(s->tr.uid, user); + ret = getchal(s, fss); + if(ret != RpcOk){ + p9crclose(fss); /* frees s */ + fss->ps = nil; + } + } + fss->phasename = phasenames; + fss->maxphase = Maxphase; + return ret; +} + +static int +p9crread(Fsstate *fss, void *va, uint *n) +{ + int m; + State *s; + + s = fss->ps; + switch(fss->phase){ + default: + return phaseerror(fss, "read"); + + case CHaveResp: + if(s->resplen < *n) + *n = s->resplen; + memmove(va, s->resp, *n); + fss->phase = Established; + return RpcOk; + + case SHaveChal: + if(s->astype == AuthChal) + m = strlen(s->chal); /* ascii string */ + else + m = s->challen; /* fixed length binary */ + if(m > *n) + return toosmall(fss, m); + *n = m; + memmove(va, s->chal, m); + fss->phase = SNeedResp; + return RpcOk; + } +} + +static int +p9response(Fsstate *fss, State *s) +{ + char key[DESKEYLEN]; + uchar buf[8]; + ulong chal; + char *pw; + + pw = _str_findattr(s->key->privattr, "!password"); + if(pw == nil) + return failure(fss, "vncresponse cannot happen"); + passtokey(key, pw); + memset(buf, 0, 8); + sprint((char*)buf, "%d", atoi(s->chal)); + if(encrypt(key, buf, 8) < 0) + return failure(fss, "can't encrypt response"); + chal = (buf[0]<<24)+(buf[1]<<16)+(buf[2]<<8)+buf[3]; + s->resplen = snprint(s->resp, sizeof s->resp, "%.8lux", chal); + return RpcOk; +} + +static uchar tab[256]; + +/* VNC reverses the bits of each byte before using as a des key */ +static void +mktab(void) +{ + int i, j, k; + static int once; + + if(once) + return; + once = 1; + + for(i=0; i<256; i++) { + j=i; + tab[i] = 0; + for(k=0; k<8; k++) { + tab[i] = (tab[i]<<1) | (j&1); + j >>= 1; + } + } +} + +static int +vncaddkey(Key *k) +{ + uchar *p; + char *s; + + k->priv = emalloc(8+1); + if(s = _str_findattr(k->privattr, "!password")){ + mktab(); + memset(k->priv, 0, 8+1); + strncpy((char*)k->priv, s, 8); + for(p=k->priv; *p; p++) + *p = tab[*p]; + }else{ + werrstr("no key data"); + return -1; + } + return replacekey(k); +} + +static void +vncclosekey(Key *k) +{ + free(k->priv); +} + +static int +vncresponse(Fsstate*, State *s) +{ + DESstate des; + + memmove(s->resp, s->chal, sizeof s->chal); + setupDESstate(&des, s->key->priv, nil); + desECBencrypt((uchar*)s->resp, s->challen, &des); + s->resplen = s->challen; + return RpcOk; +} + +static int +p9crwrite(Fsstate *fss, void *va, uint n) +{ + char tbuf[TICKETLEN+AUTHENTLEN]; + State *s; + char *data = va; + Authenticator a; + char resp[Maxchal]; + int ret; + + s = fss->ps; + switch(fss->phase){ + default: + return phaseerror(fss, "write"); + + case CNeedChal: + if(n >= sizeof(s->chal)) + return failure(fss, Ebadarg); + memset(s->chal, 0, sizeof s->chal); + memmove(s->chal, data, n); + s->challen = n; + + if(s->astype == AuthChal) + ret = p9response(fss, s); + else + ret = vncresponse(fss, s); + if(ret != RpcOk) + return ret; + fss->phase = CHaveResp; + return RpcOk; + + case SNeedResp: + /* send response to auth server and get ticket */ + if(n > sizeof(resp)) + return failure(fss, Ebadarg); + memset(resp, 0, sizeof resp); + memmove(resp, data, n); + if(write(s->asfd, resp, s->challen) != s->challen) + return failure(fss, Easproto); + + /* get ticket plus authenticator from auth server */ + if(_asrdresp(s->asfd, tbuf, TICKETLEN+AUTHENTLEN) < 0) + return failure(fss, nil); + + /* check ticket */ + convM2T(tbuf, &s->t, s->key->priv); + if(s->t.num != AuthTs + || memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0) + return failure(fss, Easproto); + convM2A(tbuf+TICKETLEN, &a, s->t.key); + if(a.num != AuthAc + || memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0 + || a.id != 0) + return failure(fss, Easproto); + + fss->haveai = 1; + fss->ai.cuid = s->t.cuid; + fss->ai.suid = s->t.suid; + fss->ai.nsecret = 0; + fss->ai.secret = nil; + fss->phase = Established; + return RpcOk; + } +} + +static int +getchal(State *s, Fsstate *fss) +{ + char trbuf[TICKREQLEN]; + int n; + + safecpy(s->tr.hostid, _str_findattr(s->key->attr, "user"), sizeof(s->tr.hostid)); + safecpy(s->tr.authdom, _str_findattr(s->key->attr, "dom"), sizeof(s->tr.authdom)); + s->tr.type = s->astype; + convTR2M(&s->tr, trbuf); + + /* get challenge from auth server */ + s->asfd = _authdial(nil, _str_findattr(s->key->attr, "dom")); + if(s->asfd < 0) + return failure(fss, Easproto); + if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) + return failure(fss, Easproto); + n = _asrdresp(s->asfd, s->chal, s->challen); + if(n <= 0){ + if(n == 0) + werrstr("_asrdresp short read"); + return failure(fss, nil); + } + s->challen = n; + fss->phase = SHaveChal; + return RpcOk; +} + +Proto p9cr = +{ +.name= "p9cr", +.init= p9crinit, +.write= p9crwrite, +.read= p9crread, +.close= p9crclose, +.keyprompt= "user? !password?", +}; + +Proto vnc = +{ +.name= "vnc", +.init= p9crinit, +.write= p9crwrite, +.read= p9crread, +.close= p9crclose, +.keyprompt= "!password?", +.addkey= vncaddkey, +}; blob - /dev/null blob + 92b055d05caa1156b8cc454daf3fefd2071dd5fc (mode 644) --- /dev/null +++ src/cmd/auth/factotum/p9sk1.c @@ -0,0 +1,353 @@ +/* + * p9sk1, p9sk2 - Plan 9 secret (private) key authentication. + * p9sk2 is an incomplete flawed variant of p9sk1. + * + * Client protocol: + * write challenge[challen] (p9sk1 only) + * read tickreq[tickreqlen] + * write ticket[ticketlen] + * read authenticator[authentlen] + * + * Server protocol: + * read challenge[challen] (p9sk1 only) + * write tickreq[tickreqlen] + * read ticket[ticketlen] + * write authenticator[authentlen] + */ + +#include "std.h" +#include "dat.h" + +extern Proto p9sk1, p9sk2; +static int gettickets(Ticketreq*, char*, Key*); + +#define max(a, b) ((a) > (b) ? (a) : (b)) +enum +{ + MAXAUTH = max(TICKREQLEN, TICKETLEN+max(TICKETLEN, AUTHENTLEN)) +}; + +static int +p9skclient(Conv *c) +{ + char *user; + char cchal[CHALLEN]; + uchar secret[8]; + char buf[MAXAUTH]; + int speakfor, ret; + Attr *a; + Authenticator au; + Key *k; + Ticket t; + Ticketreq tr; + + ret = -1; + a = nil; + k = nil; + + /* p9sk1: send client challenge */ + if(c->proto == &p9sk1){ + c->state = "write challenge"; + memrandom(cchal, CHALLEN); + if(convwrite(c, cchal, CHALLEN) < 0) + goto out; + } + + /* read ticket request */ + c->state = "read tickreq"; + if(convread(c, buf, TICKREQLEN) < 0) + goto out; + convM2TR(buf, &tr); + + /* p9sk2: use server challenge as client challenge */ + if(c->proto == &p9sk2) + memmove(cchal, tr.chal, CHALLEN); + + /* + * find a key. + * + * if the user is the factotum owner, any key will do. + * if not, then if we have a speakfor key, + * we will only vouch for the user's local identity. + * + * this logic is duplicated in p9any.c + */ + user = strfindattr(c->attr, "user"); + a = delattr(copyattr(c->attr), "role"); + a = addattr(a, "proto=p9sk1"); + + if(strcmp(c->sysuser, owner) == 0){ + speakfor = 0; + a = addattr(a, "proto=p9sk1 user? dom=%q", tr.authdom); + }else if(user==nil || strcmp(c->sysuser, user)==0){ + speakfor = 1; + a = delattr(a, "user"); + a = addattr(a, "proto=p9sk1 user? dom=%q role=speakfor", tr.authdom); + }else{ + werrstr("will not authenticate for %q as %q", c->sysuser, user); + goto out; + } + + for(;;){ + c->state = "find key"; + k = keyfetch(c, "%A", a); + if(k == nil) + goto out; + + /* relay ticket request to auth server, get tickets */ + strcpy(tr.hostid, strfindattr(k->attr, "user")); + if(speakfor) + strcpy(tr.uid, c->sysuser); + else + strcpy(tr.uid, tr.hostid); + + c->state = "get tickets"; + if(gettickets(&tr, buf, k) < 0) + goto out; + + convM2T(buf, &t, k->priv); + if(t.num == AuthTc) + break; + + /* we don't agree with the auth server about the key; try again */ + c->state = "replace key"; + if((k = keyreplace(c, k, "key mismatch with auth server")) == nil){ + werrstr("key mismatch with auth server"); + goto out; + } + } + + /* send second ticket and authenticator to server */ + c->state = "write ticket+auth"; + memmove(buf, buf+TICKETLEN, TICKETLEN); + au.num = AuthAc; + memmove(au.chal, tr.chal, CHALLEN); + au.id = 0; + convA2M(&au, buf+TICKETLEN, t.key); + if(convwrite(c, buf, TICKETLEN+AUTHENTLEN) < 0) + goto out; + + /* read authenticator from server */ + c->state = "read auth"; + if(convread(c, buf, AUTHENTLEN) < 0) + goto out; + convM2A(buf, &au, t.key); + if(au.num != AuthAs || memcmp(au.chal, cchal, CHALLEN) != 0 || au.id != 0){ + werrstr("server lies through his teeth"); + goto out; + } + + /* success */ + c->attr = addcap(c->attr, c->sysuser, &t); + des56to64((uchar*)t.key, secret); + c->attr = addattr(c->attr, "secret=%.8H", secret); + ret = 0; + +out: + freeattr(a); + keyclose(k); + return ret; +} + +static int +p9skserver(Conv *c) +{ + char cchal[CHALLEN], buf[MAXAUTH]; + uchar secret[8]; + int ret; + Attr *a; + Authenticator au; + Key *k; + Ticketreq tr; + Ticket t; + + ret = -1; + + a = addattr(copyattr(c->attr), "user? dom?"); + a = addattr(a, "user? dom? proto=p9sk1"); + if((k = keyfetch(c, "%A", a)) == nil) + goto out; + + /* p9sk1: read client challenge */ + if(c->proto == &p9sk1){ + if(convread(c, cchal, CHALLEN) < 0) + goto out; + } + + /* send ticket request */ + memset(&tr, 0, sizeof tr); + tr.type = AuthTreq; + strcpy(tr.authid, strfindattr(k->attr, "user")); + strcpy(tr.authdom, strfindattr(k->attr, "dom")); + memrandom(tr.chal, sizeof tr.chal); + convTR2M(&tr, buf); + if(convwrite(c, buf, TICKREQLEN) < 0) + goto out; + + /* p9sk2: use server challenge as client challenge */ + if(c->proto == &p9sk2) + memmove(cchal, tr.chal, sizeof tr.chal); + + /* read ticket+authenticator */ + if(convread(c, buf, TICKETLEN+AUTHENTLEN) < 0) + goto out; + + convM2T(buf, &t, k->priv); + if(t.num != AuthTs || memcmp(t.chal, tr.chal, CHALLEN) != 0){ + /* BUG badkey */ + werrstr("key mismatch with auth server"); + goto out; + } + + convM2A(buf+TICKETLEN, &au, t.key); + if(au.num != AuthAc || memcmp(au.chal, tr.chal, CHALLEN) != 0 || au.id != 0){ + werrstr("client lies through his teeth"); + goto out; + } + + /* send authenticator */ + au.num = AuthAs; + memmove(au.chal, cchal, CHALLEN); + convA2M(&au, buf, t.key); + if(convwrite(c, buf, AUTHENTLEN) < 0) + goto out; + + /* success */ + c->attr = addcap(c->attr, c->sysuser, &t); + des56to64((uchar*)t.key, secret); + c->attr = addattr(c->attr, "secret=%.8H", secret); + ret = 0; + +out: + freeattr(a); + keyclose(k); + return ret; +} + +int +_asgetticket(int fd, char *trbuf, char *tbuf) +{ + if(write(fd, trbuf, TICKREQLEN) < 0){ + close(fd); + return -1; + } + return _asrdresp(fd, tbuf, 2*TICKETLEN); +} +static int +getastickets(Ticketreq *tr, char *buf) +{ + int asfd; + int ret; + + if((asfd = xioauthdial(nil, tr->authdom)) < 0) + return -1; + convTR2M(tr, buf); + ret = xioasgetticket(asfd, buf, buf); + xioclose(asfd); + return ret; +} + +static int +mktickets(Ticketreq *tr, char *buf, Key *k) +{ + Ticket t; + + if(strcmp(tr->authid, tr->hostid) != 0) + return -1; + + memset(&t, 0, sizeof t); + memmove(t.chal, tr->chal, CHALLEN); + strcpy(t.cuid, tr->uid); + strcpy(t.suid, tr->uid); + memrandom(t.key, DESKEYLEN); + t.num = AuthTc; + convT2M(&t, buf, k->priv); + t.num = AuthTs; + convT2M(&t, buf+TICKETLEN, k->priv); + return 0; +} + +static int +gettickets(Ticketreq *tr, char *buf, Key *k) +{ + if(getastickets(tr, buf) == 0) + return 0; + if(mktickets(tr, buf, k) == 0) + return 0; + werrstr("gettickets: %r"); + return -1; +} + +static int +p9sk1check(Key *k) +{ + char *user, *dom, *pass; + Ticketreq tr; + + user = strfindattr(k->attr, "user"); + dom = strfindattr(k->attr, "dom"); + if(user==nil || dom==nil){ + werrstr("need user and dom attributes"); + return -1; + } + if(strlen(user) >= sizeof tr.authid){ + werrstr("user name too long"); + return -1; + } + if(strlen(dom) >= sizeof tr.authdom){ + werrstr("auth dom name too long"); + return -1; + } + + k->priv = emalloc(DESKEYLEN); + if(pass = strfindattr(k->privattr, "!password")) + passtokey(k->priv, pass); + else if(pass = strfindattr(k->privattr, "!hex")){ + if(hexparse(pass, k->priv, 7) < 0){ + werrstr("malformed !hex key data"); + return -1; + } + }else{ + werrstr("need !password or !hex attribute"); + return -1; + } + + return 0; +} + +static void +p9sk1close(Key *k) +{ + free(k->priv); + k->priv = nil; +} + +static Role +p9sk1roles[] = +{ + "client", p9skclient, + "server", p9skserver, + 0 +}; + +static Role +p9sk2roles[] = +{ + "client", p9skclient, + "server", p9skserver, + 0 +}; + +Proto p9sk1 = { +.name= "p9sk1", +.roles= p9sk1roles, +.checkkey= p9sk1check, +.closekey= p9sk1close, +.keyprompt= "user? dom? !password?", +}; + +Proto p9sk2 = { +.name= "p9sk2", +.roles= p9sk2roles, +}; + blob - /dev/null blob + b3d4cb6ad2c87b9b7755d314e63f41bebde4c72a (mode 644) --- /dev/null +++ src/cmd/auth/factotum/pass.c @@ -0,0 +1,100 @@ +/* + * This is just a repository for a password. + * We don't want to encourage this, there's + * no server side. + */ + +#include "dat.h" + +typedef struct State State; +struct State +{ + Key *key; +}; + +enum +{ + HavePass, + Maxphase, +}; + +static char *phasenames[Maxphase] = +{ +[HavePass] "HavePass", +}; + +static int +passinit(Proto *p, Fsstate *fss) +{ + int ask; + Key *k; + State *s; + + k = findkey(fss, Kuser, &ask, 0, fss->attr, "%s", p->keyprompt); + if(k == nil){ + if(ask) + return RpcNeedkey; + return failure(fss, nil); + } + setattrs(fss->attr, k->attr); + s = emalloc(sizeof(*s)); + s->key = k; + fss->ps = s; + return RpcOk; +} + +static void +passclose(Fsstate *fss) +{ + State *s; + + s = fss->ps; + if(s->key) + closekey(s->key); + free(s); +} + +static int +passread(Fsstate *fss, void *va, uint *n) +{ + int m; + char buf[500]; + char *pass, *user; + State *s; + + s = fss->ps; + switch(fss->phase){ + default: + return phaseerror(fss, "read"); + + case HavePass: + user = strfindattr(s->key->attr, "user"); + pass = strfindattr(s->key->privattr, "!password"); + if(user==nil || pass==nil) + return failure(fss, "passread cannot happen"); + snprint(buf, sizeof buf, "%q %q", user, pass); + m = strlen(buf); + if(m > *n) + return toosmall(fss, m); + *n = m; + memmove(va, buf, m); + return RpcOk; + } +} + +static int +passwrite(Fsstate *fss, void*, uint) +{ + return phaseerror(fss, "write"); +} + +Proto pass = +{ +.name= "pass", +.init= passinit, +.write= passwrite, +.read= passread, +.close= passclose, +.addkey= replacekey, +.keyprompt= "user? !password?", +}; blob - /dev/null blob + fb35ce83dda8c27625de6ac8dd95da2fefe9c129 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/pkcs1.c @@ -0,0 +1,154 @@ +#include "std.h" +#include "dat.h" + +/* + * PKCS #1 v2.0 signatures (aka RSASSA-PKCS1-V1_5) + * + * You don't want to read the spec. + * Here is what you need to know. + * + * RSA sign (aka RSASP1) is just an RSA encryption. + * RSA verify (aka RSAVP1) is just an RSA decryption. + * + * We sign hashes of messages instead of the messages + * themselves. + * + * The hashes are encoded in ASN.1 DER to identify + * the signature type, and then prefixed with 0x01 PAD 0x00 + * where PAD is as many 0xFF bytes as desired. + */ + +static int mkasn1(uchar *asn1, DigestAlg *alg, uchar *d, uint dlen); + +int +rsasign(RSApriv *key, DigestAlg *hash, uchar *digest, uint dlen, + uchar *sig, uint siglen) +{ + uchar asn1[64], *buf; + int n, len, pad; + mpint *m, *s; + + /* + * Create ASN.1 + */ + n = mkasn1(asn1, hash, digest, dlen); + + /* + * Create number to sign. + */ + len = (mpsignif(key->pub.n)+7)/8; + if(len < n+2){ + werrstr("rsa key too short"); + return -1; + } + pad = len - (n+2); + if(siglen < len){ + werrstr("signature buffer too short"); + return -1; + } + buf = malloc(len); + if(buf == nil) + return -1; + buf[0] = 0x01; + memset(buf+1, 0xFF, pad); + buf[1+pad] = 0x00; + memmove(buf+1+pad+1, asn1, n); + m = betomp(buf, len, nil); + free(buf); + if(m == nil) + return -1; + + /* + * Sign it. + */ + s = rsadecrypt(key, m, nil); + mpfree(m); + if(s == nil) + return -1; + mptoberjust(s, sig, len); + mpfree(s); + return len; +} + +/* + * Mptobe but shift right to fill buffer. + */ +void +mptoberjust(mpint *b, uchar *buf, uint len) +{ + int n; + + n = mptobe(b, buf, len, nil); + assert(n >= 0); + if(n < len){ + len -= n; + memmove(buf+len, buf, n); + memset(buf, 0, len); + } +} + +/* + * Simple ASN.1 encodings. + * Lengths < 128 are encoded as 1-bytes constants, + * making our life easy. + */ + +/* + * Hash OIDs + * + * SHA1 = 1.3.14.3.2.26 + * MDx = 1.2.840.113549.2.x + */ +#define O0(a,b) ((a)*40+(b)) +#define O2(x) \ + (((x)>>7)&0x7F)|0x80, \ + ((x)&0x7F) +#define O3(x) \ + (((x)>>14)&0x7F)|0x80, \ + (((x)>>7)&0x7F)|0x80, \ + ((x)&0x7F) +uchar oidsha1[] = { O0(1, 3), 14, 3, 2, 26 }; +uchar oidmd2[] = { O0(1, 2), O2(840), O3(113549), 2, 2 }; +uchar oidmd5[] = { O0(1, 2), O2(840), O3(113549), 2, 5 }; + +/* + * DigestInfo ::= SEQUENCE { + * digestAlgorithm AlgorithmIdentifier, + * digest OCTET STRING + * } + */ +static int +mkasn1(uchar *asn1, DigestAlg *alg, uchar *d, uint dlen) +{ + uchar *obj, *p; + uint olen; + + if(alg == sha1){ + obj = oidsha1; + olen = sizeof(oidsha1); + }else if(alg == md5){ + obj = oidmd5; + olen = sizeof(oidmd5); + }else{ + sysfatal("bad alg in mkasn1"); + return -1; + } + + p = asn1; + *p++ = 0x30; /* sequence */ + p++; + + *p++ = 0x06; /* object id */ + *p++ = olen; + memmove(p, obj, olen); + p += olen; + + *p++ = 0x04; /* octet string */ + *p++ = dlen; + memmove(p, d, dlen); + p += dlen; + + asn1[1] = p - (asn1+2); + return p-asn1; +} + blob - /dev/null blob + 0b6bb601731289951ac250f8f4c7ff466c78f065 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/plan9.c @@ -0,0 +1,45 @@ +#include "std.h" +#include "dat.h" +#include + +int +memrandom(void *p, int n) +{ + uchar *cp; + + for(cp = (uchar*)p; n > 0; n--) + *cp++ = fastrand(); + return 0; +} + +Attr* +addcap(Attr *a, char *from, Ticket *t) +{ + return addattr(a, "cuid=%q suid=%q cap=''", t->cuid, t->suid); +} + +int +_authdial(char *net, char *authdom) +{ + return authdial(net, authdom); +} + +Key* +plan9authkey(Attr *a) +{ + char *dom; + Key *k; + + /* + * The only important part of a is dom. + * We don't care, for example, about user name. + */ + dom = strfindattr(a, "dom"); + if(dom) + k = keylookup("proto=p9sk1 role=server user? dom=%q", dom); + else + k = keylookup("proto=p9sk1 role=server user? dom?"); + if(k == nil) + werrstr("could not find plan 9 auth key dom %q", dom); + return k; +} blob - /dev/null blob + a455bd0a12b6b925ba6e2e58f3ec4c006d3e0403 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/proto.c @@ -0,0 +1,34 @@ +#include "std.h" +#include "dat.h" + +extern Proto apop; /* apop.c */ +extern Proto chap; /* chap.c */ +extern Proto cram; /* apop.c */ +extern Proto dsa; /* dsa.c */ +extern Proto mschap; /* chap.c */ +extern Proto p9any; /* p9any.c */ +extern Proto p9sk1; /* p9sk1.c */ +extern Proto p9sk2; /* p9sk2.c */ +extern Proto rsa; /* rsa.c */ + +Proto *prototab[] = { + &apop, + &cram, + &dsa, + &p9any, + &p9sk1, + &p9sk2, + &rsa, + nil, +}; + +Proto* +protolookup(char *name) +{ + int i; + + for(i=0; prototab[i]; i++) + if(strcmp(prototab[i]->name, name) == 0) + return prototab[i]; + return nil; +} blob - /dev/null blob + e9c163aa4eff3753961142f1b20fd319388158fc (mode 644) --- /dev/null +++ src/cmd/auth/factotum/rpc.c @@ -0,0 +1,315 @@ +#include "std.h" +#include "dat.h" + +/* + * Factotum RPC + * + * Must be paired write/read cycles on /mnt/factotum/rpc. + * The format of a request is verb, single space, data. + * Data format is verb-dependent; in particular, it can be binary. + * The format of a response is the same. The write only sets up + * the RPC. The read tries to execute it. If the /mnt/factotum/key + * file is open, we ask for new keys using that instead of returning + * an error in the RPC. This means the read blocks. + * Textual arguments are parsed with tokenize, so rc-style quoting + * rules apply. + * + * Only authentication protocol messages go here. Configuration + * is still via ctl (below). + * + * Request RPCs are: + * start attrs - initializes protocol for authentication, can fail. + * returns "ok read" or "ok write" on success. + * read - execute protocol read + * write - execute protocol write + * authinfo - if the protocol is finished, return the AI if any + * attr - return protocol information + * Return values are: + * error message - an error happened. + * ok [data] - success, possible data is request dependent. + * needkey attrs - request aborted, get me this key and try again + * badkey attrs - request aborted, this key might be bad + * done [haveai] - authentication is done [haveai: you can get an ai with authinfo] + */ + +char *rpcname[] = +{ + "unknown", + "authinfo", + "attr", + "read", + "start", + "write", +}; + +static int +classify(char *s) +{ + int i; + + for(i=1; i= MaxRpc){ + werrstr("rpc too large"); + return -1; + } + + /* cancel any current rpc */ + c->rpc.op = RpcUnknown; + c->nreply = 0; + + /* parse new rpc */ + memmove(c->rpcbuf, data, count); + c->rpcbuf[count] = 0; + if(p = (uchar*)strchr((char*)c->rpcbuf, ' ')){ + *p++ = '\0'; + c->rpc.data = p; + c->rpc.count = count - (p - (uchar*)c->rpcbuf); + }else{ + c->rpc.data = ""; + c->rpc.count = 0; + } + op = classify(c->rpcbuf); + if(op == RpcUnknown){ + werrstr("bad rpc verb: %s", c->rpcbuf); + return -1; + } + + c->rpc.op = op; + return 0; +} + +void +convthread(void *v) +{ + Conv *c; + Attr *a; + char *role, *proto; + Proto *p; + Role *r; + + c = v; + a = parseattr(c->rpc.data); + if(a == nil){ + werrstr("empty attr"); + goto out; + } + c->attr = a; + proto = strfindattr(a, "proto"); + role = strfindattr(a, "role"); + + if(proto == nil){ + werrstr("no proto in attrs"); + goto out; + } + if(role == nil){ + werrstr("no role in attrs"); + goto out; + } + + p = protolookup(proto); + if(p == nil){ + werrstr("unknown proto %s", proto); + goto out; + } + + c->proto = p; + for(r=p->roles; r->name; r++){ + if(strcmp(r->name, role) != 0) + continue; + rpcrespond(c, "ok"); + c->active = 1; + if((*r->fn)(c) == 0){ + c->done = 1; + werrstr("protocol finished"); + }else + werrstr("%s %s %s: %r", p->name, r->name, c->state); + goto out; + } + werrstr("unknown role"); + +out: + c->active = 0; + c->state = 0; + rerrstr(c->err, sizeof c->err); + rpcrespond(c, "error %r"); + convclose(c); +} + +static uchar* convAI2M(uchar *p, int n, char *cuid, char *suid, char *cap, char *hex); + +void +rpcexec(Conv *c) +{ + uchar *p; + + switch(c->rpc.op){ + case RpcRead: + if(c->rpc.count > 0){ + rpcrespond(c, "error read takes no parameters"); + break; + } + /* fall through */ + default: + if(!c->active){ + if(c->done) + rpcrespond(c, "done"); + else + rpcrespond(c, "error %s", c->err); + break; + } + nbsendp(c->rpcwait, 0); + break; + case RpcUnknown: + break; + case RpcAuthinfo: + /* deprecated */ + if(c->active) + rpcrespond(c, "error conversation still active"); + else if(!c->done) + rpcrespond(c, "error conversation not successful"); + else{ + /* make up an auth info using the attr */ + p = convAI2M((uchar*)c->reply+3, sizeof c->reply-3, + strfindattr(c->attr, "cuid"), + strfindattr(c->attr, "suid"), + strfindattr(c->attr, "cap"), + strfindattr(c->attr, "secret")); + if(p == nil) + rpcrespond(c, "error %r"); + else + rpcrespondn(c, "ok", c->reply+3, p-(uchar*)(c->reply+3)); + } + break; + case RpcAttr: + rpcrespond(c, "ok %A", c->attr); + break; + case RpcStart: + convreset(c); + c->ref++; + threadcreate(convthread, c, STACK); + break; + } +} + +void +rpcrespond(Conv *c, char *fmt, ...) +{ + va_list arg; + + if(c->hangup) + return; + + if(fmt == nil) + fmt = ""; + + va_start(arg, fmt); + c->nreply = vsnprint(c->reply, sizeof c->reply, fmt, arg); + va_end(arg); + (*c->kickreply)(c); + c->rpc.op = RpcUnknown; +} + +void +rpcrespondn(Conv *c, char *verb, void *data, int count) +{ + char *p; + + if(c->hangup) + return; + + if(strlen(verb)+1+count > sizeof c->reply){ + print("RPC response too large; caller %#lux", getcallerpc(&c)); + return; + } + + strcpy(c->reply, verb); + p = c->reply + strlen(c->reply); + *p++ = ' '; + memmove(p, data, count); + c->nreply = count + (p - c->reply); + (*c->kickreply)(c); + c->rpc.op = RpcUnknown; +} + +/* deprecated */ +static uchar* +pstring(uchar *p, uchar *e, char *s) +{ + uint n; + + if(p == nil) + return nil; + if(s == nil) + s = ""; + n = strlen(s); + if(p+n+BIT16SZ >= e) + return nil; + PBIT16(p, n); + p += BIT16SZ; + memmove(p, s, n); + p += n; + return p; +} + +static uchar* +pcarray(uchar *p, uchar *e, uchar *s, uint n) +{ + if(p == nil) + return nil; + if(s == nil){ + if(n > 0) + sysfatal("pcarray"); + s = (uchar*)""; + } + if(p+n+BIT16SZ >= e) + return nil; + PBIT16(p, n); + p += BIT16SZ; + memmove(p, s, n); + p += n; + return p; +} + +static uchar* +convAI2M(uchar *p, int n, char *cuid, char *suid, char *cap, char *hex) +{ + uchar *e = p+n; + uchar *secret; + int nsecret; + + if(cuid == nil) + cuid = ""; + if(suid == nil) + suid = ""; + if(cap == nil) + cap = ""; + if(hex == nil) + hex = ""; + nsecret = strlen(hex)/2; + secret = emalloc(nsecret); + if(hexparse(hex, secret, nsecret) < 0){ + werrstr("hexparse %s failed", hex); /* can't happen */ + free(secret); + return nil; + } + p = pstring(p, e, cuid); + p = pstring(p, e, suid); + p = pstring(p, e, cap); + p = pcarray(p, e, secret, nsecret); + free(secret); + if(p == nil) + werrstr("authinfo too big"); + return p; +} + blob - /dev/null blob + 327dbc5bee2316436db0dac972eb95a4b8415cf4 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/rsa.c @@ -0,0 +1,191 @@ +#include "std.h" +#include "dat.h" + +/* + * RSA authentication. + * + * Client: + * start n=xxx ek=xxx + * write msg + * read decrypt(msg) + * + * Sign (PKCS #1 using hash=sha1 or hash=md5) + * start n=xxx ek=xxx + * write hash(msg) + * read signature(hash(msg)) + * + * all numbers are hexadecimal biginits parsable with strtomp. + * must be lower case for attribute matching in start. + */ + +static int +rsaclient(Conv *c) +{ + char *chal; + mpint *m; + Key *k; + + k = keylookup("%A", c->attr); + if(k == nil) + return -1; + c->state = "read challenge"; + if(convreadm(c, &chal) < 0){ + keyclose(k); + return -1; + } + if(strlen(chal) < 32){ + badchal: + free(chal); + convprint(c, "bad challenge"); + keyclose(k); + return -1; + } + m = strtomp(chal, nil, 16, nil); + if(m == nil) + goto badchal; + free(chal); + m = rsadecrypt(k->priv, m, m); + convprint(c, "%B", m); + mpfree(m); + keyclose(k); + return 0; +} + +static int +xrsasign(Conv *c) +{ + char *hash; + int dlen, n; + DigestAlg *hashfn; + Key *k; + uchar sig[1024], digest[64]; + + k = keylookup("%A", c->attr); + if(k == nil) + return -1; + hash = strfindattr(k->attr, "hash"); + if(hash == nil) + hash = "sha1"; + if(strcmp(hash, "sha1") == 0){ + hashfn = sha1; + dlen = SHA1dlen; + }else if(strcmp(hash, "md5") == 0){ + hashfn = md5; + dlen = MD5dlen; + }else{ + werrstr("unknown hash function %s", hash); + return -1; + } + c->state = "read data"; + if((n=convread(c, digest, dlen)) < 0){ + keyclose(k); + return -1; + } + memset(sig, 0xAA, sizeof sig); + n = rsasign(k->priv, hashfn, digest, dlen, sig, sizeof sig); + keyclose(k); + if(n < 0) + return -1; + convwrite(c, sig, n); + return 0; +} + +/* + * convert to canonical form (lower case) + * for use in attribute matches. + */ +static void +strlwr(char *a) +{ + for(; *a; a++){ + if('A' <= *a && *a <= 'Z') + *a += 'a' - 'A'; + } +} + +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; + strlwr(a); + if((a=strfindattr(k->attr, "n"))==nil + || (priv->pub.n=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!p"))==nil + || (priv->p=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!q"))==nil + || (priv->q=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!kp"))==nil + || (priv->kp=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!kq"))==nil + || (priv->kq=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!c2"))==nil + || (priv->c2=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!dk"))==nil + || (priv->dk=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + return priv; + +Error: + rsaprivfree(priv); + return nil; +} + +static int +rsacheck(Key *k) +{ + static int first = 1; + + if(first){ + fmtinstall('B', mpfmt); + first = 0; + } + + if((k->priv = readrsapriv(k)) == nil){ + werrstr("malformed key data"); + return -1; + } + return 0; +} + +static void +rsaclose(Key *k) +{ + rsaprivfree(k->priv); + k->priv = nil; +} + +static Role +rsaroles[] = +{ + "client", rsaclient, + "sign", xrsasign, + 0 +}; + +Proto rsa = { + "rsa", + rsaroles, + nil, + rsacheck, + rsaclose +}; blob - /dev/null blob + d82d7862a48e8b2692d1e0572a10e3e30f067594 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/secstore.c @@ -0,0 +1,644 @@ +/* + * Various files from /sys/src/cmd/auth/secstore, just enough + * to download a file at boot time. + */ + +#include "std.h" +#include "dat.h" +#include + +enum{ CHK = 16}; +enum{ MAXFILESIZE = 10*1024*1024 }; + +enum{// PW status bits + Enabled = (1<<0), + STA = (1<<1), // extra SecurID step +}; + +static char testmess[] = "__secstore\tPAK\nC=%s\nm=0\n"; +char *secstore; + +int +secdial(void) +{ + char *p; + + p = secstore; + if(p == nil) /* else use the authserver */ + p = getenv("secstore"); + if(p == nil) + p = getenv("auth"); + if(p == nil) + p = "secstore"; + + return dial(netmkaddr(p, "net", "secstore"), 0, 0, 0); +} + + +int +havesecstore(void) +{ + int m, n, fd; + uchar buf[500]; + + n = snprint((char*)buf, sizeof buf, testmess, owner); + hnputs(buf, 0x8000+n-2); + + fd = secdial(); + if(fd < 0) + return 0; + if(write(fd, buf, n) != n || readn(fd, buf, 2) != 2){ + close(fd); + return 0; + } + n = ((buf[0]&0x7f)<<8) + buf[1]; + if(n+1 > sizeof buf){ + werrstr("implausibly large count %d", n); + close(fd); + return 0; + } + m = readn(fd, buf, n); + close(fd); + if(m != n){ + if(m >= 0) + werrstr("short read from secstore"); + return 0; + } + buf[n] = 0; + if(strcmp((char*)buf, "!account expired") == 0){ + werrstr("account expired"); + return 0; + } + return strcmp((char*)buf, "!account exists") == 0; +} + +// delimited, authenticated, encrypted connection +enum{ Maxmsg=4096 }; // messages > Maxmsg bytes are truncated +typedef struct SConn SConn; + +extern SConn* newSConn(int); // arg is open file descriptor +struct SConn{ + void *chan; + int secretlen; + int (*secret)(SConn*, uchar*, int);// + int (*read)(SConn*, uchar*, int); // <0 if error; errmess in buffer + int (*write)(SConn*, uchar*, int); + void (*free)(SConn*); // also closes file descriptor +}; +// secret(s,b,dir) sets secret for digest, encrypt, using the secretlen +// bytes in b to form keys for the two directions; +// set dir=0 in client, dir=1 in server + +// error convention: write !message in-band +#define readstr secstore_readstr +static void writerr(SConn*, char*); +static int readstr(SConn*, char*); // call with buf of size Maxmsg+1 + // returns -1 upon error, with error message in buf + +typedef struct ConnState { + uchar secret[SHA1dlen]; + ulong seqno; + RC4state rc4; +} ConnState; + +typedef struct SS{ + int fd; // file descriptor for read/write of encrypted data + int alg; // if nonzero, "alg sha rc4_128" + ConnState in, out; +} SS; + +static int +SC_secret(SConn *conn, uchar *sigma, int direction) +{ + SS *ss = (SS*)(conn->chan); + int nsigma = conn->secretlen; + + if(direction != 0){ + hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->out.secret, nil); + hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->in.secret, nil); + }else{ + hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->out.secret, nil); + hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->in.secret, nil); + } + setupRC4state(&ss->in.rc4, ss->in.secret, 16); // restrict to 128 bits + setupRC4state(&ss->out.rc4, ss->out.secret, 16); + ss->alg = 1; + return 0; +} + +static void +hash(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen]) +{ + DigestState sha; + uchar seq[4]; + + seq[0] = seqno>>24; + seq[1] = seqno>>16; + seq[2] = seqno>>8; + seq[3] = seqno; + memset(&sha, 0, sizeof sha); + sha1(secret, SHA1dlen, nil, &sha); + sha1(data, len, nil, &sha); + sha1(seq, 4, d, &sha); +} + +static int +verify(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen]) +{ + DigestState sha; + uchar seq[4]; + uchar digest[SHA1dlen]; + + seq[0] = seqno>>24; + seq[1] = seqno>>16; + seq[2] = seqno>>8; + seq[3] = seqno; + memset(&sha, 0, sizeof sha); + sha1(secret, SHA1dlen, nil, &sha); + sha1(data, len, nil, &sha); + sha1(seq, 4, digest, &sha); + return memcmp(d, digest, SHA1dlen); +} + +static int +SC_read(SConn *conn, uchar *buf, int n) +{ + SS *ss = (SS*)(conn->chan); + uchar count[2], digest[SHA1dlen]; + int len, nr; + + if(read(ss->fd, count, 2) != 2 || count[0]&0x80 == 0){ + werrstr("!SC_read invalid count"); + return -1; + } + len = (count[0]&0x7f)<<8 | count[1]; // SSL-style count; no pad + if(ss->alg){ + len -= SHA1dlen; + if(len <= 0 || readn(ss->fd, digest, SHA1dlen) != SHA1dlen){ + werrstr("!SC_read missing sha1"); + return -1; + } + if(len > n || readn(ss->fd, buf, len) != len){ + werrstr("!SC_read missing data"); + return -1; + } + rc4(&ss->in.rc4, digest, SHA1dlen); + rc4(&ss->in.rc4, buf, len); + if(verify(ss->in.secret, buf, len, ss->in.seqno, digest) != 0){ + werrstr("!SC_read integrity check failed"); + return -1; + } + }else{ + if(len <= 0 || len > n){ + werrstr("!SC_read implausible record length"); + return -1; + } + if( (nr = readn(ss->fd, buf, len)) != len){ + werrstr("!SC_read expected %d bytes, but got %d", len, nr); + return -1; + } + } + ss->in.seqno++; + return len; +} + +static int +SC_write(SConn *conn, uchar *buf, int n) +{ + SS *ss = (SS*)(conn->chan); + uchar count[2], digest[SHA1dlen], enc[Maxmsg+1]; + int len; + + if(n <= 0 || n > Maxmsg+1){ + werrstr("!SC_write invalid n %d", n); + return -1; + } + len = n; + if(ss->alg) + len += SHA1dlen; + count[0] = 0x80 | len>>8; + count[1] = len; + if(write(ss->fd, count, 2) != 2){ + werrstr("!SC_write invalid count"); + return -1; + } + if(ss->alg){ + hash(ss->out.secret, buf, n, ss->out.seqno, digest); + rc4(&ss->out.rc4, digest, SHA1dlen); + memcpy(enc, buf, n); + rc4(&ss->out.rc4, enc, n); + if(write(ss->fd, digest, SHA1dlen) != SHA1dlen || + write(ss->fd, enc, n) != n){ + werrstr("!SC_write error on send"); + return -1; + } + }else{ + if(write(ss->fd, buf, n) != n){ + werrstr("!SC_write error on send"); + return -1; + } + } + ss->out.seqno++; + return n; +} + +static void +SC_free(SConn *conn) +{ + SS *ss = (SS*)(conn->chan); + + close(ss->fd); + free(ss); + free(conn); +} + +SConn* +newSConn(int fd) +{ + SS *ss; + SConn *conn; + + if(fd < 0) + return nil; + ss = (SS*)emalloc(sizeof(*ss)); + conn = (SConn*)emalloc(sizeof(*conn)); + ss->fd = fd; + ss->alg = 0; + conn->chan = (void*)ss; + conn->secretlen = SHA1dlen; + conn->free = SC_free; + conn->secret = SC_secret; + conn->read = SC_read; + conn->write = SC_write; + return conn; +} + +static void +writerr(SConn *conn, char *s) +{ + char buf[Maxmsg]; + + snprint(buf, Maxmsg, "!%s", s); + conn->write(conn, (uchar*)buf, strlen(buf)); +} + +static int +readstr(SConn *conn, char *s) +{ + int n; + + n = conn->read(conn, (uchar*)s, Maxmsg); + if(n >= 0){ + s[n] = 0; + if(s[0] == '!'){ + memmove(s, s+1, n); + n = -1; + } + }else{ + strcpy(s, "read error"); + } + return n; +} + +static int +getfile(SConn *conn, uchar *key, int nkey) +{ + char *buf; + int nbuf, n, nr, len; + char s[Maxmsg+1], *gf, *p, *q; + uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw; + AESstate aes; + DigestState *sha; + + gf = "factotum"; + memset(&aes, 0, sizeof aes); + + snprint(s, Maxmsg, "GET %s\n", gf); + conn->write(conn, (uchar*)s, strlen(s)); + + /* get file size */ + s[0] = '\0'; + if(readstr(conn, s) < 0){ + werrstr("secstore: %r"); + return -1; + } + if((len = atoi(s)) < 0){ + werrstr("secstore: remote file %s does not exist", gf); + return -1; + }else if(len > MAXFILESIZE){//assert + werrstr("secstore: implausible file size %d for %s", len, gf); + return -1; + } + + ibr = ibw = ib; + buf = nil; + nbuf = 0; + for(nr=0; nr < len;){ + if((n = conn->read(conn, ibw, Maxmsg)) <= 0){ + werrstr("secstore: empty file chunk n=%d nr=%d len=%d: %r", n, nr, len); + return -1; + } + nr += n; + ibw += n; + if(!aes.setup){ /* first time, read 16 byte IV */ + if(n < 16){ + werrstr("secstore: no IV in file"); + return -1; + } + sha = sha1((uchar*)"aescbc file", 11, nil, nil); + sha1(key, nkey, skey, sha); + setupAESstate(&aes, skey, AESbsize, ibr); + memset(skey, 0, sizeof skey); + ibr += AESbsize; + n -= AESbsize; + } + aesCBCdecrypt(ibw-n, n, &aes); + n = ibw-ibr-CHK; + if(n > 0){ + buf = erealloc(buf, nbuf+n+1); + memmove(buf+nbuf, ibr, n); + nbuf += n; + ibr += n; + } + memmove(ib, ibr, ibw-ibr); + ibw = ib + (ibw-ibr); + ibr = ib; + } + n = ibw-ibr; + if((n != CHK) || (memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0)){ + werrstr("secstore: decrypted file failed to authenticate!"); + free(buf); + return -1; + } + if(nbuf == 0){ + werrstr("secstore got empty file"); + return -1; + } + buf[nbuf] = '\0'; + p = buf; + n = 0; + while(p){ + if(q = strchr(p, '\n')) + *q++ = '\0'; + n++; + if(ctlwrite(p) < 0) + fprint(2, "secstore(%s) line %d: %r\n", gf, n); + p = q; + } + free(buf); + return 0; +} + +static char VERSION[] = "secstore"; + +typedef struct PAKparams{ + mpint *q, *p, *r, *g; +} PAKparams; + +static PAKparams *pak; + +// This group was generated by the seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E. +static void +initPAKparams(void) +{ + if(pak) + return; + pak = (PAKparams*)emalloc(sizeof(*pak)); + pak->q = strtomp("E0F0EF284E10796C5A2A511E94748BA03C795C13", nil, 16, nil); + pak->p = strtomp("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBBD" + "B12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86" + "3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9" + "3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", nil, 16, nil); + pak->r = strtomp("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241CEF" + "2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E887" + "D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D21" + "C4656848614D888A4", nil, 16, nil); + pak->g = strtomp("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D2327173444" + "ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD41" + "0E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734E3E" + "2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", nil, 16, nil); +} + +// H = (sha(ver,C,sha(passphrase)))^r mod p, +// a hash function expensive to attack by brute force. +static void +longhash(char *ver, char *C, uchar *passwd, mpint *H) +{ + uchar *Cp; + int i, n, nver, nC; + uchar buf[140], key[1]; + + nver = strlen(ver); + nC = strlen(C); + n = nver + nC + SHA1dlen; + Cp = (uchar*)emalloc(n); + memmove(Cp, ver, nver); + memmove(Cp+nver, C, nC); + memmove(Cp+nver+nC, passwd, SHA1dlen); + for(i = 0; i < 7; i++){ + key[0] = 'A'+i; + hmac_sha1(Cp, n, key, sizeof key, buf+i*SHA1dlen, nil); + } + memset(Cp, 0, n); + free(Cp); + betomp(buf, sizeof buf, H); + mpmod(H, pak->p, H); + mpexp(H, pak->r, pak->p, H); +} + +// Hi = H^-1 mod p +static char * +PAK_Hi(char *C, char *passphrase, mpint *H, mpint *Hi) +{ + uchar passhash[SHA1dlen]; + + sha1((uchar *)passphrase, strlen(passphrase), passhash, nil); + initPAKparams(); + longhash(VERSION, C, passhash, H); + mpinvert(H, pak->p, Hi); + return mptoa(Hi, 64, nil, 0); +} + +// another, faster, hash function for each party to +// confirm that the other has the right secrets. +static void +shorthash(char *mess, char *C, char *S, char *m, char *mu, char *sigma, char *Hi, uchar *digest) +{ + SHA1state *state; + + state = sha1((uchar*)mess, strlen(mess), 0, 0); + state = sha1((uchar*)C, strlen(C), 0, state); + state = sha1((uchar*)S, strlen(S), 0, state); + state = sha1((uchar*)m, strlen(m), 0, state); + state = sha1((uchar*)mu, strlen(mu), 0, state); + state = sha1((uchar*)sigma, strlen(sigma), 0, state); + state = sha1((uchar*)Hi, strlen(Hi), 0, state); + state = sha1((uchar*)mess, strlen(mess), 0, state); + state = sha1((uchar*)C, strlen(C), 0, state); + state = sha1((uchar*)S, strlen(S), 0, state); + state = sha1((uchar*)m, strlen(m), 0, state); + state = sha1((uchar*)mu, strlen(mu), 0, state); + state = sha1((uchar*)sigma, strlen(sigma), 0, state); + sha1((uchar*)Hi, strlen(Hi), digest, state); +} + +// On input, conn provides an open channel to the server; +// C is the name this client calls itself; +// pass is the user's passphrase +// On output, session secret has been set in conn +// (unless return code is negative, which means failure). +// If pS is not nil, it is set to the (alloc'd) name the server calls itself. +static int +PAKclient(SConn *conn, char *C, char *pass, char **pS) +{ + char *mess, *mess2, *eol, *S, *hexmu, *ks, *hexm, *hexsigma = nil, *hexHi; + char kc[2*SHA1dlen+1]; + uchar digest[SHA1dlen]; + int rc = -1, n; + mpint *x, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0); + mpint *H = mpnew(0), *Hi = mpnew(0); + + hexHi = PAK_Hi(C, pass, H, Hi); + + // random 1<=x<=q-1; send C, m=g**x H + x = mprand(164, genrandom, nil); + mpmod(x, pak->q, x); + if(mpcmp(x, mpzero) == 0) + mpassign(mpone, x); + mpexp(pak->g, x, pak->p, m); + mpmul(m, H, m); + mpmod(m, pak->p, m); + hexm = mptoa(m, 64, nil, 0); + mess = (char*)emalloc(2*Maxmsg+2); + mess2 = mess+Maxmsg+1; + snprint(mess, Maxmsg, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm); + conn->write(conn, (uchar*)mess, strlen(mess)); + + // recv g**y, S, check hash1(g**xy) + if(readstr(conn, mess) < 0){ + fprint(2, "error: %s\n", mess); + writerr(conn, "couldn't read g**y"); + goto done; + } + eol = strchr(mess, '\n'); + if(strncmp("mu=", mess, 3) != 0 || !eol || strncmp("\nk=", eol, 3) != 0){ + writerr(conn, "verifier syntax error"); + goto done; + } + hexmu = mess+3; + *eol = 0; + ks = eol+3; + eol = strchr(ks, '\n'); + if(!eol || strncmp("\nS=", eol, 3) != 0){ + writerr(conn, "verifier syntax error for secstore 1.0"); + goto done; + } + *eol = 0; + S = eol+3; + eol = strchr(S, '\n'); + if(!eol){ + writerr(conn, "verifier syntax error for secstore 1.0"); + goto done; + } + *eol = 0; + if(pS) + *pS = estrdup(S); + strtomp(hexmu, nil, 64, mu); + mpexp(mu, x, pak->p, sigma); + hexsigma = mptoa(sigma, 64, nil, 0); + shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest); + enc64(kc, sizeof kc, digest, SHA1dlen); + if(strcmp(ks, kc) != 0){ + writerr(conn, "verifier didn't match"); + goto done; + } + + // send hash2(g**xy) + shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest); + enc64(kc, sizeof kc, digest, SHA1dlen); + snprint(mess2, Maxmsg, "k'=%s\n", kc); + conn->write(conn, (uchar*)mess2, strlen(mess2)); + + // set session key + shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest); + memset(hexsigma, 0, strlen(hexsigma)); + n = conn->secret(conn, digest, 0); + memset(digest, 0, SHA1dlen); + if(n < 0){//assert + writerr(conn, "can't set secret"); + goto done; + } + + rc = 0; +done: + mpfree(x); + mpfree(sigma); + mpfree(mu); + mpfree(m); + mpfree(Hi); + mpfree(H); + free(hexsigma); + free(hexHi); + free(hexm); + free(mess); + return rc; +} + +int +secstorefetch(void) +{ + int rv = -1, fd; + char s[Maxmsg+1]; + SConn *conn; + char *pass, *sta; + + sta = nil; + conn = nil; + pass = readcons("secstore password", nil, 1); + if(pass==nil || strlen(pass)==0){ + werrstr("cancel"); + goto Out; + } + if((fd = secdial()) < 0) + goto Out; + if((conn = newSConn(fd)) == nil) + goto Out; + if(PAKclient(conn, owner, pass, nil) < 0){ + werrstr("password mistyped?"); + goto Out; + } + if(readstr(conn, s) < 0) + goto Out; + if(strcmp(s, "STA") == 0){ + sta = readcons("STA PIN+SecureID", nil, 1); + if(sta==nil || strlen(sta)==0){ + werrstr("cancel"); + goto Out; + } + if(strlen(sta) >= sizeof s - 3){ + werrstr("STA response too long"); + goto Out; + } + strcpy(s+3, sta); + conn->write(conn, (uchar*)s, strlen(s)); + readstr(conn, s); + } + if(strcmp(s, "OK") !=0){ + werrstr("%s", s); + goto Out; + } + if(getfile(conn, (uchar*)pass, strlen(pass)) < 0) + goto Out; + conn->write(conn, (uchar*)"BYE", 3); + rv = 0; + +Out: + if(conn) + conn->free(conn); + if(pass) + free(pass); + if(sta) + free(sta); + return rv; +} + blob - /dev/null blob + 814664e0fb482cf11e5da201fb5f5c63d6b5b17a (mode 644) --- /dev/null +++ src/cmd/auth/factotum/std.h @@ -0,0 +1,10 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include <9p.h> + blob - /dev/null blob + b41048980556fe7a81615ffbf1af9b703d5757b0 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/test.c @@ -0,0 +1,121 @@ +#include +#include +#include + +typedef struct Test Test; + +struct Test +{ + char *name; + int (*server)(Test*, AuthRpc*, int); + int (*client)(Test*, int); +}; + +int +ai2status(AuthInfo *ai) +{ + if(ai == nil) + return -1; + auth_freeAI(ai); + return 0; +} + +int +proxyserver(Test *t, AuthRpc *rpc, int fd) +{ + char buf[1024]; + + sprint(buf, "proto=%q role=server", t->name); + return ai2status(fauth_proxy(fd, rpc, nil, buf)); +} + +int +proxyclient(Test *t, int fd) +{ + return ai2status(auth_proxy(fd, auth_getkey, "proto=%q role=client", t->name)); +} + +Test test[] = +{ + "apop", proxyserver, proxyclient, + "cram", proxyserver, proxyclient, + "p9sk1", proxyserver, proxyclient, + "p9sk2", proxyserver, proxyclient, + "p9any", proxyserver, proxyclient, +}; + +void +usage(void) +{ + fprint(2, "usage: test [name]...\n"); + exits("usage"); +} + +void +runtest(AuthRpc *srpc, Test *t) +{ + int p[2], bad; + Waitmsg *w; + + if(pipe(p) < 0) + sysfatal("pipe: %r"); + + print("%s...", t->name); + + switch(fork()){ + case -1: + sysfatal("fork: %r"); + + case 0: + close(p[0]); + if((*t->server)(t, srpc, p[1]) < 0){ + print("\n\tserver: %r"); + _exits("oops"); + } + close(p[1]); + _exits(nil); + default: + close(p[1]); + if((*t->client)(t, p[0]) < 0){ + print("\n\tclient: %r"); + bad = 1; + } + close(p[0]); + break; + } + w = wait(); + if(w->msg[0]) + bad = 1; + print("\n"); +} + +void +main(int argc, char **argv) +{ + int i, j; + int afd; + AuthRpc *srpc; + + ARGBEGIN{ + default: + usage(); + }ARGEND + + quotefmtinstall(); + afd = open("/n/kremvax/factotum/rpc", ORDWR); + if(afd < 0) + sysfatal("open /n/kremvax/factotum/rpc: %r"); + srpc = auth_allocrpc(afd); + if(srpc == nil) + sysfatal("auth_allocrpc: %r"); + + if(argc == 0) + for(i=0; i ndat) + return -1; + if(hex[strspn(hex, "0123456789abcdefABCDEF")] != '\0') + return -1; + for(i=0; i +#include +#include + +void +f(void*) +{ +} + +void +main(void) +{ + f(auth_challenge); + f(auth_response); +} blob - /dev/null blob + 2e6b141b0cae8629e25e8d5bb0bb35b907400ca3 (mode 644) --- /dev/null +++ src/cmd/auth/factotum/xio.c @@ -0,0 +1,165 @@ +#include "std.h" +#include "dat.h" + +static Ioproc *cache[5]; +static int ncache; + +static Ioproc* +xioproc(void) +{ + Ioproc *c; + int i; + + for(i=0; i +#include +#include +#include +#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, "read error"); + } + return n; +} + blob - /dev/null blob + 9a428d833b1ac5aa6b7e8be04252bf562fc5a9fc (mode 644) --- /dev/null +++ src/cmd/auth/secstore/SConn.h @@ -0,0 +1,26 @@ +// 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 +extern void writerr(SConn*, char*); +extern int readstr(SConn*, char*); // call with buf of size Maxmsg+1 + // returns -1 upon error, with error message in buf + +extern void *emalloc(ulong); /* dies on failure; clears memory */ +extern void *erealloc(void *, ulong); +extern char *estrdup(char *); + blob - /dev/null blob + 56aeb00b531f69c7715634f77242d43cb3ba6740 (mode 644) --- /dev/null +++ src/cmd/auth/secstore/aescbc.c @@ -0,0 +1,156 @@ +/* encrypt file by writing + v2hdr, + 16byte initialization vector, + AES-CBC(key, random | file), + HMAC_SHA1(md5(key), AES-CBC(random | file)) +*/ +#include +#include +#include +#include +#include + +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) +{ + int i = Bwrite(&bout, buf, n); + + if(i == n) + return; + fprint(2, "write error\n"); + exits("write error"); +} + +void +saferead(uchar *buf, int n) +{ + int i = Bread(&bin, buf, n); + + if(i == n) + return; + fprint(2, "read error\n"); + exits("read error"); +} + +int +main(int argc, char **argv) +{ + int encrypt = 0; /* 0=decrypt, 1=encrypt */ + int n, nkey, pass_stdin = 0; + char *pass; + uchar key[AESmaxkey], key2[SHA1dlen]; + uchar buf[BUF+SHA1dlen]; /* assumption: CHK <= SHA1dlen */ + AESstate aes; + DigestState *dstate; + + ARGBEGIN{ + case 'e': + encrypt = 1; + break; + case 'i': + pass_stdin = 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{ + pass = readcons("aescbc key", nil, 1); + n = strlen(pass); + if(n >= BUF) + exits("key too long"); + strcpy((char*)buf, pass); + memset(pass, 0, n); + free(pass); + } + if(n <= 0){ + fprint(2,"no key\n"); + exits("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); + while(1){ + n = Bread(&bin, buf, BUF); + if(n < 0){ + fprint(2,"read error\n"); + exits("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){ + fprint(2,"decrypted file failed to authenticate\n"); + exits("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){ + fprint(2,"decrypted file failed to authenticate\n"); + exits("decrypted file failed to authenticate"); + } + } + } + exits(""); + return 1; /* gcc */ +} blob - /dev/null blob + b4479413a8a00ba426432bf72f0ae9b5000eb8f1 (mode 644) --- /dev/null +++ src/cmd/auth/secstore/dirls.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#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 (*)(const void *, const void *))compare); + for(nmwid=lenwid=i=0; i 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 +#include +#include +#include +#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, "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){ + 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,"mess[1]=%s\n", 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; +} + blob - /dev/null blob + aacadd9b184dd688aaebfe8296426a75d7893001 (mode 644) --- /dev/null +++ src/cmd/auth/secstore/password.c @@ -0,0 +1,136 @@ +/* password.c */ +#include +#include +#include +#include +#include +#include "SConn.h" +#include "secstore.h" + +static Biobuf* +openPW(char *id, int mode) +{ + Biobuf *b; + int nfn = strlen(SECSTORE_DIR)+strlen(id)+20; + char *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) +{ + Dir *d; + int nfn = strlen(SECSTORE_DIR)+strlen(id)+20; + char *fn = emalloc(nfn); + ulong mt; + + snprint(fn, nfn, "%s/who/%s", SECSTORE_DIR, id); + d = dirstat(fn); + free(fn); + mt = d->mtime; + free(d); + return mt; +} + +PW * +getPW(char *id, int dead_or_alive) +{ + uint now = time(0); + Biobuf *bin; + PW *pw; + char *f1, *f2; // fields 1, 2 = attribute, value + + if((bin = openPW(id, OREAD)) == 0){ + id = "FICTITIOUS"; + if((bin = openPW(id, OREAD)) == 0){ + werrstr("account does not exist"); + 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(dead_or_alive) + return pw; // return PW entry for editing, whether currently valid or not + if(pw->expire <= now){ + werrstr("account expired"); + freePW(pw); + return nil; + } + if((pw->status & Enabled) == 0){ + werrstr("account disabled"); + 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"); + 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); +} + blob - /dev/null blob + 4390129a1bd5beab28c664d673985630f9dcbf44 (mode 644) --- /dev/null +++ src/cmd/auth/secstore/secacct.c @@ -0,0 +1,35 @@ +#include +#include +#include + +int verbose = 1; +static char testmess[] = "__secstore\tPAK\nC=%s\nm=0\n"; + +void +main(int argc, char **argv) +{ + int n, m, fd; + uchar buf[500]; + + if(argc != 2) + exits("usage: secacct userid"); + + n = snprint((char*)buf, sizeof buf, testmess, argv[1]); + hnputs(buf, 0x8000+n-2); + + fd = dial("tcp!ruble.cs.bell-labs.com!5356", 0, 0, 0); + if(fd < 0) + exits("cannot dial ruble"); + if(write(fd, buf, n) != n || readn(fd, buf, 2) != 2) + exits("cannot exchange first round"); + n = ((buf[0]&0x7f)<<8) + buf[1]; + if(n+1 > sizeof buf) + exits("implausibly large count"); + m = readn(fd, buf, n); + close(fd); + if(m != n) + fprint(2,"short read from secstore\n"); + buf[m] = 0; + print("%s\n", (char*)buf); + exits(0); +} blob - /dev/null blob + 59e26d512ae6dfda4aec0a121c6ae5a7ad5ce2db (mode 644) --- /dev/null +++ src/cmd/auth/secstore/secchk.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +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); +} blob - /dev/null blob + 864aa88d373b8467e7e1ba060d36a6a86b99ba4f (mode 644) --- /dev/null +++ src/cmd/auth/secstore/secstore.c @@ -0,0 +1,585 @@ +/* network login client */ +#include +#include +#include +#include +#include +#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; +char *SECSTORE_DIR; + +void +usage(void) +{ + fprint(2, "usage: secstore [-cin] [-g getfile] [-p putfile] [-r rmfile] [-s tcp!server!5356] [-u user] [-v]\n"); + exits("usage"); +} + +static int +getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey) +{ + int fd = -1; + int i, n, nr, nw, len; + char s[Maxmsg+1]; + uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe; + AESstate aes; + DigestState *sha; + + if(strchr(gf, '/')){ + fprint(2, "simple filenames, not paths like %s\n", gf); + return -1; + } + 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'; + bufw = bufe = nil; + if(readstr(conn, s) < 0){ + fprint(2, "remote: %s\n", s); + return -1; + } + len = atoi(s); + if(len == -1){ + fprint(2, "remote file %s does not exist\n", gf); + return -1; + }else if(len == -3){ + fprint(2, "implausible filesize for %s\n", gf); + return -1; + }else if(len < 0){ + fprint(2, "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, "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, "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, "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, "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, "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,"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; iwrite(conn, (uchar*)s, strlen(s)); + + if(buf == nil){ + /* get file size */ + if((fd = open(pf, OREAD)) < 0){ + fprint(2, "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, "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, "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, "saved %ld bytes\n", len); + + return 0; +} + +static int +removefile(SConn *conn, char *rf) +{ + char buf[Maxmsg]; + + if(strchr(rf, '/')){ + fprint(2, "simple filenames, not paths like %s\n", rf); + return -1; + } + + snprint(buf, Maxmsg, "RM %s\n", 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 one 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) +{ + ulong len; + int rv = -1, newpasslen = 0; + mpint *H, *Hi; + uchar *memfile; + char *newpass, *passck; + char *list, *cur, *next, *hexHi; + char *f[8], prompt[128]; + + 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 = readcons(prompt, nil, 1); + 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 = readcons(prompt, nil, 1); + if(passck == nil){ + fprint(2, "readcons failed\n"); + goto Out; + } + if(strcmp(passck, newpass) != 0){ + fprint(2, "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 **)(void*)&list, &len, nil, 0) < 0){ + fprint(2, "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, "reencrypting '%s'\n", f[0]); + if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass, c->passlen) < 0){ + fprint(2, "getfile of '%s' failed\n", f[0]); + continue; + } + if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass, newpasslen) < 0) + fprint(2, "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) +{ + AuthConn *c; + int fd, n, ntry = 0; + char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass; + + if(dest == nil){ + fprint(2, "tried to login with nil dest\n"); + exits("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)pass, c->pass+sizeof c->pass, s); + } + while(1){ + if(verbose) + fprint(2, "dialing %s\n", dest); + if((fd = dial(dest, nil, nil, nil)) < 0){ + fprint(2, "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 = readcons("secstore password", nil, 1); + if(pass == nil) + pass = estrdup(""); + if(strlen(pass) >= sizeof c->pass){ + fprint(2, "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, "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, "server: %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 = readcons("STA PIN+SecureID", nil, 1); + if(pass == nil) + pass = estrdup(""); + 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); + } + if(strcmp(s, "OK") != 0){ + fprint(2, "%s\n", s); + c->conn->free(c->conn); + free(c); + return nil; + } + return c; +} + +int +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 *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES]; + char *serve, *tcpserve, *user; + AuthConn *c; + + serve = "$auth"; + user = getuser(); + memset(Gflag, 0, sizeof Gflag); + fmtinstall('B', mpfmt); + fmtinstall('H', encodefmt); + + ARGBEGIN{ + case 'c': + chpass = 1; + break; + case 'G': + Gflag[ngfile]++; + /* fall through */ + case 'g': + if(ngfile >= MAXFILES) + exits("too many gfiles"); + gfile[ngfile++] = ARGF(); + if(gfile[ngfile-1] == nil) + 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++] = ARGF(); + if(pfile[npfile-1] == nil) + usage(); + break; + case 'r': + if(nrfile >= MAXFILES) + exits("too many rfiles"); + rfile[nrfile++] = ARGF(); + if(rfile[nrfile-1] == nil) + 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, "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){ + fprint(2, "secstore authentication failed\n"); + exits("secstore authentication failed"); + } + if(chpass) + rc = chpasswd(c, user); + else + rc = cmd(c, gfile, Gflag, pfile, rfile); + if(rc < 0){ + fprint(2, "secstore cmd failed\n"); + exits("secstore cmd failed"); + } + exits(""); + return 0; +} + blob - /dev/null blob + dbd2ec9cfeec4dd3aad3a57c5fd9a602ce10168c (mode 644) --- /dev/null +++ src/cmd/auth/secstore/secstore.h @@ -0,0 +1,31 @@ +enum{ MAXFILESIZE = 10*1024*1024 }; + +enum{// PW status bits + 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; + +PW *getPW(char *, int); +int putPW(PW *); +void freePW(PW *); + +// *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 *); + +#define LOG "secstore" + +extern char *SECSTORE_DIR; + blob - /dev/null blob + 58f7459aec7b6f1783fda5734c1693cffecde193 (mode 644) --- /dev/null +++ src/cmd/auth/secstore/secstored.c @@ -0,0 +1,420 @@ +#include +#include +#include +#include +#include +#include +#include "SConn.h" +#include "secstore.h" + +char *SECSTORE_DIR; +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; +} + +char * +validatefile(char *f) +{ + char *nl; + + if(f==nil || *f==0) + return nil; + if(nl = strchr(f, '\n')) + *nl = 0; + if(strchr(f,'/') != nil || strcmp(f,"..")==0 || strlen(f) >= 300){ + syslog(0, LOG, "no slashes allowed: %s\n", f); + return nil; + } + return f; +} + +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\n", 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\n", 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\n", s); + free(s); + conn->write(conn, (uchar*)"-1", 2); + return -1; + } + if(len < 0 || len > MAXFILESIZE){ + syslog(0, LOG, "implausible filesize %d for %s\n", 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\n", 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\n", s); + return -1; + } + len = atoi(s); + if(len == -1){ + syslog(0, LOG, "remote file %s does not exist\n", pf); + return -1; + }else if(len < 0 || len > MAXFILESIZE){ + syslog(0, LOG, "implausible filesize %ld for %s\n", len, pf); + return -1; + } + + /* get file in Maxmsg chunks */ + if(strchr(pf,'/') != nil || strcmp(pf,"..")==0){ + syslog(0, LOG, "no slashes allowed: %s\n", 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\n"); + 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, "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; + 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, "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){ + 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\n", 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 adir[40], ldir[40], *remote; + char *serve = "tcp!*!5356", *p, aserve[128]; + char *S = "secstore"; + char *dbpath; + Ndb *db2; + + S = sysname(); + SECSTORE_DIR = unsharp("#9/secstore"); +// 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': + p = ARGF(); + if(p == nil) + usage(); + USED(p); + // setnetmtpt(net, sizeof(net), p); + 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", serve); + afd = announce(aserve, adir); + if(afd < 0) + sysfatal("%s: %r\n", 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(dbpath=unsharp("#9/ndb/auth")); + if(db == 0) + syslog(0, LOG, "no ndb/auth"); + db2 = ndbopen(0); + if(db2 == 0) + syslog(0, LOG, "no 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; + } + } +} + blob - /dev/null blob + 95adb38541c9a7a656957ae28cabd7e2ecefb149 (mode 644) --- /dev/null +++ src/cmd/auth/secstore/secureidcheck.c @@ -0,0 +1,446 @@ +/* RFC2138 */ +#include +#include +#include +#include +#include +#include +#include +#include +#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 *v, char *msg) +{ + USED(v); +/* 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++){ + alarm(4000); + 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; + while(1){ + 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) +{ + Attribute *a; + char buf[255]; + char pbuf[4*1024]; + uchar *au = p->authenticator; + int i; + char *np, *e; + + 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; ilen; 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) +{ + Packet *req = nil, *resp = nil; + ulong u[4]; + uchar x[16]; + char *radiussecret; + char ruser[ 64]; + char dest[3*IPaddrlen+20]; + Secret shared, pass; + char *rv = "authentication failed"; + Ndbs s; + Ndbtuple *t, *nt, *tt; + uchar *ip; + 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); + + 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!oradius", 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 + } + ndbfree(t); + free(radiussecret); +out: + freePacket(req); + freePacket(resp); + return rv; +} blob - /dev/null blob + 31ba184bb5d680aa3f18d666e44481814ce7cbd6 (mode 644) --- /dev/null +++ src/cmd/auth/secstore/secuser.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include "SConn.h" +#include "secstore.h" + +int verbose; + +static void userinput(char *, int); +char *SECSTORE_DIR; + +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){ + fprint(2, "unable to create %s\n", f); + exits("secstored directories"); + } + close(fd); +} + + +int +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; + + SECSTORE_DIR = unsharp("#9/secstore"); + + ARGBEGIN{ + case 'v': + verbose++; + break; + }ARGEND; + if(argc!=1){ + print("usage: secuser [-v] \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){ + print("new user, but directory %s already exists\n", home); + exits(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 = readcons(prompt, nil, 1); + if(pass == nil){ + print("getpass failed\n"); + exits("getpass 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 = readcons(prompt, nil, 1); + if(passck == nil){ + print("getpass failed\n"); + exits("getpass failed"); + } + if(strcmp(pass, passck) != 0){ + print("passwords didn't match\n"); + exits("no 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, 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->mday < 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){ + print("error writing entry: %r\n"); + exits("can't write password file"); + }else{ + print("change written\n"); + if(isnew && create(home, OREAD, DMDIR | 0775L) < 0){ + print("unable to create %s: %r\n", home); + exits(home); + } + } + + exits(""); + return 1; /* keep other compilers happy */ +} + + +static void +userinput(char *buf, int blen) +{ + int n; + + while(1){ + 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"); + } +} + blob - /dev/null blob + ebbb12df8745f412311704ab7ad1fcecc0be741e (mode 644) --- /dev/null +++ src/cmd/auth/secstore/util.c @@ -0,0 +1,28 @@ +#include +#include + +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; +}