aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/auth/factotum/apop.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/auth/factotum/apop.c')
-rw-r--r--src/cmd/auth/factotum/apop.c350
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?",
+};