Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * p9any - protocol negotiator.
 *
 * Protocol:
 *      Server->Client: list of proto@domain, tokenize separated, nul terminated
 *      Client->Server: proto domain, tokenize separated (not proto@domain), nul terminated
 *
 * Server protocol:
 *      read list of protocols.
 *      write null-terminated 
 */

#include "dat.h"

static Proto *negotiable[] = {
        &p9sk1,
};

struct State
{
        Fsstate subfss;
        State *substate;        /* be very careful; this is not one of our States */
        Proto *subproto;
        int keyasked;
        String *subdom;
        int version;
};

enum
{
        CNeedProtos,
        CHaveProto,
        CNeedOK,
        CRelay,

        SHaveProtos,
        SNeedProto,
        SHaveOK,
        SRelay,

        Maxphase,
};

static char *phasenames[Maxphase] =
{
[CNeedProtos]   "CNeedProtos",
[CHaveProto]    "CHaveProto",
[CNeedOK]       "CNeedOK",
[CRelay]        "CRelay",
[SHaveProtos]   "SHaveProtos",
[SNeedProto]    "SNeedProto",
[SHaveOK]       "SHaveOK",
[SRelay]        "SRelay",
};

static int
p9anyinit(Proto*, Fsstate *fss)
{
        int iscli;
        State *s;

        if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
                return failure(fss, nil);

        s = emalloc(sizeof *s);
        fss = fss;
        fss->phasename = phasenames;
        fss->maxphase = Maxphase;
        if(iscli)
                fss->phase = CNeedProtos;
        else
                fss->phase = SHaveProtos;
        s->version = 1;
        fss->ps = s;
        return RpcOk;
}

static void
p9anyclose(Fsstate *fss)
{
        State *s;

        s = fss->ps;
        if(s->subproto && s->subfss.ps && s->subproto->close)
                (*s->subproto->close)(&s->subfss);
        s->subproto = nil;
        s->substate = nil;
        s_free(s->subdom);
        s->subdom = nil;
        s->keyasked = 0;
        memset(&s->subfss, 0, sizeof s->subfss);
        free(s);
}

static void
setupfss(Fsstate *fss, State *s, Key *k)
{
        fss->attr = setattr(fss->attr, "proto=%q", s->subproto->name);
        fss->attr = setattr(fss->attr, "dom=%q", _strfindattr(k->attr, "dom"));
        s->subfss.attr = fss->attr;
        s->subfss.phase = Notstarted;
        s->subfss.sysuser = fss->sysuser;
        s->subfss.seqnum = fss->seqnum;
        s->subfss.conf = fss->conf;
        s->subfss.nconf = fss->nconf;
}

static int
passret(Fsstate *fss, State *s, int ret)
{
        switch(ret){
        default:
                return ret;
        case RpcFailure:
                if(s->subfss.phase == Broken)
                        fss->phase = Broken;
                memmove(fss->err, s->subfss.err, sizeof fss->err);
                return ret;
        case RpcNeedkey:
                memmove(fss->keyinfo, s->subfss.keyinfo, sizeof fss->keyinfo);
                return ret;
        case RpcOk:
                if(s->subfss.haveai){
                        fss->haveai = 1;
                        fss->ai = s->subfss.ai;
                        s->subfss.haveai = 0;
                }
                if(s->subfss.phase == Established)
                        fss->phase = Established;
                return ret;
        case RpcToosmall:
                fss->rpc.nwant = s->subfss.rpc.nwant;
                return ret;
        case RpcConfirm:
                fss->conf = s->subfss.conf;
                fss->nconf = s->subfss.nconf;
                return ret;
        }
}

