ref: ae8a9f8b900f6bd4e0f9c6cd99fbe44c1a46e4a3
parent: 38c2cdf164f0cc12db65f9f51b3b4a7815543d12
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Tue Apr 2 12:23:01 EDT 2019
sshnet: bring back sshnet using ssh(1) mux mode this is a port of the original ssh1 sshnet to our ssh2 client using mux mode.
--- /dev/null
+++ b/sys/src/cmd/sshnet.c
@@ -1,0 +1,1286 @@
+/*
+ * SSH network file system.
+ * Presents remote TCP stack as /net-style file system.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+
+typedef struct Client Client;
+typedef struct Msg Msg;
+
+enum
+{
+ Qroot,
+ Qcs,
+ Qtcp,
+ Qclone,
+ Qn,
+ Qctl,
+ Qdata,
+ Qlocal,
+ Qremote,
+ Qstatus,
+};
+
+#define PATH(type, n) ((type)|((n)<<8))
+#define TYPE(path) ((int)(path) & 0xFF)
+#define NUM(path) ((uint)(path)>>8)
+
+Channel *sshmsgchan; /* chan(Msg*) */
+Channel *fsreqchan; /* chan(Req*) */
+Channel *fsreqwaitchan; /* chan(nil) */
+Channel *fsclunkchan; /* chan(Fid*) */
+Channel *fsclunkwaitchan; /* chan(nil) */
+ulong time0;
+
+enum
+{
+ Closed,
+ Dialing,
+ Established,
+ Teardown,
+};
+
+char *statestr[] = {
+ "Closed",
+ "Dialing",
+ "Established",
+ "Teardown",
+};
+
+struct Client
+{
+ int ref;
+ int state;
+ int num;
+ int servernum;
+ char *connect;
+
+ int sendpkt;
+ int sendwin;
+ int recvwin;
+ int recvacc;
+
+ Req *wq;
+ Req **ewq;
+
+ Req *rq;
+ Req **erq;
+
+ Msg *mq;
+ Msg **emq;
+};
+
+enum {
+ MSG_CHANNEL_OPEN = 90,
+ MSG_CHANNEL_OPEN_CONFIRMATION,
+ MSG_CHANNEL_OPEN_FAILURE,
+ MSG_CHANNEL_WINDOW_ADJUST,
+ MSG_CHANNEL_DATA,
+ MSG_CHANNEL_EXTENDED_DATA,
+ MSG_CHANNEL_EOF,
+ MSG_CHANNEL_CLOSE,
+ MSG_CHANNEL_REQUEST,
+ MSG_CHANNEL_SUCCESS,
+ MSG_CHANNEL_FAILURE,
+
+ MaxPacket = 1<<15,
+ WinPackets = 8,
+};
+
+struct Msg
+{
+ Msg *link;
+
+ uchar *rp;
+ uchar *wp;
+ uchar *ep;
+ uchar buf[MaxPacket];
+};
+
+#define PUT4(p, u) (p)[0] = (u)>>24, (p)[1] = (u)>>16, (p)[2] = (u)>>8, (p)[3] = (u)
+#define GET4(p) (u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24
+
+int nclient;
+Client **client;
+char *mtpt;
+int sshfd;
+int localport;
+char localip[] = "::";
+
+int
+vpack(uchar *p, int n, char *fmt, va_list a)
+{
+ uchar *p0 = p, *e = p+n;
+ u32int u;
+ void *s;
+ int c;
+
+ for(;;){
+ switch(c = *fmt++){
+ case '\0':
+ return p - p0;
+ case '_':
+ if(++p > e) goto err;
+ break;
+ case '.':
+ *va_arg(a, void**) = p;
+ break;
+ case 'b':
+ if(p >= e) goto err;
+ *p++ = va_arg(a, int);
+ break;
+ case '[':
+ case 's':
+ s = va_arg(a, void*);
+ u = va_arg(a, int);
+ if(c == 's'){
+ if(p+4 > e) goto err;
+ PUT4(p, u), p += 4;
+ }
+ if(u > e-p) goto err;
+ memmove(p, s, u);
+ p += u;
+ break;
+ case 'u':
+ u = va_arg(a, int);
+ if(p+4 > e) goto err;
+ PUT4(p, u), p += 4;
+ break;
+ }
+ }
+err:
+ return -1;
+}
+
+int
+vunpack(uchar *p, int n, char *fmt, va_list a)
+{
+ uchar *p0 = p, *e = p+n;
+ u32int u;
+ void *s;
+
+ for(;;){
+ switch(*fmt++){
+ case '\0':
+ return p - p0;
+ case '_':
+ if(++p > e) goto err;
+ break;
+ case '.':
+ *va_arg(a, void**) = p;
+ break;
+ case 'b':
+ if(p >= e) goto err;
+ *va_arg(a, int*) = *p++;
+ break;
+ case 's':
+ if(p+4 > e) goto err;
+ u = GET4(p), p += 4;
+ if(u > e-p) goto err;
+ *va_arg(a, void**) = p;
+ *va_arg(a, int*) = u;
+ p += u;
+ break;
+ case '[':
+ s = va_arg(a, void*);
+ u = va_arg(a, int);
+ if(u > e-p) goto err;
+ memmove(s, p, u);
+ p += u;
+ break;
+ case 'u':
+ if(p+4 > e) goto err;
+ u = GET4(p);
+ *va_arg(a, int*) = u;
+ p += 4;
+ break;
+ }
+ }
+err:
+ return -1;
+}
+
+Msg*
+allocmsg(void)
+{
+ Msg *m;
+
+ m = emalloc9p(sizeof(Msg));
+ m->link = nil;
+ m->rp = m->wp = m->buf;
+ m->ep = m->rp + sizeof(m->buf);
+ return m;
+}
+
+Msg*
+pack(Msg *m, char *fmt, ...)
+{
+ va_list a;
+ int n;
+
+ if(m == nil)
+ m = allocmsg();
+ va_start(a, fmt);
+ n = vpack(m->wp, m->ep - m->wp, fmt, a);
+ if(n < 0)
+ sysfatal("pack faild");
+ m->wp += n;
+ va_end(a);
+ return m;
+}
+
+int
+unpack(Msg *m, char *fmt, ...)
+{
+ va_list a;
+ int n;
+
+ va_start(a, fmt);
+ n = vunpack(m->rp, m->wp - m->rp, fmt, a);
+ if(n > 0)
+ m->rp += n;
+ va_end(a);
+ return n;
+}
+
+void
+sendmsg(Msg *m)
+{
+ int n;
+
+ if(m == nil)
+ return;
+ n = m->wp - m->rp;
+ if(n > 0){
+ if(write(sshfd, m->rp, n) != n)
+ sysfatal("write to ssh failed: %r");
+ }
+ free(m);
+}
+
+int
+newclient(void)
+{
+ int i;
+ Client *c;
+
+ for(i=0; i<nclient; i++)
+ if(client[i]->ref==0 && client[i]->state == Closed)
+ return i;
+
+ if(nclient%16 == 0)
+ client = erealloc9p(client, (nclient+16)*sizeof(client[0]));
+
+ c = emalloc9p(sizeof(Client));
+ memset(c, 0, sizeof(*c));
+ c->num = nclient;
+ client[nclient++] = c;
+ return c->num;
+}
+
+Client*
+getclient(int num)
+{
+ if(num < 0 || num >= nclient)
+ return nil;
+ return client[num];
+}
+
+void
+adjustwin(Client *c, int len)
+{
+ c->recvacc += len;
+ if(c->recvacc >= MaxPacket*WinPackets/2 || c->recvwin < MaxPacket){
+ sendmsg(pack(nil, "buu", MSG_CHANNEL_WINDOW_ADJUST, c->servernum, c->recvacc));
+ c->recvacc = 0;
+ }
+ c->recvwin += len;
+}
+
+void
+senddata(Client *c, void *data, int len)
+{
+ sendmsg(pack(nil, "bus", MSG_CHANNEL_DATA, c->servernum, (char*)data, len));
+ c->sendwin -= len;
+}
+
+void
+queuerreq(Client *c, Req *r)
+{
+ if(c->rq==nil)
+ c->erq = &c->rq;
+ *c->erq = r;
+ r->aux = nil;
+ c->erq = (Req**)&r->aux;
+}
+
+void
+queuermsg(Client *c, Msg *m)
+{
+ if(c->mq==nil)
+ c->emq = &c->mq;
+ *c->emq = m;
+ m->link = nil;
+ c->emq = (Msg**)&m->link;
+}
+
+void
+matchrmsgs(Client *c)
+{
+ Req *r;
+ Msg *m;
+ int n, rm;
+
+ while(c->rq != nil && c->mq != nil){
+ r = c->rq;
+ c->rq = r->aux;
+
+ rm = 0;
+ m = c->mq;
+ n = r->ifcall.count;
+ if(n >= m->wp - m->rp){
+ n = m->wp - m->rp;
+ c->mq = m->link;
+ rm = 1;
+ }
+ memmove(r->ofcall.data, m->rp, n);
+ if(rm)
+ free(m);
+ else
+ m->rp += n;
+ r->ofcall.count = n;
+ respond(r, nil);
+ adjustwin(c, n);
+ }
+}
+
+void
+queuewreq(Client *c, Req *r)
+{
+ if(c->wq==nil)
+ c->ewq = &c->wq;
+ *c->ewq = r;
+ r->aux = nil;
+ c->ewq = (Req**)&r->aux;
+}
+
+void
+procwreqs(Client *c)
+{
+ Req *r;
+ int n;
+
+ while((r = c->wq) != nil && (n = c->sendwin) > 0){
+ if(n > c->sendpkt)
+ n = c->sendpkt;
+ if(r->ifcall.count > n){
+ senddata(c, r->ifcall.data, n);
+ r->ifcall.count -= n;
+ memmove(r->ifcall.data, (char*)r->ifcall.data + n, r->ifcall.count);
+ continue;
+ }
+ c->wq = (Req*)r->aux;
+ r->aux = nil;
+ senddata(c, r->ifcall.data, r->ifcall.count);
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+ }
+}
+
+Req*
+findreq(Client *c, Req *r)
+{
+ Req **l;
+
+ for(l=&c->rq; *l; l=(Req**)&(*l)->aux){
+ if(*l == r){
+ *l = r->aux;
+ if(*l == nil)
+ c->erq = l;
+ return r;
+ }
+ }
+ for(l=&c->wq; *l; l=(Req**)&(*l)->aux){
+ if(*l == r){
+ *l = r->aux;
+ if(*l == nil)
+ c->ewq = l;
+ return r;
+ }
+ }
+ return nil;
+}
+
+void
+dialedclient(Client *c)
+{
+ Req *r;
+
+ if(r=c->wq){
+ if(r->aux != nil)
+ sysfatal("more than one outstanding dial request (BUG)");
+ if(c->state == Established)
+ respond(r, nil);
+ else
+ respond(r, "connect failed");
+ }
+ c->wq = nil;
+}
+
+void
+teardownclient(Client *c)
+{
+ c->state = Teardown;
+ sendmsg(pack(nil, "bu", MSG_CHANNEL_EOF, c->servernum));
+}
+
+void
+hangupclient(Client *c)
+{
+ Req *r, *next;
+ Msg *m, *mnext;
+
+ c->state = Closed;
+ for(m=c->mq; m; m=mnext){
+ mnext = m->link;
+ free(m);
+ }
+ c->mq = nil;
+ for(r=c->rq; r; r=next){
+ next = r->aux;
+ respond(r, "hangup on network connection");
+ }
+ c->rq = nil;
+ for(r=c->wq; r; r=next){
+ next = r->aux;
+ respond(r, "hangup on network connection");
+ }
+ c->wq = nil;
+}
+
+void
+closeclient(Client *c)
+{
+ Msg *m, *next;
+
+ if(--c->ref)
+ return;
+
+ if(c->rq != nil || c->wq != nil)
+ sysfatal("ref count reached zero with requests pending (BUG)");
+
+ for(m=c->mq; m; m=next){
+ next = m->link;
+ free(m);
+ }
+ c->mq = nil;
+
+ if(c->state != Closed)
+ teardownclient(c);
+}
+
+
+void
+sshreadproc(void*)
+{
+ Msg *m;
+ int n;
+
+ for(;;){
+ m = allocmsg();
+ n = read(sshfd, m->rp, m->ep - m->rp);
+ if(n <= 0)
+ sysfatal("eof on ssh connection");
+ m->wp += n;
+ sendp(sshmsgchan, m);
+ }
+}
+
+typedef struct Tab Tab;
+struct Tab
+{
+ char *name;
+ ulong mode;
+};
+
+Tab tab[] =
+{
+ "/", DMDIR|0555,
+ "cs", 0666,
+ "tcp", DMDIR|0555,
+ "clone", 0666,
+ nil, DMDIR|0555,
+ "ctl", 0666,
+ "data", 0666,
+ "local", 0444,
+ "remote", 0444,
+ "status", 0444,
+};
+
+static void
+fillstat(Dir *d, uvlong path)
+{
+ Tab *t;
+
+ memset(d, 0, sizeof(*d));
+ d->uid = estrdup9p("ssh");
+ d->gid = estrdup9p("ssh");
+ d->qid.path = path;
+ d->atime = d->mtime = time0;
+ t = &tab[TYPE(path)];
+ if(t->name)
+ d->name = estrdup9p(t->name);
+ else{
+ d->name = smprint("%ud", NUM(path));
+ if(d->name == nil)
+ sysfatal("out of memory");
+ }
+ d->qid.type = t->mode>>24;
+ d->mode = t->mode;
+}
+
+static void
+fsattach(Req *r)
+{
+ if(r->ifcall.aname && r->ifcall.aname[0]){
+ respond(r, "invalid attach specifier");
+ return;
+ }
+ r->fid->qid.path = PATH(Qroot, 0);
+ r->fid->qid.type = QTDIR;
+ r->fid->qid.vers = 0;
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+ fillstat(&r->d, r->fid->qid.path);
+ respond(r, nil);
+}
+
+static int
+rootgen(int i, Dir *d, void*)
+{
+ i += Qroot+1;
+ if(i <= Qtcp){
+ fillstat(d, i);
+ return 0;
+ }
+ return -1;
+}
+
+static int
+tcpgen(int i, Dir *d, void*)
+{
+ i += Qtcp+1;
+ if(i < Qn){
+ fillstat(d, i);
+ return 0;
+ }
+ i -= Qn;
+ if(i < nclient){
+ fillstat(d, PATH(Qn, i));
+ return 0;
+ }
+ return -1;
+}
+
+static int
+clientgen(int i, Dir *d, void *aux)
+{
+ Client *c;
+
+ c = aux;
+ i += Qn+1;
+ if(i <= Qstatus){
+ fillstat(d, PATH(i, c->num));
+ return 0;
+ }
+ return -1;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+ int i, n;
+ char buf[32];
+ ulong path;
+
+ path = fid->qid.path;
+ if(!(fid->qid.type&QTDIR))
+ return "walk in non-directory";
+
+ if(strcmp(name, "..") == 0){
+ switch(TYPE(path)){
+ case Qn:
+ qid->path = PATH(Qtcp, NUM(path));
+ qid->type = tab[Qtcp].mode>>24;
+ return nil;
+ case Qtcp:
+ qid->path = PATH(Qroot, 0);
+ qid->type = tab[Qroot].mode>>24;
+ return nil;
+ case Qroot:
+ return nil;
+ default:
+ return "bug in fswalk1";
+ }
+ }
+
+ i = TYPE(path)+1;
+ for(; i<nelem(tab); i++){
+ if(i==Qn){
+ n = atoi(name);
+ snprint(buf, sizeof buf, "%d", n);
+ if(n < nclient && strcmp(buf, name) == 0){
+ qid->path = PATH(i, n);
+ qid->type = tab[i].mode>>24;
+ return nil;
+ }
+ break;
+ }
+ if(strcmp(name, tab[i].name) == 0){
+ qid->path = PATH(i, NUM(path));
+ qid->type = tab[i].mode>>24;
+ return nil;
+ }
+ if(tab[i].mode&DMDIR)
+ break;
+ }
+ return "directory entry not found";
+}
+
+typedef struct Cs Cs;
+struct Cs
+{
+ char *resp;
+ int isnew;
+};
+
+static int
+ndbfindport(char *p)
+{
+ char *s, *port;
+ int n;
+ static Ndb *db;
+
+ if(*p == '\0')
+ return -1;
+
+ n = strtol(p, &s, 0);
+ if(*s == '\0')
+ return n;
+
+ if(db == nil){
+ db = ndbopen("/lib/ndb/common");
+ if(db == nil)
+ return -1;
+ }
+
+ port = ndbgetvalue(db, nil, "tcp", p, "port", nil);
+ if(port == nil)
+ return -1;
+ n = atoi(port);
+ free(port);
+
+ return n;
+}
+
+static void
+csread(Req *r)
+{
+ Cs *cs;
+
+ cs = r->fid->aux;
+ if(cs->resp==nil){
+ respond(r, "cs read without write");
+ return;
+ }
+ if(r->ifcall.offset==0){
+ if(!cs->isnew){
+ r->ofcall.count = 0;
+ respond(r, nil);
+ return;
+ }
+ cs->isnew = 0;
+ }
+ readstr(r, cs->resp);
+ respond(r, nil);
+}
+
+static void
+cswrite(Req *r)
+{
+ int port, nf;
+ char err[ERRMAX], *f[4], *s, *ns;
+ Cs *cs;
+
+ cs = r->fid->aux;
+ s = emalloc9p(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = '\0';
+
+ nf = getfields(s, f, nelem(f), 0, "!");
+ if(nf != 3){
+ free(s);
+ respond(r, "can't translate");
+ return;
+ }
+ if(strcmp(f[0], "tcp") != 0 && strcmp(f[0], "net") != 0){
+ free(s);
+ respond(r, "unknown protocol");
+ return;
+ }
+ port = ndbfindport(f[2]);
+ if(port <= 0){
+ free(s);
+ respond(r, "no translation found");
+ return;
+ }
+
+ ns = smprint("%s/tcp/clone %s!%d", mtpt, f[1], port);
+ if(ns == nil){
+ free(s);
+ rerrstr(err, sizeof err);
+ respond(r, err);
+ return;
+ }
+ free(s);
+ free(cs->resp);
+ cs->resp = ns;
+ cs->isnew = 1;
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+}
+
+static void
+ctlread(Req *r, Client *c)
+{
+ char buf[32];
+
+ sprint(buf, "%d", c->num);
+ readstr(r, buf);
+ respond(r, nil);
+}
+
+static void
+ctlwrite(Req *r, Client *c)
+{
+ char *f[3], *s;
+ int nf;
+
+ s = emalloc9p(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = '\0';
+
+ nf = tokenize(s, f, 3);
+ if(nf == 0){
+ free(s);
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+ return;
+ }
+
+ if(strcmp(f[0], "hangup") == 0){
+ if(c->state != Established)
+ goto Badarg;
+ if(nf != 1)
+ goto Badarg;
+ teardownclient(c);
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+ }else if(strcmp(f[0], "connect") == 0){
+ if(c->state != Closed)
+ goto Badarg;
+ if(nf != 2)
+ goto Badarg;
+ c->connect = estrdup9p(f[1]);
+ nf = getfields(f[1], f, nelem(f), 0, "!");
+ if(nf != 2){
+ free(c->connect);
+ c->connect = nil;
+ goto Badarg;
+ }
+ c->sendwin = MaxPacket;
+ c->recvwin = WinPackets * MaxPacket;
+ c->recvacc = 0;
+ c->state = Dialing;
+ queuewreq(c, r);
+
+ sendmsg(pack(nil, "bsuuususu", MSG_CHANNEL_OPEN,
+ "direct-tcpip", 12,
+ c->num, c->recvwin, MaxPacket,
+ f[0], strlen(f[0]), ndbfindport(f[1]),
+ localip, strlen(localip), localport));
+ }else{
+ Badarg:
+ respond(r, "bad or inappropriate tcp control message");
+ }
+ free(s);
+}
+
+static void
+dataread(Req *r, Client *c)
+{
+ if(c->state != Established){
+ respond(r, "not connected");
+ return;
+ }
+ queuerreq(c, r);
+ matchrmsgs(c);
+}
+
+static void
+datawrite(Req *r, Client *c)
+{
+ if(c->state != Established){
+ respond(r, "not connected");
+ return;
+ }
+ if(r->ifcall.count == 0){
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+ return;
+ }
+ queuewreq(c, r);
+ procwreqs(c);
+}
+
+static void
+localread(Req *r)
+{
+ char buf[128];
+
+ snprint(buf, sizeof buf, "%s!%d\n", localip, localport);
+ readstr(r, buf);
+ respond(r, nil);
+}
+
+static void
+remoteread(Req *r, Client *c)
+{
+ char *s;
+ char buf[128];
+
+ s = c->connect;
+ if(s == nil)
+ s = "::!0";
+ snprint(buf, sizeof buf, "%s\n", s);
+ readstr(r, buf);
+ respond(r, nil);
+}
+
+static void
+statusread(Req *r, Client *c)
+{
+ char *s;
+
+ s = statestr[c->state];
+ readstr(r, s);
+ respond(r, nil);
+}
+
+static void
+fsread(Req *r)
+{
+ char e[ERRMAX];
+ ulong path;
+
+ path = r->fid->qid.path;
+ switch(TYPE(path)){
+ default:
+ snprint(e, sizeof e, "bug in fsread path=%lux", path);
+ respond(r, e);
+ break;
+
+ case Qroot:
+ dirread9p(r, rootgen, nil);
+ respond(r, nil);
+ break;
+
+ case Qcs:
+ csread(r);
+ break;
+
+ case Qtcp:
+ dirread9p(r, tcpgen, nil);
+ respond(r, nil);
+ break;
+
+ case Qn:
+ dirread9p(r, clientgen, client[NUM(path)]);
+ respond(r, nil);
+ break;
+
+ case Qctl:
+ ctlread(r, client[NUM(path)]);
+ break;
+
+ case Qdata:
+ dataread(r, client[NUM(path)]);
+ break;
+
+ case Qlocal:
+ localread(r);
+ break;
+
+ case Qremote:
+ remoteread(r, client[NUM(path)]);
+ break;
+
+ case Qstatus:
+ statusread(r, client[NUM(path)]);
+ break;
+ }
+}
+
+static void
+fswrite(Req *r)
+{
+ ulong path;
+ char e[ERRMAX];
+
+ path = r->fid->qid.path;
+ switch(TYPE(path)){
+ default:
+ snprint(e, sizeof e, "bug in fswrite path=%lux", path);
+ respond(r, e);
+ break;
+
+ case Qcs:
+ cswrite(r);
+ break;
+
+ case Qctl:
+ ctlwrite(r, client[NUM(path)]);
+ break;
+
+ case Qdata:
+ datawrite(r, client[NUM(path)]);
+ break;
+ }
+}
+
+static void
+fsopen(Req *r)
+{
+ static int need[4] = { 4, 2, 6, 1 };
+ ulong path;
+ int n;
+ Tab *t;
+ Cs *cs;
+
+ /*
+ * lib9p already handles the blatantly obvious.
+ * we just have to enforce the permissions we have set.
+ */
+ path = r->fid->qid.path;
+ t = &tab[TYPE(path)];
+ n = need[r->ifcall.mode&3];
+ if((n&t->mode) != n){
+ respond(r, "permission denied");
+ return;
+ }
+
+ switch(TYPE(path)){
+ case Qcs:
+ cs = emalloc9p(sizeof(Cs));
+ r->fid->aux = cs;
+ respond(r, nil);
+ break;
+ case Qclone:
+ n = newclient();
+ path = PATH(Qctl, n);
+ r->fid->qid.path = path;
+ r->ofcall.qid.path = path;
+ if(chatty9p)
+ fprint(2, "open clone => path=%lux\n", path);
+ t = &tab[Qctl];
+ /* fall through */
+ default:
+ if(t-tab >= Qn)
+ client[NUM(path)]->ref++;
+ respond(r, nil);
+ break;
+ }
+}
+
+static void
+fsflush(Req *r)
+{
+ int i;
+
+ for(i=0; i<nclient; i++)
+ if(findreq(client[i], r->oldreq))
+ respond(r->oldreq, "interrupted");
+ respond(r, nil);
+}
+
+static void
+handlemsg(Msg *m)
+{
+ int chan, win, pkt, n;
+ Client *c;
+ char *s;
+
+ switch(m->rp[0]){
+ case MSG_CHANNEL_WINDOW_ADJUST:
+ if(unpack(m, "_uu", &chan, &n) < 0)
+ break;
+ c = getclient(chan);
+ if(c != nil && c->state==Established){
+ c->sendwin += n;
+ procwreqs(c);
+ }
+ break;
+ case MSG_CHANNEL_DATA:
+ if(unpack(m, "_us", &chan, &s, &n) < 0)
+ break;
+ c = getclient(chan);
+ if(c != nil && c->state==Established){
+ c->recvwin -= n;
+ m->rp = (uchar*)s;
+ queuermsg(c, m);
+ matchrmsgs(c);
+ return;
+ }
+ break;
+ case MSG_CHANNEL_EOF:
+ if(unpack(m, "_u", &chan) < 0)
+ break;
+ c = getclient(chan);
+ if(c != nil){
+ hangupclient(c);
+ m->rp = m->wp = m->buf;
+ sendmsg(pack(m, "bu", MSG_CHANNEL_CLOSE, c->servernum));
+ return;
+ }
+ break;
+ case MSG_CHANNEL_CLOSE:
+ if(unpack(m, "_u", &chan) < 0)
+ break;
+ c = getclient(chan);
+ if(c != nil)
+ hangupclient(c);
+ break;
+ case MSG_CHANNEL_OPEN_CONFIRMATION:
+ if(unpack(m, "_uuuu", &chan, &n, &win, &pkt) < 0)
+ break;
+ c = getclient(chan);
+ if(c == nil || c->state != Dialing)
+ break;
+ if(pkt <= 0 || pkt > MaxPacket)
+ pkt = MaxPacket;
+ c->sendpkt = pkt;
+ c->sendwin = win;
+ c->servernum = n;
+ c->state = Established;
+ dialedclient(c);
+ break;
+ case MSG_CHANNEL_OPEN_FAILURE:
+ if(unpack(m, "_uu", &chan, &n) < 0)
+ break;
+ c = getclient(chan);
+ if(c == nil || c->state != Dialing)
+ break;
+ c->servernum = n;
+ c->state = Closed;
+ dialedclient(c);
+ break;
+ }
+ free(m);
+}
+
+void
+fsnetproc(void*)
+{
+ ulong path;
+ Alt a[4];
+ Cs *cs;
+ Fid *fid;
+ Req *r;
+ Msg *m;
+
+ threadsetname("fsthread");
+
+ a[0].op = CHANRCV;
+ a[0].c = fsclunkchan;
+ a[0].v = &fid;
+ a[1].op = CHANRCV;
+ a[1].c = fsreqchan;
+ a[1].v = &r;
+ a[2].op = CHANRCV;
+ a[2].c = sshmsgchan;
+ a[2].v = &m;
+ a[3].op = CHANEND;
+
+ for(;;){
+ switch(alt(a)){
+ case 0:
+ path = fid->qid.path;
+ switch(TYPE(path)){
+ case Qcs:
+ cs = fid->aux;
+ if(cs){
+ free(cs->resp);
+ free(cs);
+ }
+ break;
+ }
+ if(fid->omode != -1 && TYPE(path) >= Qn)
+ closeclient(client[NUM(path)]);
+ sendp(fsclunkwaitchan, nil);
+ break;
+ case 1:
+ switch(r->ifcall.type){
+ case Tattach:
+ fsattach(r);
+ break;
+ case Topen:
+ fsopen(r);
+ break;
+ case Tread:
+ fsread(r);
+ break;
+ case Twrite:
+ fswrite(r);
+ break;
+ case Tstat:
+ fsstat(r);
+ break;
+ case Tflush:
+ fsflush(r);
+ break;
+ default:
+ respond(r, "bug in fsthread");
+ break;
+ }
+ sendp(fsreqwaitchan, 0);
+ break;
+ case 2:
+ handlemsg(m);
+ break;
+ }
+ }
+}
+
+static void
+fssend(Req *r)
+{
+ sendp(fsreqchan, r);
+ recvp(fsreqwaitchan); /* avoids need to deal with spurious flushes */
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+ sendp(fsclunkchan, fid);
+ recvp(fsclunkwaitchan);
+}
+
+void
+takedown(Srv*)
+{
+ threadexitsall("done");
+}
+
+Srv fs =
+{
+.attach= fssend,
+.destroyfid= fsdestroyfid,
+.walk1= fswalk1,
+.open= fssend,
+.read= fssend,
+.write= fssend,
+.stat= fssend,
+.flush= fssend,
+.end= takedown,
+};
+
+int pfd[2];
+int sshargc;
+char **sshargv;
+
+void
+startssh(void *)
+{
+ char *f;
+
+ close(pfd[0]);
+ dup(pfd[1], 0);
+ dup(pfd[1], 1);
+ close(pfd[1]);
+ if(strncmp(sshargv[0], "./", 2) != 0)
+ f = smprint("/bin/%s", sshargv[0]);
+ else
+ f = sshargv[0];
+ procexec(nil, f, sshargv);
+ sysfatal("exec: %r");
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: sshnet [-m mtpt] [ssh options]\n");
+ exits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ char *service;
+
+ fmtinstall('H', encodefmt);
+
+ mtpt = "/net";
+ service = nil;
+ ARGBEGIN{
+ case 'D':
+ chatty9p++;
+ break;
+ case 'm':
+ mtpt = EARGF(usage());
+ break;
+ case 's':
+ service = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc == 0)
+ usage();
+
+ sshargc = argc + 2;
+ sshargv = emalloc9p(sizeof(char *) * (sshargc + 1));
+ sshargv[0] = "ssh";
+ sshargv[1] = "-X";
+ memcpy(sshargv + 2, argv, argc * sizeof(char *));
+
+ pipe(pfd);
+ sshfd = pfd[0];
+ procrfork(startssh, nil, mainstacksize, RFFDG|RFNOTEG|RFNAMEG);
+ close(pfd[1]);
+
+ time0 = time(0);
+ sshmsgchan = chancreate(sizeof(Msg*), 16);
+ fsreqchan = chancreate(sizeof(Req*), 0);
+ fsreqwaitchan = chancreate(sizeof(void*), 0);
+ fsclunkchan = chancreate(sizeof(Fid*), 0);
+ fsclunkwaitchan = chancreate(sizeof(void*), 0);
+
+ procrfork(sshreadproc, nil, mainstacksize, RFNAMEG|RFNOTEG);
+ procrfork(fsnetproc, nil, mainstacksize, RFNAMEG|RFNOTEG);
+
+ threadpostmountsrv(&fs, service, mtpt, MREPL);
+ exits(0);
+}