#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, *p, *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"; p = s; if(convwrite(c, p, strlen(p)+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]); freeattr(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 = { "p9any", p9anyroles };