shithub: riscv

Download patch

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);
+}