527 lines
12 KiB
C
527 lines
12 KiB
C
/*
|
|
* This file is part of the UCB release of Plan 9. It is subject to the license
|
|
* terms in the LICENSE file found in the top-level directory of this
|
|
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
|
|
* part of the UCB release of Plan 9, including this file, may be copied,
|
|
* modified, propagated, or distributed except according to the terms contained
|
|
* in the LICENSE file.
|
|
*/
|
|
/* Portions of this file are Copyright (C) 9front's team.
|
|
* See /doc/license/9front-mit for details about the licensing.
|
|
* See http://git.9front.org/plan9front/plan9front/HEAD/info.html for a list of authors.
|
|
*/
|
|
|
|
/*
|
|
* Factotum RPC
|
|
*
|
|
* Must be paired write/read cycles on /mnt/factotum/rpc.
|
|
* The format of a request is verb, single space, data.
|
|
* Data format is verb-dependent; in particular, it can be binary.
|
|
* The format of a response is the same. The write only sets up
|
|
* the RPC. The read tries to execute it. If the /mnt/factotum/key
|
|
* file is open, we ask for new keys using that instead of returning
|
|
* an error in the RPC. This means the read blocks.
|
|
* Textual arguments are parsed with tokenize, so rc-style quoting
|
|
* rules apply.
|
|
*
|
|
* Only authentication protocol messages go here. Configuration
|
|
* is still via ctl (below).
|
|
*
|
|
* Return values are:
|
|
* error message - an error happened.
|
|
* ok [data] - success, possible data is request dependent.
|
|
* needkey proto domain user - request aborted, get me this key and try again
|
|
* badkey proto domain user - request aborted, this key might be bad
|
|
* done [haveai] - authentication is done [haveai: you can get an ai with authinfo]
|
|
* Request RPCs are:
|
|
* start attrs - initializes protocol for authentication, can fail.
|
|
* returns "ok read" or "ok write" on success.
|
|
* read - execute protocol read
|
|
* write - execute protocol write
|
|
* authinfo - if the protocol is finished, return the AI if any
|
|
* attr - return protocol information
|
|
*/
|
|
#define PORTABLE_SYSCALLS
|
|
#include "dat.h"
|
|
|
|
Req *rpcwait;
|
|
|
|
typedef struct Verb Verb;
|
|
struct Verb {
|
|
char *verb;
|
|
int iverb;
|
|
};
|
|
|
|
enum {
|
|
Vunknown = -1,
|
|
Vauthinfo = 0,
|
|
Vread,
|
|
Vstart,
|
|
Vwrite,
|
|
Vattr,
|
|
};
|
|
|
|
Verb rpctab[] = {
|
|
"authinfo", Vauthinfo,
|
|
"read", Vread,
|
|
"start", Vstart,
|
|
"write", Vwrite,
|
|
"attr", Vattr,
|
|
};
|
|
|
|
static int
|
|
classify(char *s, Verb *verbtab, int nverbtab)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; i<nverbtab; i++)
|
|
if(strcmp(s, verbtab[i].verb) == 0)
|
|
return verbtab[i].iverb;
|
|
return Vunknown;
|
|
}
|
|
|
|
void
|
|
rpcwrite(Req *r)
|
|
{
|
|
Fsstate *fss;
|
|
|
|
if(r->ifcall.count >= Maxrpc){
|
|
respond(r, Etoolarge);
|
|
return;
|
|
}
|
|
fss = r->fid->aux;
|
|
if(fss->pending){
|
|
respond(r, "rpc already pending; read to clear");
|
|
return;
|
|
}
|
|
memmove(fss->rpc.buf, r->ifcall.data, r->ifcall.count);
|
|
fss->rpc.buf[r->ifcall.count] = '\0';
|
|
fss->rpc.verb = fss->rpc.buf;
|
|
if(fss->rpc.arg = strchr(fss->rpc.buf, ' ')){
|
|
*fss->rpc.arg++ = '\0';
|
|
fss->rpc.narg = r->ifcall.count - (fss->rpc.arg - fss->rpc.buf);
|
|
}else{
|
|
fss->rpc.arg = "";
|
|
fss->rpc.narg = 0;
|
|
}
|
|
fss->rpc.iverb = classify(fss->rpc.verb, rpctab, nelem(rpctab));
|
|
r->ofcall.count = r->ifcall.count;
|
|
fss->pending = 1;
|
|
respond(r, nil);
|
|
}
|
|
|
|
static void
|
|
retstring(Req *r, Fsstate *fss, char *s)
|
|
{
|
|
int n;
|
|
|
|
n = strlen(s);
|
|
if(n > r->ifcall.count)
|
|
n = r->ifcall.count;
|
|
memmove(r->ofcall.data, s, n);
|
|
r->ofcall.count = n;
|
|
fss->pending = 0;
|
|
respond(r, nil);
|
|
return;
|
|
}
|
|
|
|
void
|
|
retrpc(Req *r, int ret, Fsstate *fss)
|
|
{
|
|
switch(ret){
|
|
default:
|
|
snprint(fss->rpc.buf, Maxrpc, "internal error %d", ret);
|
|
retstring(r, fss, fss->rpc.buf);
|
|
return;
|
|
case RpcErrstr:
|
|
snprint(fss->rpc.buf, Maxrpc, "error %r");
|
|
retstring(r, fss, fss->rpc.buf);
|
|
return;
|
|
case RpcFailure:
|
|
snprint(fss->rpc.buf, Maxrpc, "error %s", fss->err);
|
|
retstring(r, fss, fss->rpc.buf);
|
|
return;
|
|
case RpcNeedkey:
|
|
if(needkeyqueue(r, fss) < 0){
|
|
snprint(fss->rpc.buf, Maxrpc, "needkey %s", fss->keyinfo);
|
|
retstring(r, fss, fss->rpc.buf);
|
|
}
|
|
return;
|
|
case RpcOk:
|
|
retstring(r, fss, "ok");
|
|
return;
|
|
case RpcToosmall:
|
|
snprint(fss->rpc.buf, Maxrpc, "toosmall %d", fss->rpc.nwant);
|
|
retstring(r, fss, fss->rpc.buf);
|
|
return;
|
|
case RpcPhase:
|
|
snprint(fss->rpc.buf, Maxrpc, "phase %r");
|
|
retstring(r, fss, fss->rpc.buf);
|
|
return;
|
|
case RpcConfirm:
|
|
confirmqueue(r, fss);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int
|
|
rdwrcheck(Req *r, Fsstate *fss)
|
|
{
|
|
if(fss->ps == nil){
|
|
retstring(r, fss, "error no current protocol");
|
|
return -1;
|
|
}
|
|
if(fss->phase == Notstarted){
|
|
retstring(r, fss, "protocol not started");
|
|
return -1;
|
|
}
|
|
if(fss->phase == Broken){
|
|
snprint(fss->rpc.buf, Maxrpc, "error %s", fss->err);
|
|
retstring(r, fss, fss->rpc.buf);
|
|
return -1;
|
|
}
|
|
if(fss->phase == Established){
|
|
if(fss->haveai)
|
|
retstring(r, fss, "done haveai");
|
|
else
|
|
retstring(r, fss, "done");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
logret(char *pre, Fsstate *fss, int ret)
|
|
{
|
|
switch(ret){
|
|
default:
|
|
flog("%s: code %d", pre, ret);
|
|
break;
|
|
case RpcErrstr:
|
|
flog("%s: error %r", pre);
|
|
break;
|
|
case RpcFailure:
|
|
flog("%s: failure %s", pre, fss->err);
|
|
break;
|
|
case RpcNeedkey:
|
|
flog("%s: needkey %s", pre, fss->keyinfo);
|
|
break;
|
|
case RpcOk:
|
|
flog("%s: ok", pre);
|
|
break;
|
|
case RpcToosmall:
|
|
flog("%s: toosmall %d", pre, fss->rpc.nwant);
|
|
break;
|
|
case RpcPhase:
|
|
flog("%s: phase: %r", pre);
|
|
break;
|
|
case RpcConfirm:
|
|
flog("%s: waiting for confirm", pre);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
rpcrdwrlog(Fsstate *fss, char *rdwr, uint32_t n, int ophase, int ret)
|
|
{
|
|
char buf0[40], buf1[40], pre[300];
|
|
|
|
if(!debug)
|
|
return;
|
|
snprint(pre, sizeof pre, "%d: %s %ud in phase %s yields phase %s",
|
|
fss->seqnum, rdwr, n, phasename(fss, ophase, buf0), phasename(fss, fss->phase, buf1));
|
|
logret(pre, fss, ret);
|
|
}
|
|
|
|
void
|
|
rpcstartlog(Attr *attr, Fsstate *fss, int ret)
|
|
{
|
|
char pre[300], tmp[40];
|
|
|
|
if(!debug)
|
|
return;
|
|
snprint(pre, sizeof pre, "%d: start %A yields phase %s", fss->seqnum,
|
|
attr, phasename(fss, fss->phase, tmp));
|
|
logret(pre, fss, ret);
|
|
}
|
|
|
|
int seqnum;
|
|
|
|
void
|
|
rpcread(Req *r)
|
|
{
|
|
Attr *attr;
|
|
char *p;
|
|
int ophase, ret;
|
|
uint8_t *e;
|
|
uint32_t count;
|
|
Fsstate *fss;
|
|
Proto *proto;
|
|
|
|
if(r->ifcall.count < 64){
|
|
respond(r, "rpc read too small");
|
|
return;
|
|
}
|
|
fss = r->fid->aux;
|
|
if(!fss->pending){
|
|
respond(r, "no rpc pending");
|
|
return;
|
|
}
|
|
switch(fss->rpc.iverb){
|
|
default:
|
|
case Vunknown:
|
|
retstring(r, fss, "error unknown verb");
|
|
break;
|
|
|
|
case Vstart:
|
|
if(fss->phase != Notstarted){
|
|
flog("%d: implicit close due to second start; old attr '%A'", fss->seqnum, fss->attr);
|
|
if(fss->proto && fss->ps)
|
|
(*fss->proto->close)(fss);
|
|
fss->ps = nil;
|
|
fss->proto = nil;
|
|
_freeattr(fss->attr);
|
|
fss->attr = nil;
|
|
fss->phase = Notstarted;
|
|
}
|
|
attr = _parseattr(fss->rpc.arg);
|
|
if((p = _strfindattr(attr, "proto")) == nil){
|
|
retstring(r, fss, "error did not specify proto");
|
|
_freeattr(attr);
|
|
break;
|
|
}
|
|
if((proto = findproto(p)) == nil){
|
|
snprint(fss->rpc.buf, Maxrpc, "error unknown protocol %q", p);
|
|
retstring(r, fss, fss->rpc.buf);
|
|
_freeattr(attr);
|
|
break;
|
|
}
|
|
fss->attr = attr;
|
|
fss->proto = proto;
|
|
fss->seqnum = ++seqnum;
|
|
ret = (*proto->init)(proto, fss);
|
|
rpcstartlog(attr, fss, ret);
|
|
if(ret != RpcOk){
|
|
_freeattr(fss->attr);
|
|
fss->attr = nil;
|
|
fss->phase = Notstarted;
|
|
}
|
|
retrpc(r, ret, fss);
|
|
break;
|
|
|
|
case Vread:
|
|
if(fss->rpc.arg && fss->rpc.arg[0]){
|
|
retstring(r, fss, "error read needs no parameters");
|
|
break;
|
|
}
|
|
if(rdwrcheck(r, fss) < 0)
|
|
break;
|
|
count = r->ifcall.count - 3;
|
|
ophase = fss->phase;
|
|
ret = fss->proto->read(fss, (uint8_t*)r->ofcall.data+3, &count);
|
|
rpcrdwrlog(fss, "read", count, ophase, ret);
|
|
if(ret == RpcOk){
|
|
memmove(r->ofcall.data, "ok ", 3);
|
|
if(count == 0)
|
|
r->ofcall.count = 2;
|
|
else
|
|
r->ofcall.count = 3+count;
|
|
fss->pending = 0;
|
|
respond(r, nil);
|
|
}else
|
|
retrpc(r, ret, fss);
|
|
break;
|
|
|
|
case Vwrite:
|
|
if(rdwrcheck(r, fss) < 0)
|
|
break;
|
|
ophase = fss->phase;
|
|
ret = fss->proto->write(fss, fss->rpc.arg, fss->rpc.narg);
|
|
rpcrdwrlog(fss, "write", fss->rpc.narg, ophase, ret);
|
|
retrpc(r, ret, fss);
|
|
break;
|
|
|
|
case Vauthinfo:
|
|
if(fss->phase != Established){
|
|
retstring(r, fss, "error authentication unfinished");
|
|
break;
|
|
}
|
|
if(!fss->haveai){
|
|
retstring(r, fss, "error no authinfo available");
|
|
break;
|
|
}
|
|
memmove(r->ofcall.data, "ok ", 3);
|
|
fss->ai.cap = mkcap(r->fid->uid, fss->ai.suid);
|
|
e = convAI2M(&fss->ai, (uint8_t*)r->ofcall.data+3, r->ifcall.count-3);
|
|
free(fss->ai.cap);
|
|
fss->ai.cap = nil;
|
|
if(e == nil){
|
|
retstring(r, fss, "error read too small");
|
|
break;
|
|
}
|
|
r->ofcall.count = e - (uint8_t*)r->ofcall.data;
|
|
fss->pending = 0;
|
|
respond(r, nil);
|
|
break;
|
|
|
|
case Vattr:
|
|
snprint(fss->rpc.buf, Maxrpc, "ok %A", fss->attr);
|
|
retstring(r, fss, fss->rpc.buf);
|
|
break;
|
|
}
|
|
}
|
|
|
|
enum {
|
|
Vdelkey,
|
|
Vaddkey,
|
|
Vdebug,
|
|
};
|
|
|
|
Verb ctltab[] = {
|
|
"delkey", Vdelkey,
|
|
"key", Vaddkey,
|
|
"debug", Vdebug,
|
|
};
|
|
|
|
/*
|
|
* key attr=val... - add a key
|
|
* the attr=val pairs are protocol-specific.
|
|
* for example, both of these are valid:
|
|
* key p9sk1 gre cs.bell-labs.com mysecret
|
|
* key p9sk1 gre cs.bell-labs.com 11223344556677 fmt=des7hex
|
|
* delkey ... - delete a key
|
|
* if given, the attr=val pairs are used to narrow the search
|
|
* [maybe should require a password?]
|
|
*/
|
|
|
|
int
|
|
ctlwrite(char *a, int atzero)
|
|
{
|
|
char *p;
|
|
int i, nmatch, ret;
|
|
Attr *attr, **l, **lpriv, **lprotos, *pa, *priv, *protos;
|
|
Key *k;
|
|
Proto *proto;
|
|
|
|
if(a[0] == '#' || a[0] == '\0')
|
|
return 0;
|
|
|
|
/*
|
|
* it would be nice to emit a warning of some sort here.
|
|
* we ignore all but the first line of the write. this helps
|
|
* both with things like "echo delkey >/mnt/factotum/ctl"
|
|
* and writes that (incorrectly) contain multiple key lines.
|
|
*/
|
|
if(p = strchr(a, '\n')){
|
|
if(p[1] != '\0'){
|
|
werrstr("multiline write not allowed");
|
|
return -1;
|
|
}
|
|
*p = '\0';
|
|
}
|
|
|
|
if((p = strchr(a, ' ')) == nil)
|
|
p = "";
|
|
else
|
|
*p++ = '\0';
|
|
switch(classify(a, ctltab, nelem(ctltab))){
|
|
default:
|
|
case Vunknown:
|
|
werrstr("unknown verb");
|
|
return -1;
|
|
case Vdebug:
|
|
debug ^= 1;
|
|
return 0;
|
|
case Vdelkey:
|
|
nmatch = 0;
|
|
attr = _parseattr(p);
|
|
for(pa=attr; pa; pa=pa->next){
|
|
if(pa->type != AttrQuery && pa->name[0]=='!'){
|
|
werrstr("only !private? patterns are allowed for private fields");
|
|
_freeattr(attr);
|
|
return -1;
|
|
}
|
|
}
|
|
Again:
|
|
qlock(ring);
|
|
for(i=0; i<ring->nkey; i++){
|
|
k = ring->key[i];
|
|
if(matchattr(attr, k->attr, k->privattr)){
|
|
nmatch++;
|
|
ring->nkey--;
|
|
memmove(&ring->key[i], &ring->key[i+1], (ring->nkey-i)*sizeof(ring->key[0]));
|
|
qunlock(ring);
|
|
closekey(k);
|
|
goto Again;
|
|
}
|
|
}
|
|
qunlock(ring);
|
|
_freeattr(attr);
|
|
if(nmatch == 0){
|
|
werrstr("found no keys to delete");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
case Vaddkey:
|
|
attr = _parseattr(p);
|
|
/* separate out proto= attributes */
|
|
lprotos = &protos;
|
|
for(l=&attr; (*l); ){
|
|
if(strcmp((*l)->name, "proto") == 0){
|
|
*lprotos = *l;
|
|
lprotos = &(*l)->next;
|
|
*l = (*l)->next;
|
|
}else
|
|
l = &(*l)->next;
|
|
}
|
|
*lprotos = nil;
|
|
if(protos == nil){
|
|
werrstr("key without protos");
|
|
_freeattr(attr);
|
|
return -1;
|
|
}
|
|
|
|
/* separate out private attributes */
|
|
lpriv = &priv;
|
|
for(l=&attr; (*l); ){
|
|
if((*l)->name[0] == '!'){
|
|
*lpriv = *l;
|
|
lpriv = &(*l)->next;
|
|
*l = (*l)->next;
|
|
}else
|
|
l = &(*l)->next;
|
|
}
|
|
*lpriv = nil;
|
|
|
|
/* add keys */
|
|
ret = 0;
|
|
for(pa=protos; pa; pa=pa->next){
|
|
if((proto = findproto(pa->val)) == nil){
|
|
werrstr("unknown proto %s", pa->val);
|
|
ret = -1;
|
|
continue;
|
|
}
|
|
if(proto->addkey == nil){
|
|
werrstr("proto %s doesn't take keys", proto->name);
|
|
ret = -1;
|
|
continue;
|
|
}
|
|
k = emalloc(sizeof(Key));
|
|
k->attr = _mkattr(AttrNameval, "proto", proto->name, _copyattr(attr));
|
|
k->privattr = _copyattr(priv);
|
|
k->ref = 1;
|
|
k->proto = proto;
|
|
if(proto->addkey(k, atzero) < 0){
|
|
ret = -1;
|
|
closekey(k);
|
|
continue;
|
|
}
|
|
closekey(k);
|
|
}
|
|
_freeattr(attr);
|
|
_freeattr(priv);
|
|
_freeattr(protos);
|
|
return ret;
|
|
}
|
|
}
|