diff options
Diffstat (limited to 'src/cmd/auth/factotum/apop.c')
-rw-r--r-- | src/cmd/auth/factotum/apop.c | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/src/cmd/auth/factotum/apop.c b/src/cmd/auth/factotum/apop.c new file mode 100644 index 00000000..8e340d47 --- /dev/null +++ b/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?", +}; |