static int
p9anyread(Fsstate *fss, void *a, uint *n)
{
        int i, m, ophase, ret;
        Attr *anew;
        Key *k;
        Keyinfo ki;
        String *negstr;
        State *s;

        s = fss->ps;
        switch(fss->phase){
        default:
                return phaseerror(fss, "read");

        case SHaveProtos:
                m = 0;
                negstr = s_new();
                mkkeyinfo(&ki, fss, nil);
                ki.attr = nil;
                ki.noconf = 1;
                ki.user = nil;
                for(i=0; i<nelem(negotiable); i++){
                        anew = setattr(_copyattr(fss->attr), "proto=%q dom?", negotiable[i]->name);
                        ki.attr = anew;
                        for(ki.skip=0; findkey(&k, &ki, nil)==RpcOk; ki.skip++){
                                if(m++)
                                        s_append(negstr, " ");
                                s_append(negstr, negotiable[i]->name);
                                s_append(negstr, "@");
                                s_append(negstr, _strfindattr(k->attr, "dom"));
                                closekey(k);
                        }
                        _freeattr(anew);
                }
                if(m == 0){
                        s_free(negstr);
                        return failure(fss, Enegotiation);
                }
                i = s_len(negstr)+1;
                if(*n < i){
                        s_free(negstr);
                        return toosmall(fss, i);
                }
                *n = i;
                memmove(a, s_to_c(negstr), i+1);
                fss->phase = SNeedProto;
                s_free(negstr);
                return RpcOk;

        case CHaveProto:
                i = strlen(s->subproto->name)+1+s_len(s->subdom)+1;
                if(*n < i)
                        return toosmall(fss, i);
                *n = i;
                strcpy(a, s->subproto->name);
                strcat(a, " ");
                strcat(a, s_to_c(s->subdom));
                if(s->version == 1)
                        fss->phase = CRelay;
                else
                        fss->phase = CNeedOK;
                return RpcOk;

        case SHaveOK:
                i = 3;
                if(*n < i)
                        return toosmall(fss, i);
                *n = i;
                strcpy(a, "OK");
                fss->phase = SRelay;
                return RpcOk;

        case CRelay:
        case SRelay:
                ophase = s->subfss.phase;
                ret = (*s->subproto->read)(&s->subfss, a, n);
                rpcrdwrlog(&s->subfss, "read", *n, ophase, ret);
                return passret(fss, s, ret);
        }
}

static char*
getdom(char *p)
{
        p = strchr(p, '@');
        if(p == nil)
                return "";
        return p+1;
}

static Proto*
findneg(char *name)
{
        int i, len;
        char *p;

        if(p = strchr(name, '@'))
                len = p-name;
        else
                len = strlen(name);

        for(i=0; i<nelem(negotiable); i++)
                if(strncmp(negotiable[i]->name, name, len) == 0 && negotiable[i]->name[len] == 0)
                        return negotiable[i];
        return nil;
}

