| #include "std.h" |
| #include "dat.h" |
| |
| Conv *conv; |
| |
| ulong taggen = 1; |
| |
| Conv* |
| convalloc(char *sysuser) |
| { |
| Conv *c; |
| |
| c = mallocz(sizeof(Conv), 1); |
| if(c == nil) |
| return nil; |
| c->ref = 1; |
| c->tag = taggen++; |
| c->next = conv; |
| c->sysuser = estrdup(sysuser); |
| c->state = "nascent"; |
| c->rpcwait = chancreate(sizeof(void*), 0); |
| c->keywait = chancreate(sizeof(void*), 0); |
| strcpy(c->err, "protocol has not started"); |
| conv = c; |
| convreset(c); |
| return c; |
| } |
| |
| void |
| convreset(Conv *c) |
| { |
| if(c->ref != 1){ |
| c->hangup = 1; |
| nbsendp(c->rpcwait, 0); |
| while(c->ref > 1) |
| yield(); |
| c->hangup = 0; |
| } |
| c->state = "nascent"; |
| c->err[0] = '\0'; |
| freeattr(c->attr); |
| c->attr = nil; |
| c->proto = nil; |
| c->rpc.op = 0; |
| c->active = 0; |
| c->done = 0; |
| c->hangup = 0; |
| } |
| |
| void |
| convhangup(Conv *c) |
| { |
| c->hangup = 1; |
| c->rpc.op = 0; |
| (*c->kickreply)(c); |
| nbsendp(c->rpcwait, 0); |
| } |
| |
| void |
| convclose(Conv *c) |
| { |
| Conv *p; |
| |
| if(c == nil) |
| return; |
| |
| if(--c->ref > 0) |
| return; |
| |
| if(c == conv){ |
| conv = c->next; |
| goto free; |
| } |
| for(p=conv; p && p->next!=c; p=p->next) |
| ; |
| if(p == nil){ |
| print("cannot find conv in list\n"); |
| return; |
| } |
| p->next = c->next; |
| |
| free: |
| c->next = nil; |
| free(c); |
| } |
| |
| static Rpc* |
| convgetrpc(Conv *c, int want) |
| { |
| for(;;){ |
| if(c->hangup){ |
| flog("convgetrpc: hangup"); |
| werrstr("hangup"); |
| return nil; |
| } |
| if(c->rpc.op == RpcUnknown){ |
| recvp(c->rpcwait); |
| if(c->hangup){ |
| flog("convgetrpc: hangup"); |
| werrstr("hangup"); |
| return nil; |
| } |
| if(c->rpc.op == RpcUnknown) |
| continue; |
| } |
| if(want < 0 || c->rpc.op == want) |
| return &c->rpc; |
| rpcrespond(c, "phase in state '%s' want '%s'", c->state, rpcname[want]); |
| } |
| /* not reached */ |
| } |
| |
| /* read until the done function tells us that's enough */ |
| int |
| convreadfn(Conv *c, int (*done)(void*, int), char **ps) |
| { |
| int n; |
| Rpc *r; |
| char *s; |
| |
| for(;;){ |
| r = convgetrpc(c, RpcWrite); |
| if(r == nil) |
| return -1; |
| n = (*done)(r->data, r->count); |
| if(n == r->count) |
| break; |
| rpcrespond(c, "toosmall %d", n); |
| } |
| |
| s = emalloc(r->count+1); |
| memmove(s, r->data, r->count); |
| s[r->count] = 0; |
| *ps = s; |
| rpcrespond(c, "ok"); |
| return r->count; |
| } |
| |
| /* |
| * read until we get a non-zero write. assumes remote side |
| * knows something about the protocol (is not auth_proxy). |
| * the remote side typically won't bother with the zero-length |
| * write to find out the length -- the loop is there only so the |
| * test program can call auth_proxy on both sides of a pipe |
| * to play a conversation. |
| */ |
| int |
| convreadm(Conv *c, char **ps) |
| { |
| char *s; |
| Rpc *r; |
| |
| for(;;){ |
| r = convgetrpc(c, RpcWrite); |
| if(r == nil) |
| return -1; |
| if(r->count > 0) |
| break; |
| rpcrespond(c, "toosmall %d", AuthRpcMax); |
| } |
| s = emalloc(r->count+1); |
| memmove(s, r->data, r->count); |
| s[r->count] = 0; |
| *ps = s; |
| rpcrespond(c, "ok"); |
| return r->count; |
| } |
| |
| /* read exactly count bytes */ |
| int |
| convread(Conv *c, void *data, int count) |
| { |
| Rpc *r; |
| |
| for(;;){ |
| r = convgetrpc(c, RpcWrite); |
| if(r == nil) |
| return -1; |
| if(r->count == count) |
| break; |
| if(r->count < count) |
| rpcrespond(c, "toosmall %d", count); |
| else |
| rpcrespond(c, "error too much data; want %d got %d", count, r->count); |
| } |
| memmove(data, r->data, count); |
| rpcrespond(c, "ok"); |
| return 0; |
| } |
| |
| /* write exactly count bytes */ |
| int |
| convwrite(Conv *c, void *data, int count) |
| { |
| Rpc *r; |
| |
| r = convgetrpc(c, RpcRead); |
| if(r == nil) |
| return -1; |
| rpcrespondn(c, "ok", data, count); |
| return 0; |
| } |
| |
| /* print to the conversation */ |
| int |
| convprint(Conv *c, char *fmt, ...) |
| { |
| char *s; |
| va_list arg; |
| int ret; |
| |
| va_start(arg, fmt); |
| s = vsmprint(fmt, arg); |
| va_end(arg); |
| if(s == nil) |
| return -1; |
| ret = convwrite(c, s, strlen(s)); |
| free(s); |
| return ret; |
| } |
| |
| /* ask for a key */ |
| int |
| convneedkey(Conv *c, Attr *a) |
| { |
| /* |
| * Piggyback key requests in the usual RPC channel. |
| * Wait for the next RPC and then send a key request |
| * in response. The keys get added out-of-band (via the |
| * ctl file), so assume the key has been added when the |
| * next request comes in. |
| * |
| * The convgetrpc seems dodgy, because we might be in |
| * the middle of an rpc, and what about the one that comes |
| * in later? It's all actually okay: convgetrpc is idempotent |
| * until rpcrespond is called, so if we're in the middle of an rpc, |
| * the first convgetrpc is a no-op, the rpcrespond sends back |
| * the needkey, and then the client repeats the rpc we're in |
| * the middle of. Otherwise, if we're not in the middle of an |
| * rpc, the first convgetrpc waits for one, we respond needkey, |
| * and then the second convgetrpc waits for another. Because |
| * there is no second response, eventually the caller will get |
| * around to asking for an rpc itself, at which point the already |
| * gotten rpc will be returned again. |
| */ |
| if(convgetrpc(c, -1) == nil) |
| return -1; |
| if(conv->proto) |
| a = addattrs(parseattr(c->proto->keyprompt), a); |
| flog("convneedkey %A", a); |
| rpcrespond(c, "needkey %A", a); |
| if(convgetrpc(c, -1) == nil) |
| return -1; |
| flog("convneedkey returning"); |
| return 0; |
| } |
| |
| /* ask for a replacement for a bad key*/ |
| int |
| convbadkey(Conv *c, Key *k, char *msg, Attr *a) |
| { |
| if(convgetrpc(c, -1) == nil) |
| return -1; |
| flog("convbadkey %A %N / %s / %A", k->attr, k->privattr, msg, a); |
| rpcrespond(c, "badkey %A %N\n%s\n%A", |
| k->attr, k->privattr, msg, a); |
| if(convgetrpc(c, -1) == nil) |
| return -1; |
| return 0; |
| } |
| |