blob: 0267a6164717a68000c578b20fe517428d7fbd4d [file] [log] [blame]
#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
};