static int
p9anywrite(Fsstate *fss, void *va, uint n)
{
        char *a, *dom, *user, *token[20];
        int asking, i, m, ophase, ret;
        Attr *anew, *anewsf, *attr;
        Key *k;
        Keyinfo ki;
        Proto *p;
        State *s;

        s = fss->ps;
        a = va;
        switch(fss->phase){
        default:
                return phaseerror(fss, "write");

        case CNeedProtos:
                if(n==0 || a[n-1] != '\0')
                        return toosmall(fss, 2048);
                a = estrdup(a);
                m = tokenize(a, token, nelem(token));
                if(m > 0 && strncmp(token[0], "v.", 2) == 0){
                        s->version = atoi(token[0]+2);
                        if(s->version != 2){
                                free(a);
                                return failure(fss, "unknown version of p9any");
                        }
                }
        
                /*
                 * look for a key
                 */
                anew = _delattr(_delattr(_copyattr(fss->attr), "proto"), "role");
                anewsf = _delattr(_copyattr(anew), "user");
                user = _strfindattr(anew, "user");
                k = nil;
                p = nil;
                dom = nil;
                for(i=(s->version==1?0:1); i<m; i++){
                        p = findneg(token[i]);
                        if(p == nil)
                                continue;
                        dom = getdom(token[i]);
                        ret = RpcFailure;
                        mkkeyinfo(&ki, fss, nil);
                        if(user==nil || strcmp(user, fss->sysuser)==0){
                                ki.attr = anewsf;
                                ki.user = nil;
                                ret = findkey(&k, &ki, "proto=%q dom=%q role=speakfor %s",
                                                p->name, dom, p->keyprompt);
                        }
                        if(ret == RpcFailure){
                                ki.attr = anew;
                                ki.user = fss->sysuser;
                                ret = findkey(&k, &ki,
                                        "proto=%q dom=%q role=client %s",
                                        p->name, dom, p->keyprompt);
                        }
                        if(ret == RpcConfirm){
                                free(a);
                                return ret;
                        }
                        if(ret == RpcOk)
                                break;
                }
                _freeattr(anewsf);

                /*
                 * no acceptable key, go through the proto@domains one at a time.
                 */
                asking = 0;
                if(k == nil){
                        while(!asking && s->keyasked < m){
                                p = findneg(token[s->keyasked]);
                                if(p == nil){
                                        s->keyasked++;
                                        continue;
                                }
                                dom = getdom(token[s->keyasked]);
                                mkkeyinfo(&ki, fss, nil);
                                ki.attr = anew;
                                ret = findkey(&k, &ki,
                                        "proto=%q dom=%q role=client %s",
                                        p->name, dom, p->keyprompt);
                                s->keyasked++;
                                if(ret == RpcNeedkey){
                                        asking = 1;
                                        break;
                                }
                        }
                }
                if(k == nil){
                        free(a);
                        _freeattr(anew);
                        if(asking)
                                return RpcNeedkey;
                        else if(s->keyasked)
                                return failure(fss, nil);
                        else
                                return failure(fss, Enegotiation);
                }
                s->subdom = s_copy(dom);
                s->subproto = p;
                free(a);
                _freeattr(anew);
                setupfss(fss, s, k);
                closekey(k);
                ret = (*s->subproto->init)(p, &s->subfss);
                rpcstartlog(s->subfss.attr, &s->subfss, ret);
                if(ret == RpcOk)
                        fss->phase = CHaveProto;
                return passret(fss, s, ret);

        case SNeedProto:
                if(n==0 || a[n-1] != '\0')
                        return toosmall(fss, n+1);
                a = estrdup(a);
                m = tokenize(a, token, nelem(token));
                if(m != 2){
                        free(a);
                        return failure(fss, Ebadarg);
                }
                p = findneg(token[0]);
                if(p == nil){
                        free(a);
                        return failure(fss, Enegotiation);
                }
                attr = _delattr(_copyattr(fss->attr), "proto");
                mkkeyinfo(&ki, fss, nil);
                ki.attr = attr;
                ki.user = nil;
                ret = findkey(&k, &ki, "proto=%q dom=%q role=server", token[0], token[1]);
                free(a);
                _freeattr(attr);
                if(ret == RpcConfirm)
                        return ret;
                if(ret != RpcOk)
                        return failure(fss, Enegotiation);
                s->subproto = p;
                setupfss(fss, s, k);
                closekey(k);
                ret = (*s->subproto->init)(p, &s->subfss);
                if(ret == RpcOk){
                        if(s->version == 1)
                                fss->phase = SRelay;
                        else
                                fss->phase = SHaveOK;
                }
                return passret(fss, s, ret);

        case CNeedOK:
                if(n < 3)
                        return toosmall(fss, 3);
                if(strcmp("OK", a) != 0)
                        return failure(fss, "server gave up");
                fss->phase = CRelay;
                return RpcOk;

        case CRelay:
        case SRelay:
                ophase = s->subfss.phase;
                ret = (*s->subproto->write)(&s->subfss, va, n);
                rpcrdwrlog(&s->subfss, "write", n, ophase, ret);
                return passret(fss, s, ret);
        }
}

Proto p9any = 
{
.name=  "p9any",
.init=          p9anyinit,
.write= p9anywrite,
.read=  p9anyread,
.close= p9anyclose,
};