diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/ssh/sshnet.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ssh/sshnet.c')
-rwxr-xr-x | sys/src/cmd/ssh/sshnet.c | 1110 |
1 files changed, 1110 insertions, 0 deletions
diff --git a/sys/src/cmd/ssh/sshnet.c b/sys/src/cmd/ssh/sshnet.c new file mode 100755 index 000000000..1de834c72 --- /dev/null +++ b/sys/src/cmd/ssh/sshnet.c @@ -0,0 +1,1110 @@ +/* + * SSH network file system. + * Presents remote TCP stack as /net-style file system. + */ + +#include "ssh.h" +#include <bio.h> +#include <ndb.h> +#include <thread.h> +#include <fcall.h> +#include <9p.h> + +int rawhack = 1; +Conn *conn; +char *remoteip = "<remote>"; +char *mtpt; + +Cipher *allcipher[] = { + &cipherrc4, + &cipherblowfish, + &cipher3des, + &cipherdes, + &ciphernone, + &ciphertwiddle, +}; + +Auth *allauth[] = { + &authpassword, + &authrsa, + &authtis, +}; + +char *cipherlist = "rc4 3des"; +char *authlist = "rsa password tis"; + +Cipher* +findcipher(char *name, Cipher **list, int nlist) +{ + int i; + + for(i=0; i<nlist; i++) + if(strcmp(name, list[i]->name) == 0) + return list[i]; + error("unknown cipher %s", name); + return nil; +} + +Auth* +findauth(char *name, Auth **list, int nlist) +{ + int i; + + for(i=0; i<nlist; i++) + if(strcmp(name, list[i]->name) == 0) + return list[i]; + error("unknown auth %s", name); + return nil; +} + +void +usage(void) +{ + fprint(2, "usage: sshnet [-A authlist] [-c cipherlist] [-m mtpt] [user@]hostname\n"); + exits("usage"); +} + +int +isatty(int fd) +{ + char buf[64]; + + buf[0] = '\0'; + fd2path(fd, buf, sizeof buf); + if(strlen(buf)>=9 && strcmp(buf+strlen(buf)-9, "/dev/cons")==0) + return 1; + return 0; +} + +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", +}; + +typedef struct Client Client; +struct Client +{ + int ref; + int state; + int num; + int servernum; + char *connect; + Req *rq; + Req **erq; + Msg *mq; + Msg **emq; +}; + +int nclient; +Client **client; + +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; +} + +void +queuereq(Client *c, Req *r) +{ + if(c->rq==nil) + c->erq = &c->rq; + *c->erq = r; + r->aux = nil; + c->erq = (Req**)&r->aux; +} + +void +queuemsg(Client *c, Msg *m) +{ + if(c->mq==nil) + c->emq = &c->mq; + *c->emq = m; + m->link = nil; + c->emq = (Msg**)&m->link; +} + +void +matchmsgs(Client *c) +{ + Req *r; + Msg *m; + int n, rm; + + while(c->rq && c->mq){ + r = c->rq; + c->rq = r->aux; + + rm = 0; + m = c->mq; + n = r->ifcall.count; + if(n >= m->ep - m->rp){ + n = m->ep - 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); + } +} + +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; + } + } + return nil; +} + +void +dialedclient(Client *c) +{ + Req *r; + + if(r=c->rq){ + 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->rq = nil; +} + +void +teardownclient(Client *c) +{ + Msg *m; + + c->state = Teardown; + m = allocmsg(conn, SSH_MSG_CHANNEL_INPUT_EOF, 4); + putlong(m, c->servernum); + sendmsg(m); +} + +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; +} + +void +closeclient(Client *c) +{ + Msg *m, *next; + + if(--c->ref) + return; + + if(c->rq != 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 *a) +{ + Conn *c; + Msg *m; + + c = a; + for(;;){ + m = recvmsg(c, -1); + if(m == nil) + sysfatal("eof on ssh connection"); + 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 = emalloc(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; + Msg *m; + + s = emalloc(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); + respond(r, nil); + return; + } + + if(strcmp(f[0], "hangup") == 0){ + if(c->state != Established) + goto Badarg; + if(nf != 1) + goto Badarg; + queuereq(c, r); + teardownclient(c); + }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->state = Dialing; + m = allocmsg(conn, SSH_MSG_PORT_OPEN, 4+4+strlen(f[0])+4+4+strlen("localhost")); + putlong(m, c->num); + putstring(m, f[0]); + putlong(m, ndbfindport(f[1])); + putstring(m, "localhost"); + queuereq(c, r); + sendmsg(m); + }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; + } + queuereq(c, r); + matchmsgs(c); +} + +static void +datawrite(Req *r, Client *c) +{ + Msg *m; + + if(c->state != Established){ + respond(r, "not connected"); + return; + } + if(r->ifcall.count){ + m = allocmsg(conn, SSH_MSG_CHANNEL_DATA, 4+4+r->ifcall.count); + putlong(m, c->servernum); + putlong(m, r->ifcall.count); + putbytes(m, r->ifcall.data, r->ifcall.count); + sendmsg(m); + } + r->ofcall.count = r->ifcall.count; + respond(r, nil); +} + +static void +localread(Req *r) +{ + char buf[128]; + + snprint(buf, sizeof buf, "%s!%d\n", remoteip, 0); + 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 buf[64]; + char *s; + + snprint(buf, sizeof buf, "%s!%d", remoteip, 0); + 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 = emalloc(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, n; + Client *c; + + switch(m->type){ + case SSH_MSG_DISCONNECT: + case SSH_CMSG_EXIT_CONFIRMATION: + sysfatal("disconnect"); + + case SSH_CMSG_STDIN_DATA: + case SSH_CMSG_EOF: + case SSH_CMSG_WINDOW_SIZE: + /* don't care */ + free(m); + break; + + case SSH_MSG_CHANNEL_DATA: + chan = getlong(m); + n = getlong(m); + if(m->rp+n != m->ep) + sysfatal("got bad channel data"); + if(chan<nclient && (c=client[chan])->state==Established){ + queuemsg(c, m); + matchmsgs(c); + }else + free(m); + break; + + case SSH_MSG_CHANNEL_INPUT_EOF: + chan = getlong(m); + free(m); + if(chan<nclient){ + c = client[chan]; + chan = c->servernum; + hangupclient(c); + m = allocmsg(conn, SSH_MSG_CHANNEL_OUTPUT_CLOSED, 4); + putlong(m, chan); + sendmsg(m); + } + break; + + case SSH_MSG_CHANNEL_OUTPUT_CLOSED: + chan = getlong(m); + if(chan<nclient) + hangupclient(client[chan]); + free(m); + break; + + case SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + chan = getlong(m); + c = nil; + if(chan>=nclient || (c=client[chan])->state != Dialing){ + if(c) + fprint(2, "cstate %d\n", c->state); + sysfatal("got unexpected open confirmation for %d", chan); + } + c->servernum = getlong(m); + c->state = Established; + dialedclient(c); + free(m); + break; + + case SSH_MSG_CHANNEL_OPEN_FAILURE: + chan = getlong(m); + c = nil; + if(chan>=nclient || (c=client[chan])->state != Dialing) + sysfatal("got unexpected open failure"); + if(m->rp+4 <= m->ep) + c->servernum = getlong(m); + c->state = Closed; + dialedclient(c); + free(m); + break; + } +} + +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, +}; + +void +threadmain(int argc, char **argv) +{ + int i, fd; + char *host, *user, *p, *service; + char *f[16]; + Msg *m; + static Conn c; + + fmtinstall('B', mpfmt); + fmtinstall('H', encodefmt); + + mtpt = "/net"; + service = nil; + user = nil; + ARGBEGIN{ + case 'B': /* undocumented, debugging */ + doabort = 1; + break; + case 'D': /* undocumented, debugging */ + debuglevel = strtol(EARGF(usage()), nil, 0); + break; + case '9': /* undocumented, debugging */ + chatty9p++; + break; + + case 'A': + authlist = EARGF(usage()); + break; + case 'c': + cipherlist = EARGF(usage()); + break; + case 'm': + mtpt = EARGF(usage()); + break; + case 's': + service = EARGF(usage()); + break; + default: + usage(); + }ARGEND + + if(argc != 1) + usage(); + + host = argv[0]; + + if((p = strchr(host, '@')) != nil){ + *p++ = '\0'; + user = host; + host = p; + } + if(user == nil) + user = getenv("user"); + if(user == nil) + sysfatal("cannot find user name"); + + privatefactotum(); + + if((fd = dial(netmkaddr(host, "tcp", "ssh"), nil, nil, nil)) < 0) + sysfatal("dialing %s: %r", host); + + c.interactive = isatty(0); + c.fd[0] = c.fd[1] = fd; + c.user = user; + c.host = host; + setaliases(&c, host); + + c.nokcipher = getfields(cipherlist, f, nelem(f), 1, ", "); + c.okcipher = emalloc(sizeof(Cipher*)*c.nokcipher); + for(i=0; i<c.nokcipher; i++) + c.okcipher[i] = findcipher(f[i], allcipher, nelem(allcipher)); + + c.nokauth = getfields(authlist, f, nelem(f), 1, ", "); + c.okauth = emalloc(sizeof(Auth*)*c.nokauth); + for(i=0; i<c.nokauth; i++) + c.okauth[i] = findauth(f[i], allauth, nelem(allauth)); + + sshclienthandshake(&c); + + requestpty(&c); /* turns on TCP_NODELAY on other side */ + m = allocmsg(&c, SSH_CMSG_EXEC_SHELL, 0); + sendmsg(m); + + 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); + + conn = &c; + procrfork(sshreadproc, &c, 8192, RFNAMEG|RFNOTEG); + procrfork(fsnetproc, nil, 8192, RFNAMEG|RFNOTEG); + + threadpostmountsrv(&fs, service, mtpt, MREPL); + exits(0); +} + |