| #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; i<ring.nkey; i++){ |
| k = ring.key[i]; |
| for(j=0; okproto[j]; j++) |
| if(k->proto == 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; i<n; i++){ |
| if((q = strchr(f[i], '@')) == nil) |
| continue; |
| if(dom && strcmp(q+1, dom) != 0) |
| continue; |
| *q++ = '\0'; |
| if((k = keylookup("%A proto=%q dom=%q", attr, f[i], q)) |
| && strfindattr(k->attr, "confirm") == nil) |
| goto found; |
| *--q = '@'; |
| } |
| |
| /* look for any keys at all */ |
| for(i=0; i<n; i++){ |
| if((q = strchr(f[i], '@')) == nil) |
| continue; |
| if(dom && strcmp(q+1, dom) != 0) |
| continue; |
| *q++ = '\0'; |
| if(k = keylookup("%A proto=%q dom=%q", attr, f[i], q)) |
| goto found; |
| *--q = '@'; |
| } |
| |
| /* ask for new keys */ |
| c->state = "ask for keys"; |
| for(i=0; i<n; i++){ |
| if((q = strchr(f[i], '@')) == nil) |
| continue; |
| if(dom && strcmp(q+1, dom) != 0) |
| continue; |
| *q++ = '\0'; |
| p = protolookup(f[i]); |
| if(p == nil || p->keyprompt == 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 |
| }; |
| |