/* * /net/ssh */ #include #include #include #include #include <9p.h> #include #include #include #include #include #include "netssh.h" extern int nokeyverify; void stclunk(Fid *); void stend(Srv *); void stflush(Req *); void stopen(Req *); void stread(Req *); void stwrite(Req *); Srv netsshsrv = { .open = stopen, .read = stread, .write = stwrite, .flush = stflush, .destroyfid = stclunk, .end = stend, }; Cipher *cryptos[] = { &cipheraes128, &cipheraes192, &cipheraes256, // &cipherblowfish, &cipher3des, &cipherrc4, }; Kex *kexes[] = { &dh1sha1, &dh14sha1, }; PKA *pkas[3]; char *macnames[] = { "hmac-sha1", }; char *st_names[] = { [Empty] "Empty", [Allocated] "Allocated", [Initting] "Initting", [Listening] "Listening", [Opening] "Opening", [Negotiating] "Negotiating", [Authing] "Authing", [Established] "Established", [Eof] "Eof", [Closing] "Closing", [Closed] "Closed", }; int debug; int kflag; char *mntpt = "/net"; char uid[32]; Conn *connections[MAXCONN]; File *rootfile, *clonefile, *ctlfile, *keysfile; Ioproc *io9p; MBox keymbox; QLock availlck; Rendez availrend; SSHChan *alloc_chan(Conn *); Conn *alloc_conn(void); int auth_req(Packet *, Conn *); int client_auth(Conn *, Ioproc *); int dohandshake(Conn *, char *); char *factlookup(int, int, char *[]); void filedup(Req *, File *); void readdata(void *); void reader(void *); void readreqrem(void *); void send_kexinit(Conn *); void server(char *, char *); void shutdown(Conn *); void stlisconn(void *); void stlischan(void *); int validatekex(Conn *, Packet *); int validatekexc(Packet *); int validatekexs(Packet *); void writectlproc(void *); void writedataproc(void *); void writereqremproc(void *); static int deferredinit(Conn *c); static void sshlogint(Conn *c, char *file, char *p) { char *role, *id; if (c == nil) role = ""; else if (c->role == Server) role = "server "; else role = "client "; if (c == nil) id = strdup(""); else if (c->user || c->remote) id = smprint("user %s@%s id %d ", c->user, c->remote, c->id); else id = smprint("id %d ", c->id); syslog(0, file, "%s: %s%s%s", argv0, role, id, p); free(id); } void sshlog(Conn *c, char *fmt, ...) { va_list args; char *p; /* do this first in case fmt contains "%r" */ va_start(args, fmt); p = vsmprint(fmt, args); va_end(args); sshlogint(c, "ssh", p); sshlogint(c, "sshdebug", p); /* log in both places */ free(p); } void sshdebug(Conn *c, char *fmt, ...) { va_list args; char *p; if (!debug) return; /* do this first in case fmt contains "%r" */ va_start(args, fmt); p = vsmprint(fmt, args); va_end(args); sshlogint(c, "sshdebug", p); free(p); } void usage(void) { fprint(2, "usage: %s [-dkv] [-m mntpt] [-s srvpt]\n", argv0); exits("usage"); } void threadmain(int argc, char *argv[]) { char *p, *srvpt = nil; quotefmtinstall(); threadsetname("main"); nokeyverify = 1; /* temporary until verification is fixed */ ARGBEGIN { case '9': chatty9p = 1; break; case 'd': debug++; break; case 'k': kflag = 1; break; case 'm': mntpt = EARGF(usage()); break; case 's': srvpt = EARGF(usage()); break; case 'v': nokeyverify = 1; break; case 'V': nokeyverify = 0; break; default: usage(); break; } ARGEND; p = getenv("nosshkeyverify"); if (p && p[0] != '\0') nokeyverify = 1; free(p); if (readfile("/dev/user", uid, sizeof uid) <= 0) strcpy(uid, "none"); keymbox.mchan = chancreate(4, 0); availrend.l = &availlck; dh_init(pkas); /* become a daemon */ if (rfork(RFNOTEG) < 0) fprint(2, "%s: rfork(NOTEG) failed: %r\n", argv0); server(mntpt, srvpt); threadexits(nil); } int readio(Ioproc *io, int fd, void *buf, int n) { if (io) return ioread(io, fd, buf, n); else return read(fd, buf, n); } int writeio(Ioproc *io, int fd, void *buf, int n) { if (io) return iowrite(io, fd, buf, n); else return write(fd, buf, n); } int read9pmsg(int fd, void *abuf, uint n) { int m, len; uchar *buf; if (io9p == nil) io9p = ioproc(); buf = abuf; /* read count */ m = ioreadn(io9p, fd, buf, BIT32SZ); if(m != BIT32SZ){ if(m < 0) return -1; return 0; } len = GBIT32(buf); if(len <= BIT32SZ || len > n){ werrstr("bad length in 9P2000 message header"); return -1; } len -= BIT32SZ; m = ioreadn(io9p, fd, buf+BIT32SZ, len); if(m < len) return 0; return BIT32SZ+m; } void stend(Srv *) { closeioproc(io9p); threadkillgrp(threadgetgrp()); } void server(char *mntpt, char *srvpt) { Dir d; char *p; int fd; netsshsrv.tree = alloctree(uid, uid, 0777, nil); rootfile = createfile(netsshsrv.tree->root, "ssh", uid, 0555|DMDIR, (void*)Qroot); clonefile = createfile(rootfile, "clone", uid, 0666, (void*)Qclone); ctlfile = createfile(rootfile, "ctl", uid, 0666, (void*)Qctl); keysfile = createfile(rootfile, "keys", uid, 0600, (void *)Qreqrem); /* * needs to be MBEFORE in case there are previous, now defunct, * netssh processes mounted in mntpt. */ threadpostmountsrv(&netsshsrv, srvpt, mntpt, MBEFORE); p = esmprint("%s/cs", mntpt); fd = open(p, OWRITE); free(p); if (fd >= 0) { fprint(fd, "add ssh"); close(fd); } if (srvpt) { nulldir(&d); d.mode = 0666; p = esmprint("/srv/%s", srvpt); dirwstat(p, &d); free(p); } sshdebug(nil, "server started for %s", getuser()); } static void respexit(Conn *c, Req *r, void *freeme, char *msg) { if (msg) sshdebug(c, "%s", msg); r->aux = 0; respond(r, msg); free(freeme); threadexits(nil); /* maybe use msg here */ } void stopen(Req *r) { int lev, xconn, fd; uvlong qidpath; char *p; char buf[32]; Conn *c; SSHChan *sc; qidpath = (uvlong)r->fid->file->aux; lev = qidpath >> Levshift; switch ((ulong)(qidpath & Qtypemask)) { default: respond(r, nil); break; case Qlisten: r->aux = (void *)threadcreate((lev == Connection? stlisconn: stlischan), r, Defstk); break; case Qclone: switch (lev) { case Top: /* should use dial(2) instead of diddling /net/tcp */ p = esmprint("%s/tcp/clone", mntpt); fd = open(p, ORDWR); if (fd < 0) { sshdebug(nil, "stopen: open %s failed: %r", p); free(p); responderror(r); return; } free(p); c = alloc_conn(); if (c == nil) { close(fd); respond(r, "no more connections"); return; } c->ctlfd = fd; c->poisoned = 0; filedup(r, c->ctlfile); sshlog(c, "new connection on fd %d", fd); break; case Connection: xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { respond(r, "bad connection"); return; } sc = alloc_chan(c); if (sc == nil) { respond(r, "no more channels"); return; } filedup(r, sc->ctl); break; default: snprint(buf, sizeof buf, "bad level %d", lev); readstr(r, buf); break; } respond(r, nil); break; } } static void listerrexit(Req *r, Ioproc *io, Conn *cl) { r->aux = 0; responderror(r); closeioproc(io); shutdown(cl); threadexits(nil); } void stlisconn(void *a) { int xconn, fd, n; uvlong qidpath; char *msg; char buf[Numbsz], path[NETPATHLEN]; Conn *c, *cl; Ioproc *io; Req *r; threadsetname("stlisconn"); r = a; qidpath = (uvlong)r->fid->file->aux; xconn = (qidpath >> Connshift) & Connmask; cl = connections[xconn]; if (cl == nil) { sshlog(cl, "bad connection"); respond(r, "bad connection"); threadexits("bad connection"); } if (cl->poisoned) { sshdebug(cl, "stlisconn conn %d poisoned", xconn); r->aux = 0; respond(r, "top level listen conn poisoned"); threadexits("top level listen conn poisoned"); } if (cl->ctlfd < 0) { sshdebug(cl, "stlisconn conn %d ctlfd < 0; poisoned", xconn); r->aux = 0; respond(r, "top level listen with closed fd"); shutdown(cl); cl->poisoned = 1; /* no more use until ctlfd is set */ threadexits("top level listen with closed fd"); } io = ioproc(); /* read xconn's tcp conn's ctl file */ seek(cl->ctlfd, 0, 0); n = ioread(io, cl->ctlfd, buf, sizeof buf - 1); if (n == 0) { sshlog(cl, "stlisconn read eof on fd %d", cl->ctlfd); listerrexit(r, io, cl); } else if (n < 0) { sshlog(cl, "stlisconn read failed on fd %d: %r", cl->ctlfd); listerrexit(r, io, cl); } buf[n] = '\0'; cl->state = Listening; /* should use dial(2) instead of diddling /net/tcp */ snprint(path, sizeof path, "%s/tcp/%s/listen", mntpt, buf); for(;;) { fd = ioopen(io, path, ORDWR); if (fd < 0) listerrexit(r, io, cl); c = alloc_conn(); if (c) break; n = ioread(io, fd, buf, sizeof buf - 1); if (n <= 0) listerrexit(r, io, cl); buf[n] = '\0'; msg = smprint("reject %s no available connections", buf); iowrite(io, fd, msg, strlen(msg)); free(msg); close(fd); /* surely ioclose? */ } c->ctlfd = fd; if (c->ctlfd < 0) { sshlog(cl, "stlisconn c->ctlfd < 0 for conn %d", xconn); threadexitsall("stlisconn c->ctlfd < 0"); } c->poisoned = 0; c->stifle = 1; /* defer server; was for coexistence */ filedup(r, c->ctlfile); sshdebug(c, "responding to listen open"); r->aux = 0; respond(r, nil); closeioproc(io); threadexits(nil); } void stlischan(void *a) { Req *r; Packet *p2; Ioproc *io; Conn *c; SSHChan *sc; int i, n, xconn; uvlong qidpath; threadsetname("stlischan"); r = a; qidpath = (uvlong)r->fid->file->aux; xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { respond(r, "bad channel"); sshlog(c, "bad channel"); threadexits(nil); } if (c->state == Closed || c->state == Closing) respexit(c, r, nil, "channel listen on closed connection"); sc = c->chans[qidpath & Chanmask]; qlock(&c->l); sc->lreq = r; for (i = 0; i < c->nchan; ++i) if (c->chans[i] && c->chans[i]->state == Opening && c->chans[i]->ann && strcmp(c->chans[i]->ann, sc->ann) == 0) break; if (i >= c->nchan) { sc->state = Listening; rsleep(&sc->r); i = sc->waker; if (i < 0) { qunlock(&c->l); r->aux = 0; responderror(r); threadexits(nil); } } else rwakeup(&c->chans[i]->r); qunlock(&c->l); if (c->state == Closed || c->state == Closing || c->state == Eof) respexit(c, r, nil, "channel listen on closed connection"); c->chans[i]->state = Established; p2 = new_packet(c); c->chans[i]->rwindow = Maxpayload; add_byte(p2, SSH_MSG_CHANNEL_OPEN_CONFIRMATION); hnputl(p2->payload + 1, c->chans[i]->otherid); hnputl(p2->payload + 5, c->chans[i]->id); hnputl(p2->payload + 9, Maxpayload); hnputl(p2->payload + 13, Maxrpcbuf); p2->rlength = 18; n = finish_packet(p2); filedup(r, c->chans[i]->ctl); io = ioproc(); n = iowrite(io, c->datafd, p2->nlength, n); closeioproc(io); free(p2); sshdebug(c, "responding to chan listen open"); r->aux = 0; if (n < 0) responderror(r); else respond(r, nil); threadexits(nil); } void getdata(Conn *c, SSHChan *sc, Req *r) { Packet *p; Plist *d; int n; n = r->ifcall.count; if (sc->dataq->rem < n) n = sc->dataq->rem; if (n > Maxrpcbuf) n = Maxrpcbuf; r->ifcall.offset = 0; readbuf(r, sc->dataq->st, n); sc->dataq->st += n; sc->dataq->rem -= n; sc->inrqueue -= n; if (sc->dataq->rem <= 0) { d = sc->dataq; sc->dataq = sc->dataq->next; if (d->pack->tlength > sc->rwindow) sc->rwindow = 0; else sc->rwindow -= d->pack->tlength; free(d->pack); free(d); } if (sc->rwindow < 16*1024) { /* magic. half-way, maybe? */ sc->rwindow += Maxpayload; sshdebug(c, "increasing receive window to %lud, inq %lud\n", argv0, sc->rwindow, sc->inrqueue); p = new_packet(c); add_byte(p, SSH_MSG_CHANNEL_WINDOW_ADJUST); hnputl(p->payload+1, sc->otherid); hnputl(p->payload+5, Maxpayload); p->rlength += 8; n = finish_packet(p); iowrite(c->dio, c->datafd, p->nlength, n); free(p); } r->aux = 0; respond(r, nil); } void stread(Req *r) { Conn *c; SSHChan *sc; int n, lev, cnum, xconn; uvlong qidpath; char buf[Arbbufsz], path[NETPATHLEN]; threadsetname("stread"); qidpath = (uvlong)r->fid->file->aux; lev = qidpath >> Levshift; xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { if (lev != Top || (qidpath & Qtypemask) != Qreqrem) { respond(r, "Invalid connection"); return; } cnum = 0; sc = nil; } else { cnum = qidpath & Chanmask; sc = c->chans[cnum]; } switch ((ulong)(qidpath & Qtypemask)) { case Qctl: case Qlisten: if (r->ifcall.offset != 0) { respond(r, nil); break; } switch (lev) { case Top: readstr(r, st_names[c->state]); break; case Connection: case Subchannel: snprint(buf, sizeof buf, "%d", lev == Connection? xconn: cnum); readstr(r, buf); break; default: snprint(buf, sizeof buf, "stread error, level %d", lev); respond(r, buf); return; } respond(r, nil); break; case Qclone: if (r->ifcall.offset != 0) { respond(r, nil); break; } readstr(r, "Congratulations, you've achieved the impossible\n"); respond(r, nil); break; case Qdata: if (lev == Top) { respond(r, nil); break; } if (lev == Connection) { if (0 && c->stifle) { /* was for coexistence */ c->stifle = 0; if (deferredinit(c) < 0) { respond(r, "deferredinit failed"); break; } } if (c->cap) /* auth capability? */ readstr(r, c->cap); respond(r, nil); break; } r->aux = (void *)threadcreate(readdata, r, Defstk); break; case Qlocal: if (lev == Connection) if (c->ctlfd < 0) readstr(r, "::!0\n"); else { n = pread(c->ctlfd, buf, 10, 0); // magic 10 buf[n >= 0? n: 0] = '\0'; snprint(path, sizeof path, "%s/tcp/%s/local", mntpt, buf); readfile(path, buf, sizeof buf); readstr(r, buf); } respond(r, nil); break; case Qreqrem: r->aux = (void *)threadcreate(readreqrem, r, Defstk); break; case Qstatus: switch (lev) { case Top: readstr(r, "Impossible"); break; case Connection: readstr(r, (uint)c->state > Closed? "Unknown": st_names[c->state]); break; case Subchannel: readstr(r, (uint)sc->state > Closed? "Unknown": st_names[sc->state]); break; } respond(r, nil); break; case Qtcp: /* connection number of underlying tcp connection */ if (lev == Connection) if (c->ctlfd < 0) readstr(r, "-1\n"); else { n = pread(c->ctlfd, buf, 10, 0); /* magic 10 */ buf[n >= 0? n: 0] = '\0'; readstr(r, buf); } respond(r, nil); break; default: respond(r, nil); break; } } void readreqrem(void *a) { Ioproc *io; Req *r; Conn *c; SSHChan *sc; int fd, n, lev, cnum, xconn; uvlong qidpath; char buf[Arbbufsz], path[NETPATHLEN]; threadsetname("readreqrem"); r = a; qidpath = (uvlong)r->fid->file->aux; lev = qidpath >> Levshift; xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { if (lev != Top) { respond(r, "Invalid connection"); return; } sc = nil; } else { cnum = qidpath & Chanmask; sc = c->chans[cnum]; } switch (lev) { case Top: if (r->ifcall.offset == 0 && keymbox.state != Empty) { r->aux = 0; respond(r, "Key file collision"); /* WTF? */ break; } if (r->ifcall.offset != 0) { readstr(r, keymbox.msg); r->aux = 0; respond(r, nil); if (r->ifcall.offset + r->ifcall.count >= strlen(keymbox.msg)) keymbox.state = Empty; else keymbox.state = Allocated; break; } keymbox.state = Allocated; for(;;) { if (keymbox.msg == nil) if (recv(keymbox.mchan, nil) < 0) { r->aux = 0; responderror(r); keymbox.state = Empty; threadexits(nil); } if (keymbox.state == Empty) break; else if (keymbox.state == Allocated) { if (keymbox.msg) { readstr(r, keymbox.msg); if (r->ifcall.offset + r->ifcall.count >= strlen(keymbox.msg)) { free(keymbox.msg); keymbox.msg = nil; keymbox.state = Empty; } } break; } } r->aux = 0; respond(r, nil); break; case Connection: if (c->ctlfd >= 0) { io = ioproc(); seek(c->ctlfd, 0, 0); n = ioread(io, c->ctlfd, buf, 10); /* magic 10 */ if (n < 0) { r->aux = 0; responderror(r); closeioproc(io); break; } buf[n] = '\0'; snprint(path, NETPATHLEN, "%s/tcp/%s/remote", mntpt, buf); if ((fd = ioopen(io, path, OREAD)) < 0 || (n = ioread(io, fd, buf, Arbbufsz - 1)) < 0) { r->aux = 0; responderror(r); if (fd >= 0) ioclose(io, fd); closeioproc(io); break; } ioclose(io, fd); closeioproc(io); buf[n] = '\0'; readstr(r, buf); } else readstr(r, "::!0\n"); r->aux = 0; respond(r, nil); break; case Subchannel: if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->reqq == nil && sc->dataq == nil) { sshdebug(c, "sending EOF1 to channel request listener"); r->aux = 0; respond(r, nil); break; } while (sc->reqq == nil) { if (recv(sc->reqchan, nil) < 0) { r->aux = 0; responderror(r); threadexits(nil); } if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->reqq == nil && sc->dataq == nil) { sshdebug(c, "sending EOF2 to channel request " "listener"); respexit(c, r, nil, nil); } } n = r->ifcall.count; if (sc->reqq->rem < n) n = sc->reqq->rem; if (n > Maxrpcbuf) n = Maxrpcbuf; r->ifcall.offset = 0; readbuf(r, sc->reqq->st, n); sc->reqq->st += n; sc->reqq->rem -= n; if (sc->reqq->rem <= 0) { Plist *d = sc->reqq; sc->reqq = sc->reqq->next; free(d->pack); free(d); } r->aux = 0; respond(r, nil); break; } threadexits(nil); } void readdata(void *a) { Req *r; Conn *c; SSHChan *sc; int cnum, xconn; uvlong qidpath; threadsetname("readdata"); r = a; qidpath = (uvlong)r->fid->file->aux; xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { respond(r, "bad connection"); sshlog(c, "bad connection"); threadexits(nil); } cnum = qidpath & Chanmask; sc = c->chans[cnum]; if (sc->dataq == nil && (sc->state == Closed || sc->state == Closing || sc->state == Eof)) { sshdebug(c, "sending EOF1 to channel listener"); r->aux = 0; respond(r, nil); threadexits(nil); } if (sc->dataq != nil) { getdata(c, sc, r); threadexits(nil); } while (sc->dataq == nil) { if (recv(sc->inchan, nil) < 0) { sshdebug(c, "got interrupt/error in readdata %r"); r->aux = 0; responderror(r); threadexits(nil); } if (sc->dataq == nil && (sc->state == Closed || sc->state == Closing || sc->state == Eof)) { sshdebug(c, "sending EOF2 to channel listener"); r->aux = 0; respond(r, nil); threadexits(nil); } } getdata(c, sc, r); threadexits(nil); } void stwrite(Req *r) { Conn *c; SSHChan *ch; int lev, xconn; uvlong qidpath; threadsetname("stwrite"); qidpath = (uvlong)r->fid->file->aux; lev = qidpath >> Levshift; xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { respond(r, "invalid connection"); return; } ch = c->chans[qidpath & Chanmask]; switch ((ulong)(qidpath & Qtypemask)) { case Qclone: case Qctl: r->aux = (void *)threadcreate(writectlproc, r, Defstk); break; case Qdata: r->ofcall.count = r->ifcall.count; if (lev == Top || lev == Connection || c->state == Closed || c->state == Closing || ch->state == Closed || ch->state == Closing) { respond(r, nil); break; } if (0 && c->stifle) { /* was for coexistence */ c->stifle = 0; if (deferredinit(c) < 0) { respond(r, "deferredinit failed"); break; } } r->aux = (void *)threadcreate(writedataproc, r, Defstk); break; case Qreqrem: r->aux = (void *)threadcreate(writereqremproc, r, Defstk); break; default: respond(r, nil); break; } } static int dialbyhand(Conn *c, int ntok, char *toks[]) { /* * this uses /net/tcp to connect directly. * should use dial(2) instead of doing it by hand. */ sshdebug(c, "tcp connect %s %s", toks[1], ntok > 3? toks[2]: ""); return fprint(c->ctlfd, "connect %s %s", toks[1], ntok > 3? toks[2]: ""); } static void userauth(Conn *c, Req *r, char *buf, int ntok, char *toks[]) { int n; char *attrs[5]; Packet *p; if (ntok < 3 || ntok > 4) respexit(c, r, buf, "bad connect command"); if (!c->service) c->service = estrdup9p(toks[0]); if (c->user) free(c->user); c->user = estrdup9p(toks[2]); sshdebug(c, "userauth for user %s", c->user); if (ntok == 4 && strcmp(toks[1], "k") == 0) { if (c->authkey) { free(c->authkey); c->authkey = nil; } if (c->password) free(c->password); c->password = estrdup9p(toks[3]); sshdebug(c, "userauth got password"); } else { if (c->password) { free(c->password); c->password = nil; } memset(attrs, 0, sizeof attrs); attrs[0] = "proto=rsa"; attrs[1] = "!dk?"; attrs[2] = smprint("user=%s", c->user); attrs[3] = smprint("sys=%s", c->remote); if (c->authkey) free(c->authkey); sshdebug(c, "userauth trying rsa"); if (ntok == 3) c->authkey = factlookup(4, 2, attrs); else { attrs[4] = toks[3]; c->authkey = factlookup(5, 2, attrs); } free(attrs[2]); free(attrs[3]); } if (!c->password && !c->authkey) respexit(c, r, buf, "no auth info"); else if (c->state != Authing) { p = new_packet(c); add_byte(p, SSH_MSG_SERVICE_REQUEST); add_string(p, c->service); n = finish_packet(p); sshdebug(c, "sending msg svc req for %s", c->service); if (writeio(c->dio, c->datafd, p->nlength, n) != n) { sshdebug(c, "authing write failed: %r"); free(p); r->aux = 0; responderror(r); free(buf); threadexits(nil); } free(p); } else if (client_auth(c, c->dio) < 0) respexit(c, r, buf, "ssh-userauth client auth failed"); qlock(&c->l); if (c->state != Established) { sshdebug(c, "sleeping for auth"); rsleep(&c->r); } qunlock(&c->l); if (c->state != Established) respexit(c, r, buf, "ssh-userath auth failed (not Established)"); } void writectlproc(void *a) { Req *r; Packet *p; Conn *c; SSHChan *ch; char *tcpconn2, *buf, *toks[4]; int n, ntok, lev, xconn; uvlong qidpath; char path[NETPATHLEN], tcpconn[Numbsz]; threadsetname("writectlproc"); r = a; qidpath = (uvlong)r->fid->file->aux; lev = qidpath >> Levshift; xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { respond(r, "bad connection"); sshlog(c, "bad connection"); threadexits(nil); } ch = c->chans[qidpath & Chanmask]; if (r->ifcall.count <= Numbsz) buf = emalloc9p(Numbsz + 1); else buf = emalloc9p(r->ifcall.count + 1); memmove(buf, r->ifcall.data, r->ifcall.count); buf[r->ifcall.count] = '\0'; sshdebug(c, "level %d writectl: %s", lev, buf); ntok = tokenize(buf, toks, nelem(toks)); switch (lev) { case Connection: if (strcmp(toks[0], "id") == 0) { /* was for sshswitch */ if (ntok < 2) respexit(c, r, buf, "bad id request"); strncpy(c->idstring, toks[1], sizeof c->idstring); sshdebug(c, "id %s", toks[1]); break; } if (strcmp(toks[0], "connect") == 0) { if (ntok < 2) respexit(c, r, buf, "bad connect request"); /* * should use dial(2) instead of doing it by hand. */ memset(tcpconn, '\0', sizeof(tcpconn)); pread(c->ctlfd, tcpconn, sizeof tcpconn, 0); dialbyhand(c, ntok, toks); c->role = Client; /* Override the PKA list; we can take any in */ pkas[0] = &rsa_pka; pkas[1] = &dss_pka; pkas[2] = nil; tcpconn2 = estrdup9p(tcpconn); /* swap id strings, negotiate crypto */ if (dohandshake(c, tcpconn2) < 0) { sshlog(c, "connect handshake failed: " "tcp conn %s", tcpconn2); free(tcpconn2); respexit(c, r, buf, "connect handshake failed"); } free(tcpconn2); keymbox.state = Empty; nbsendul(keymbox.mchan, 1); break; } if (c->state == Closed || c->state == Closing) respexit(c, r, buf, "connection closed"); if (strcmp(toks[0], "ssh-userauth") == 0) userauth(c, r, buf, ntok, toks); else if (strcmp(toks[0], "ssh-connection") == 0) { /* your ad here */ } else if (strcmp(toks[0], "hangup") == 0) { if (c->rpid >= 0) threadint(c->rpid); shutdown(c); } else if (strcmp(toks[0], "announce") == 0) { sshdebug(c, "got %s argument for announce", toks[1]); write(c->ctlfd, r->ifcall.data, r->ifcall.count); } else if (strcmp(toks[0], "accept") == 0) { /* should use dial(2) instead of diddling /net/tcp */ memset(tcpconn, '\0', sizeof(tcpconn)); pread(c->ctlfd, tcpconn, sizeof tcpconn, 0); fprint(c->ctlfd, "accept %s", tcpconn); c->role = Server; tcpconn2 = estrdup9p(tcpconn); /* swap id strings, negotiate crypto */ if (dohandshake(c, tcpconn2) < 0) { sshlog(c, "accept handshake failed: " "tcp conn %s", tcpconn2); free(tcpconn2); shutdown(c); respexit(c, r, buf, "accept handshake failed"); } free(tcpconn2); } else if (strcmp(toks[0], "reject") == 0) { memset(tcpconn, '\0', sizeof(tcpconn)); pread(c->ctlfd, tcpconn, sizeof tcpconn, 0); snprint(path, NETPATHLEN, "%s/tcp/%s/data", mntpt, tcpconn); c->datafd = open(path, ORDWR); p = new_packet(c); add_byte(p, SSH_MSG_DISCONNECT); add_byte(p, SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT); add_string(p, toks[2]); add_string(p, "EN"); n = finish_packet(p); if (c->dio && c->datafd >= 0) iowrite(c->dio, c->datafd, p->nlength, n); free(p); if (c->ctlfd >= 0) fprint(c->ctlfd, "reject %s %s", buf, toks[2]); if (c->rpid >= 0) threadint(c->rpid); shutdown(c); } break; case Subchannel: if (c->state == Closed || c->state == Closing) respexit(c, r, buf, "channel closed"); if (strcmp(toks[0], "connect") == 0) { p = new_packet(c); add_byte(p, SSH_MSG_CHANNEL_OPEN); sshdebug(c, "chan writectl: connect %s", ntok > 1? toks[1]: "session"); add_string(p, ntok > 1? toks[1]: "session"); add_uint32(p, ch->id); add_uint32(p, Maxpayload); add_uint32(p, Maxrpcbuf); /* more stuff if it's an x11 session */ n = finish_packet(p); iowrite(c->dio, c->datafd, p->nlength, n); free(p); qlock(&c->l); if (ch->otherid == -1) rsleep(&ch->r); qunlock(&c->l); } else if (strcmp(toks[0], "global") == 0) { /* your ad here */ } else if (strcmp(toks[0], "hangup") == 0) { if (ch->state != Closed && ch->state != Closing) { ch->state = Closing; if (ch->otherid != -1) { p = new_packet(c); add_byte(p, SSH_MSG_CHANNEL_CLOSE); add_uint32(p, ch->otherid); n = finish_packet(p); iowrite(c->dio, c->datafd, p->nlength, n); free(p); } qlock(&c->l); rwakeup(&ch->r); qunlock(&c->l); nbsendul(ch->inchan, 1); nbsendul(ch->reqchan, 1); } for (n = 0; n < MAXCONN && (c->chans[n] == nil || c->chans[n]->state == Empty || c->chans[n]->state == Closing || c->chans[n]->state == Closed); ++n) ; if (n >= MAXCONN) { if (c->rpid >= 0) threadint(c->rpid); shutdown(c); } } else if (strcmp(toks[0], "announce") == 0) { sshdebug(c, "got argument `%s' for chan announce", toks[1]); free(ch->ann); ch->ann = estrdup9p(toks[1]); } break; } r->ofcall.count = r->ifcall.count; r->aux = 0; respond(r, nil); free(buf); threadexits(nil); } void writereqremproc(void *a) { Req *r; Packet *p; Conn *c; SSHChan *ch; char *cmd, *q, *buf, *toks[4]; int n, ntok, lev, xconn; uvlong qidpath; threadsetname("writereqremproc"); r = a; qidpath = (uvlong)r->fid->file->aux; lev = qidpath >> Levshift; xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { respond(r, "Invalid connection"); threadexits(nil); } ch = c->chans[qidpath & Chanmask]; if (r->ifcall.count <= 10) buf = emalloc9p(10 + 1); else buf = emalloc9p(r->ifcall.count + 1); memmove(buf, r->ifcall.data, r->ifcall.count); buf[r->ifcall.count] = '\0'; sshdebug(c, "writereqrem: %s", buf); ntok = tokenize(buf, toks, nelem(toks)); if (lev == Top) { free(keymbox.msg); keymbox.msg = buf; nbsendul(keymbox.mchan, 1); r->ofcall.count = r->ifcall.count; respexit(c, r, nil, nil); } r->ofcall.count = r->ifcall.count; if (c->state == Closed || c->state == Closing || ch->state == Closed || ch->state == Closing) respexit(c, r, buf, nil); p = new_packet(c); if (strcmp(toks[0], "success") == 0) { add_byte(p, SSH_MSG_CHANNEL_SUCCESS); add_uint32(p, ch->otherid); } else if (strcmp(toks[0], "failure") == 0) { add_byte(p, SSH_MSG_CHANNEL_FAILURE); add_uint32(p, ch->otherid); } else if (strcmp(toks[0], "close") == 0) { ch->state = Closing; add_byte(p, SSH_MSG_CHANNEL_CLOSE); add_uint32(p, ch->otherid); } else if (strcmp(toks[0], "shell") == 0) { ch->state = Established; /* * Some servers *cough*OpenSSH*cough* don't seem to be able * to intelligently handle a shell with no pty. */ add_byte(p, SSH_MSG_CHANNEL_REQUEST); add_uint32(p, ch->otherid); add_string(p, "pty-req"); add_byte(p, 0); if (ntok == 1) add_string(p, "dumb"); else add_string(p, toks[1]); add_uint32(p, 0); add_uint32(p, 0); add_uint32(p, 0); add_uint32(p, 0); add_string(p, ""); n = finish_packet(p); iowrite(c->dio, c->datafd, p->nlength, n); init_packet(p); p->c = c; add_byte(p, SSH_MSG_CHANNEL_REQUEST); add_uint32(p, ch->otherid); add_string(p, "shell"); add_byte(p, 0); sshdebug(c, "sending shell request: rlength=%lud twindow=%lud", p->rlength, ch->twindow); } else if (strcmp(toks[0], "exec") == 0) { ch->state = Established; add_byte(p, SSH_MSG_CHANNEL_REQUEST); add_uint32(p, ch->otherid); add_string(p, "exec"); add_byte(p, 0); cmd = emalloc9p(Bigbufsz); q = seprint(cmd, cmd+Bigbufsz, "%s", toks[1]); for (n = 2; n < ntok; ++n) { q = seprint(q, cmd+Bigbufsz, " %q", toks[n]); if (q == nil) break; } add_string(p, cmd); free(cmd); } else respexit(c, r, buf, "bad request command"); n = finish_packet(p); iowrite(c->dio, c->datafd, p->nlength, n); free(p); respexit(c, r, buf, nil); } void writedataproc(void *a) { Req *r; Packet *p; Conn *c; SSHChan *ch; int n, xconn; uvlong qidpath; threadsetname("writedataproc"); r = a; qidpath = (uvlong)r->fid->file->aux; xconn = (qidpath >> Connshift) & Connmask; c = connections[xconn]; if (c == nil) { respond(r, "Invalid connection"); threadexits(nil); } ch = c->chans[qidpath & Chanmask]; p = new_packet(c); add_byte(p, SSH_MSG_CHANNEL_DATA); hnputl(p->payload+1, ch->otherid); p->rlength += 4; add_block(p, r->ifcall.data, r->ifcall.count); n = finish_packet(p); if (ch->sent + p->rlength > ch->twindow) { qlock(&ch->xmtlock); while (ch->sent + p->rlength > ch->twindow) rsleep(&ch->xmtrendez); qunlock(&ch->xmtlock); } iowrite(c->dio, c->datafd, p->nlength, n); respexit(c, r, p, nil); } /* * Although this is named stclunk, it's attached to the destroyfid * member of the Srv struct. It turns out there's no member * called clunk. But if there are no other references, a 9P Tclunk * will end up calling destroyfid. */ void stclunk(Fid *f) { Packet *p; Conn *c; SSHChan *sc; int n, lev, cnum, chnum; uvlong qidpath; threadsetname("stclunk"); if (f == nil || f->file == nil) return; qidpath = (uvlong)f->file->aux; lev = qidpath >> Levshift; cnum = (qidpath >> Connshift) & Connmask; chnum = qidpath & Chanmask; c = connections[cnum]; sshdebug(c, "got clunk on file: %#llux %d %d %d: %s", qidpath, lev, cnum, chnum, f->file->name); /* qidpath test implies conn 0, chan 0 */ if (lev == Top && qidpath == Qreqrem) { if (keymbox.state != Empty) { keymbox.state = Empty; // nbsendul(keymbox.mchan, 1); } keymbox.msg = nil; return; } if (c == nil) return; if (lev == Connection && (qidpath & Qtypemask) == Qctl && (c->state == Opening || c->state == Negotiating || c->state == Authing)) { for (n = 0; n < MAXCONN && (!c->chans[n] || c->chans[n]->state == Empty || c->chans[n]->state == Closed || c->chans[n]->state == Closing); ++n) ; if (n >= MAXCONN) { if (c->rpid >= 0) threadint(c->rpid); shutdown(c); } return; } sc = c->chans[chnum]; if (lev != Subchannel) return; if ((qidpath & Qtypemask) == Qlisten && sc->state == Listening) { qlock(&c->l); if (sc->state != Closed) { sc->state = Closed; chanclose(sc->inchan); chanclose(sc->reqchan); } qunlock(&c->l); } else if ((qidpath & Qtypemask) == Qdata && sc->state != Empty && sc->state != Closed && sc->state != Closing) { if (f->file != sc->data && f->file != sc->request) { sshlog(c, "great evil is upon us; destroying a fid " "we didn't create"); return; } p = new_packet(c); add_byte(p, SSH_MSG_CHANNEL_CLOSE); hnputl(p->payload+1, sc->otherid); p->rlength += 4; n = finish_packet(p); sc->state = Closing; iowrite(c->dio, c->datafd, p->nlength, n); free(p); qlock(&c->l); rwakeup(&sc->r); qunlock(&c->l); nbsendul(sc->inchan, 1); nbsendul(sc->reqchan, 1); } for (n = 0; n < MAXCONN && (!c->chans[n] || c->chans[n]->state == Empty || c->chans[n]->state == Closed || c->chans[n]->state == Closing); ++n) ; if (n >= MAXCONN) { if (c->rpid >= 0) threadint(c->rpid); shutdown(c); } } void stflush(Req *r) { Req *or; uvlong qidpath; threadsetname("stflush"); or = r->oldreq; qidpath = (uvlong)or->fid->file->aux; sshdebug(nil, "got flush on file %#llux %lld %lld %lld: %s %#p", argv0, qidpath, qidpath >> Levshift, (qidpath >> Connshift) & Connmask, qidpath & Chanmask, or->fid->file->name, or->aux); if (!or->aux) respond(or, "interrupted"); else if (or->ifcall.type == Topen && (qidpath & Qtypemask) == Qlisten || or->ifcall.type == Tread && (qidpath & Qtypemask) == Qdata && (qidpath >> Levshift) == Subchannel || or->ifcall.type == Tread && (qidpath & Qtypemask) == Qreqrem) threadint((uintptr)or->aux); else { threadkill((uintptr)or->aux); or->aux = 0; respond(or, "interrupted"); } respond(r, nil); } void filedup(Req *r, File *src) { r->ofcall.qid = src->qid; closefile(r->fid->file); r->fid->file = src; incref(src); } Conn * alloc_conn(void) { int slevconn, i, s, firstnil; char buf[Numbsz]; Conn *c; static QLock aclock; qlock(&aclock); firstnil = -1; for (i = 0; i < MAXCONN; ++i) { if (connections[i] == nil) { if (firstnil == -1) firstnil = i; continue; } s = connections[i]->state; if (s == Empty || s == Closed) break; } if (i >= MAXCONN) { if (firstnil == -1) { /* all slots in use? */ qunlock(&aclock); return nil; } /* no reusable slots, allocate a new Conn */ connections[firstnil] = emalloc9p(sizeof(Conn)); memset(connections[firstnil], 0, sizeof(Conn)); i = firstnil; } c = connections[i]; memset(&c->r, '\0', sizeof(Rendez)); c->r.l = &c->l; c->dio = ioproc(); c->rio = nil; c->state = Allocated; c->role = Server; c->id = i; c->stifle = c->poisoned = 0; c->user = c->service = nil; c->inseq = c->nchan = c->outseq = 0; c->cscrypt = c->csmac = c->ctlfd = c->datafd = c->decrypt = c->encrypt = c->inmac = c->ncscrypt = c->ncsmac = c->nsccrypt = c->nscmac = c->outmac = c->rpid = c->sccrypt = c->scmac = c->tcpconn = -1; if (c->e) { mpfree(c->e); c->e = nil; } if (c->x) { mpfree(c->x); c->x = nil; } snprint(buf, sizeof buf, "%d", i); if (c->dir == nil) { slevconn = Connection << Levshift | i << Connshift; c->dir = createfile(rootfile, buf, uid, 0555|DMDIR, (void *)(slevconn | Qroot)); c->clonefile = createfile(c->dir, "clone", uid, 0666, (void *)(slevconn | Qclone)); c->ctlfile = createfile(c->dir, "ctl", uid, 0666, (void *)(slevconn | Qctl)); c->datafile = createfile(c->dir, "data", uid, 0666, (void *)(slevconn | Qdata)); c->listenfile = createfile(c->dir, "listen", uid, 0666, (void *)(slevconn | Qlisten)); c->localfile = createfile(c->dir, "local", uid, 0444, (void *)(slevconn | Qlocal)); c->remotefile = createfile(c->dir, "remote", uid, 0444, (void *)(slevconn | Qreqrem)); c->statusfile = createfile(c->dir, "status", uid, 0444, (void *)(slevconn | Qstatus)); c->tcpfile = createfile(c->dir, "tcp", uid, 0444, (void *)(slevconn | Qtcp)); } // c->skexinit = c->rkexinit = nil; c->got_sessid = 0; c->otherid = nil; c->inik = c->outik = nil; c->s2ccs = c->c2scs = c->enccs = c->deccs = nil; qunlock(&aclock); return c; } SSHChan * alloc_chan(Conn *c) { int cnum, slcn; char buf[Numbsz]; Plist *p, *next; SSHChan *sc; if (c->nchan >= MAXCONN) return nil; qlock(&c->l); cnum = c->nchan; if (c->chans[cnum] == nil) { c->chans[cnum] = emalloc9p(sizeof(SSHChan)); memset(c->chans[cnum], 0, sizeof(SSHChan)); } sc = c->chans[cnum]; snprint(buf, sizeof buf, "%d", cnum); memset(&sc->r, '\0', sizeof(Rendez)); sc->r.l = &c->l; sc->id = cnum; sc->state = Empty; sc->conn = c->id; sc->otherid = sc->waker = -1; sc->sent = sc->twindow = sc->rwindow = sc->inrqueue = 0; sc->ann = nil; sc->lreq = nil; if (sc->dir == nil) { slcn = Subchannel << Levshift | c->id << Connshift | cnum; sc->dir = createfile(c->dir, buf, uid, 0555|DMDIR, (void *)(slcn | Qroot)); sc->ctl = createfile(sc->dir, "ctl", uid, 0666, (void *)(slcn | Qctl)); sc->data = createfile(sc->dir, "data", uid, 0666, (void *)(slcn | Qdata)); sc->listen = createfile(sc->dir, "listen", uid, 0666, (void *)(slcn | Qlisten)); sc->request = createfile(sc->dir, "request", uid, 0666, (void *)(slcn | Qreqrem)); sc->status = createfile(sc->dir, "status", uid, 0444, (void *)(slcn | Qstatus)); sc->tcp = createfile(sc->dir, "tcp", uid, 0444, (void *)(slcn | Qtcp)); } c->nchan++; for (; sc->reqq != nil; sc->reqq = next) { p = sc->reqq; next = p->next; free(p->pack); free(p); } sc->dataq = sc->datatl = sc->reqtl = nil; if (sc->inchan) chanfree(sc->inchan); sc->inchan = chancreate(4, 0); if (sc->reqchan) chanfree(sc->reqchan); sc->reqchan = chancreate(4, 0); memset(&sc->xmtrendez, '\0', sizeof(Rendez)); sc->xmtrendez.l = &sc->xmtlock; qunlock(&c->l); return sc; } static int readlineio(Conn *, Ioproc *io, int fd, char *buf, int size) { int n; char *p; for (p = buf; p < buf + size - 1; p++) { n = ioread(io, fd, p, 1); if (n != 1 || *p == '\n') { *p = '\0'; break; } } return p - buf; } static char * readremote(Conn *c, Ioproc *io, char *tcpconn) { int n, remfd; char *p, *remote; char path[Arbbufsz], buf[NETPATHLEN]; remote = nil; snprint(path, sizeof path, "%s/tcp/%s/remote", mntpt, tcpconn); remfd = ioopen(io, path, OREAD); if (remfd < 0) { sshlog(c, "readremote: can't open %s: %r", path); return nil; } n = ioread(io, remfd, buf, sizeof buf - 1); if (n > 0) { buf[n] = 0; p = strchr(buf, '!'); if (p) *p = 0; remote = estrdup9p(buf); } ioclose(io, remfd); return remote; } static void sendmyid(Conn *c, Ioproc *io) { char path[Arbbufsz]; snprint(path, sizeof path, "%s\r\n", MYID); iowrite(io, c->datafd, path, strlen(path)); } /* save and tidy up the remote id */ static void stashremid(Conn *c, char *remid) { char *nl; if (c->otherid) free(c->otherid); c->otherid = estrdup9p(remid); nl = strchr(c->otherid, '\n'); if (nl) *nl = '\0'; nl = strchr(c->otherid, '\r'); if (nl) *nl = '\0'; } static void hangupconn(Conn *c) { hangup(c->ctlfd); close(c->ctlfd); close(c->datafd); c->ctlfd = c->datafd = -1; } #ifdef COEXIST static int exchids(Conn *c, Ioproc *io, char *remid, int remsz) { int n; /* * exchange versions. server writes id, then reads; * client reads id then writes (in theory). */ if (c->role == Server) { sendmyid(c, io); n = readlineio(c, io, c->datafd, remid, remsz); if (n < 5) /* can't be a valid SSH id string */ return -1; sshdebug(c, "dohandshake: server, got `%s', sent `%s'", remid, MYID); } else { /* client: read server's id */ n = readlineio(c, io, c->datafd, remid, remsz); if (n < 5) /* can't be a valid SSH id string */ return -1; sendmyid(c, io); sshdebug(c, "dohandshake: client, got `%s' sent `%s'", remid, MYID); if (remid[0] == '\0') { sshlog(c, "dohandshake: client, empty remote id string;" " out of sync"); return -1; } } sshdebug(c, "remote id string `%s'", remid); return 0; } /* * negotiate the protocols. * We don't do the full negotiation here, because we also have * to handle a re-negotiation request from the other end. * So we just kick it off and let the receiver process take it from there. */ static int negotiate(Conn *c) { send_kexinit(c); qlock(&c->l); if ((c->role == Client && c->state != Negotiating) || (c->role == Server && c->state != Established)) { sshdebug(c, "awaiting establishment"); rsleep(&c->r); } qunlock(&c->l); if (c->role == Server && c->state != Established || c->role == Client && c->state != Negotiating) { sshdebug(c, "failed to establish"); return -1; } sshdebug(c, "established; crypto now on"); return 0; } /* this was deferred when trying to make coexistence with v1 work */ static int deferredinit(Conn *c) { char remid[Arbbufsz]; Ioproc *io; io = ioproc(); /* * don't bother checking the remote's id string. * as a client, we can cope with v1 if we don't verify the host key. */ if (exchids(c, io, remid, sizeof remid) < 0 || 0 && c->role == Client && strncmp(remid, "SSH-2", 5) != 0 && strncmp(remid, "SSH-1.99", 8) != 0) { /* not a protocol version we know; give up */ closeioproc(io); hangupconn(c); return -1; } closeioproc(io); stashremid(c, remid); c->state = Initting; /* start the reader thread */ if (c->rpid < 0) c->rpid = threadcreate(reader, c, Defstk); return negotiate(c); } int dohandshake(Conn *c, char *tcpconn) { int tcpdfd; char *remote; char path[Arbbufsz]; Ioproc *io; io = ioproc(); /* read tcp conn's remote address into c->remote */ remote = readremote(c, io, tcpconn); if (remote) { free(c->remote); c->remote = remote; } /* open tcp conn's data file */ c->tcpconn = atoi(tcpconn); snprint(path, sizeof path, "%s/tcp/%s/data", mntpt, tcpconn); tcpdfd = ioopen(io, path, ORDWR); closeioproc(io); if (tcpdfd < 0) { sshlog(c, "dohandshake: can't open %s: %r", path); return -1; } c->datafd = tcpdfd; /* underlying tcp data descriptor */ return deferredinit(c); } #endif /* COEXIST */ int dohandshake(Conn *c, char *tcpconn) { int fd, n; char *p, *othid; char path[Arbbufsz], buf[NETPATHLEN]; Ioproc *io; io = ioproc(); snprint(path, sizeof path, "%s/tcp/%s/remote", mntpt, tcpconn); fd = ioopen(io, path, OREAD); n = ioread(io, fd, buf, sizeof buf - 1); if (n > 0) { buf[n] = 0; p = strchr(buf, '!'); if (p) *p = 0; free(c->remote); c->remote = estrdup9p(buf); } ioclose(io, fd); snprint(path, sizeof path, "%s/tcp/%s/data", mntpt, tcpconn); fd = ioopen(io, path, ORDWR); if (fd < 0) { closeioproc(io); return -1; } c->datafd = fd; /* exchange versions--we're only doing SSH2, unfortunately */ snprint(path, sizeof path, "%s\r\n", MYID); if (c->idstring && c->idstring[0]) strncpy(path, c->idstring, sizeof path); else { iowrite(io, fd, path, strlen(path)); p = path; n = 0; do { if (ioread(io, fd, p, 1) < 0) { fprint(2, "%s: short read in ID exchange: %r\n", argv0); break; } ++n; } while (*p++ != '\n'); if (n < 5) { /* can't be a valid SSH id string */ close(fd); goto err; } *p = 0; } sshdebug(c, "id string `%s'", path); if (c->idstring[0] == '\0' && strncmp(path, "SSH-2", 5) != 0 && strncmp(path, "SSH-1.99", 8) != 0) { /* not a protocol version we know; give up */ ioclose(io, fd); goto err; } closeioproc(io); if (c->otherid) free(c->otherid); c->otherid = othid = estrdup9p(path); for (n = strlen(othid) - 1; othid[n] == '\r' || othid[n] == '\n'; --n) othid[n] = '\0'; c->state = Initting; /* start the reader thread */ if (c->rpid < 0) c->rpid = threadcreate(reader, c, Defstk); /* * negotiate the protocols * We don't do the full negotiation here, because we also have * to handle a re-negotiation request from the other end. So * we just kick it off and let the receiver process take it from there. */ send_kexinit(c); qlock(&c->l); if ((c->role == Client && c->state != Negotiating) || (c->role == Server && c->state != Established)) rsleep(&c->r); qunlock(&c->l); if (c->role == Server && c->state != Established || c->role == Client && c->state != Negotiating) return -1; return 0; err: /* should use hangup in dial(2) instead of diddling /net/tcp */ snprint(path, sizeof path, "%s/tcp/%s/ctl", mntpt, tcpconn); fd = ioopen(io, path, OWRITE); iowrite(io, fd, "hangup", 6); ioclose(io, fd); closeioproc(io); return -1; } void send_kexinit(Conn *c) { Packet *ptmp; char *buf, *p, *e; int i, msglen; sshdebug(c, "initializing kexinit packet"); if (c->skexinit != nil) free(c->skexinit); c->skexinit = new_packet(c); buf = emalloc9p(Bigbufsz); buf[0] = (uchar)SSH_MSG_KEXINIT; add_packet(c->skexinit, buf, 1); for (i = 0; i < 16; ++i) buf[i] = fastrand(); add_packet(c->skexinit, buf, 16); /* cookie */ e = buf + Bigbufsz - 1; p = seprint(buf, e, "%s", kexes[0]->name); for (i = 1; i < nelem(kexes); ++i) p = seprint(p, e, ",%s", kexes[i]->name); sshdebug(c, "sent KEX algs: %s", buf); add_string(c->skexinit, buf); /* Key exchange */ if (pkas[0] == nil) add_string(c->skexinit, ""); else{ p = seprint(buf, e, "%s", pkas[0]->name); for (i = 1; i < nelem(pkas) && pkas[i] != nil; ++i) p = seprint(p, e, ",%s", pkas[i]->name); sshdebug(c, "sent host key algs: %s", buf); add_string(c->skexinit, buf); /* server's key algs */ } p = seprint(buf, e, "%s", cryptos[0]->name); for (i = 1; i < nelem(cryptos); ++i) p = seprint(p, e, ",%s", cryptos[i]->name); sshdebug(c, "sent crypto algs: %s", buf); add_string(c->skexinit, buf); /* c->s crypto */ add_string(c->skexinit, buf); /* s->c crypto */ p = seprint(buf, e, "%s", macnames[0]); for (i = 1; i < nelem(macnames); ++i) p = seprint(p, e, ",%s", macnames[i]); sshdebug(c, "sent MAC algs: %s", buf); add_string(c->skexinit, buf); /* c->s mac */ add_string(c->skexinit, buf); /* s->c mac */ add_string(c->skexinit, "none"); /* c->s compression */ add_string(c->skexinit, "none"); /* s->c compression */ add_string(c->skexinit, ""); /* c->s languages */ add_string(c->skexinit, ""); /* s->c languages */ memset(buf, 0, 5); add_packet(c->skexinit, buf, 5); ptmp = new_packet(c); memmove(ptmp, c->skexinit, sizeof(Packet)); msglen = finish_packet(ptmp); if (c->dio && c->datafd >= 0) iowrite(c->dio, c->datafd, ptmp->nlength, msglen); free(ptmp); free(buf); } static void establish(Conn *c) { qlock(&c->l); c->state = Established; rwakeup(&c->r); qunlock(&c->l); } static int negotiating(Conn *c, Packet *p, Packet *p2, char *buf, int size) { int i, n; USED(size); switch (p->payload[0]) { case SSH_MSG_DISCONNECT: if (debug) { get_string(p, p->payload + 5, buf, Arbbufsz, nil); sshdebug(c, "got disconnect: %s", buf); } return -1; case SSH_MSG_NEWKEYS: /* * If we're just updating, go straight to * established, otherwise wait for auth'n. */ i = c->encrypt; memmove(c->c2siv, c->nc2siv, SHA1dlen*2); memmove(c->s2civ, c->ns2civ, SHA1dlen*2); memmove(c->c2sek, c->nc2sek, SHA1dlen*2); memmove(c->s2cek, c->ns2cek, SHA1dlen*2); memmove(c->c2sik, c->nc2sik, SHA1dlen*2); memmove(c->s2cik, c->ns2cik, SHA1dlen*2); c->cscrypt = c->ncscrypt; c->sccrypt = c->nsccrypt; c->csmac = c->ncsmac; c->scmac = c->nscmac; c->c2scs = cryptos[c->cscrypt]->init(c, 0); c->s2ccs = cryptos[c->sccrypt]->init(c, 1); if (c->role == Server) { c->encrypt = c->sccrypt; c->decrypt = c->cscrypt; c->outmac = c->scmac; c->inmac = c->csmac; c->enccs = c->s2ccs; c->deccs = c->c2scs; c->outik = c->s2cik; c->inik = c->c2sik; } else{ c->encrypt = c->cscrypt; c->decrypt = c->sccrypt; c->outmac = c->csmac; c->inmac = c->scmac; c->enccs = c->c2scs; c->deccs = c->s2ccs; c->outik = c->c2sik; c->inik = c->s2cik; } sshdebug(c, "using %s for encryption and %s for decryption", cryptos[c->encrypt]->name, cryptos[c->decrypt]->name); qlock(&c->l); if (i != -1) c->state = Established; if (c->role == Client) rwakeup(&c->r); qunlock(&c->l); break; case SSH_MSG_KEXDH_INIT: kexes[c->kexalg]->serverkex(c, p); break; case SSH_MSG_KEXDH_REPLY: init_packet(p2); p2->c = c; if (kexes[c->kexalg]->clientkex2(c, p) < 0) { add_byte(p2, SSH_MSG_DISCONNECT); add_byte(p2, SSH_DISCONNECT_KEY_EXCHANGE_FAILED); add_string(p2, "Key exchange failure"); add_string(p2, ""); n = finish_packet(p2); iowrite(c->rio, c->datafd, p2->nlength, n); shutdown(c); free(p); free(p2); closeioproc(c->rio); c->rio = nil; c->rpid = -1; qlock(&c->l); rwakeup(&c->r); qunlock(&c->l); sshlog(c, "key exchange failure"); threadexits(nil); } add_byte(p2, SSH_MSG_NEWKEYS); n = finish_packet(p2); iowrite(c->rio, c->datafd, p2->nlength, n); qlock(&c->l); rwakeup(&c->r); qunlock(&c->l); break; case SSH_MSG_SERVICE_REQUEST: get_string(p, p->payload + 1, buf, Arbbufsz, nil); sshdebug(c, "got service request: %s", buf); if (strcmp(buf, "ssh-userauth") == 0 || strcmp(buf, "ssh-connection") == 0) { init_packet(p2); p2->c = c; sshdebug(c, "connection"); add_byte(p2, SSH_MSG_SERVICE_ACCEPT); add_string(p2, buf); n = finish_packet(p2); iowrite(c->rio, c->datafd, p2->nlength, n); c->state = Authing; } else{ init_packet(p2); p2->c = c; add_byte(p2, SSH_MSG_DISCONNECT); add_byte(p2, SSH_DISCONNECT_SERVICE_NOT_AVAILABLE); add_string(p2, "Unknown service type"); add_string(p2, ""); n = finish_packet(p2); iowrite(c->rio, c->datafd, p2->nlength, n); return -1; } break; case SSH_MSG_SERVICE_ACCEPT: get_string(p, p->payload + 1, buf, Arbbufsz, nil); if (c->service && strcmp(c->service, "ssh-userauth") == 0) { free(c->service); c->service = estrdup9p("ssh-connection"); } sshdebug(c, "got service accept: %s: responding with %s %s", buf, c->user, c->service); n = client_auth(c, c->rio); c->state = Authing; if (n < 0) { qlock(&c->l); rwakeup(&c->r); qunlock(&c->l); } break; } return 0; } static void nochans(Conn *c, Packet *p, Packet *p2) { int n; init_packet(p2); p2->c = c; add_byte(p2, SSH_MSG_CHANNEL_OPEN_FAILURE); add_block(p2, p->payload + 5, 4); hnputl(p2->payload + p2->rlength - 1, 4); p2->rlength += 4; add_string(p2, "No available channels"); add_string(p2, "EN"); n = finish_packet(p2); iowrite(c->rio, c->datafd, p2->nlength, n); } static int established(Conn *c, Packet *p, Packet *p2, char *buf, int size) { int i, n, cnum; uchar *q; Plist *pl; SSHChan *ch; USED(size); if (debug > 1) { sshdebug(c, "in Established state, got:"); dump_packet(p); } switch (p->payload[0]) { case SSH_MSG_DISCONNECT: if (debug) { get_string(p, p->payload + 5, buf, Arbbufsz, nil); sshdebug(c, "got disconnect: %s", buf); } return -1; case SSH_MSG_IGNORE: case SSH_MSG_UNIMPLEMENTED: break; case SSH_MSG_DEBUG: if (debug || p->payload[1]) { get_string(p, p->payload + 2, buf, Arbbufsz, nil); sshdebug(c, "got debug message: %s", buf); } break; case SSH_MSG_KEXINIT: send_kexinit(c); if (c->rkexinit) free(c->rkexinit); c->rkexinit = new_packet(c); memmove(c->rkexinit, p, sizeof(Packet)); if (validatekex(c, p) < 0) { sshdebug(c, "kex crypto algorithm mismatch (Established)"); return -1; } sshdebug(c, "using %s Kex algorithm and %s PKA", kexes[c->kexalg]->name, pkas[c->pkalg]->name); c->state = Negotiating; break; case SSH_MSG_GLOBAL_REQUEST: case SSH_MSG_REQUEST_SUCCESS: case SSH_MSG_REQUEST_FAILURE: break; case SSH_MSG_CHANNEL_OPEN: q = get_string(p, p->payload + 1, buf, Arbbufsz, nil); sshdebug(c, "searching for a listener for channel type %s", buf); ch = alloc_chan(c); if (ch == nil) { nochans(c, p, p2); break; } sshdebug(c, "alloced channel %d for listener", ch->id); qlock(&c->l); ch->otherid = nhgetl(q); ch->twindow = nhgetl(q+4); sshdebug(c, "got lock in channel open"); for (i = 0; i < c->nchan; ++i) if (c->chans[i] && c->chans[i]->state == Listening && c->chans[i]->ann && strcmp(c->chans[i]->ann, buf) == 0) break; if (i >= c->nchan) { sshdebug(c, "no listener: sleeping"); ch->state = Opening; if (ch->ann) free(ch->ann); ch->ann = estrdup9p(buf); sshdebug(c, "waiting for someone to announce %s", ch->ann); rsleep(&ch->r); } else{ sshdebug(c, "found listener on channel %d", ch->id); c->chans[i]->waker = ch->id; rwakeup(&c->chans[i]->r); } qunlock(&c->l); break; case SSH_MSG_CHANNEL_OPEN_CONFIRMATION: cnum = nhgetl(p->payload + 1); ch = c->chans[cnum]; qlock(&c->l); ch->otherid = nhgetl(p->payload+5); ch->twindow = nhgetl(p->payload+9); rwakeup(&ch->r); qunlock(&c->l); break; case SSH_MSG_CHANNEL_OPEN_FAILURE: cnum = nhgetl(p->payload + 1); ch = c->chans[cnum]; qlock(&c->l); rwakeup(&ch->r); qunlock(&c->l); return -1; case SSH_MSG_CHANNEL_WINDOW_ADJUST: cnum = nhgetl(p->payload + 1); ch = c->chans[cnum]; ch->twindow += nhgetl(p->payload + 5); sshdebug(c, "new twindow for channel: %d: %lud", cnum, ch->twindow); qlock(&ch->xmtlock); rwakeup(&ch->xmtrendez); qunlock(&ch->xmtlock); break; case SSH_MSG_CHANNEL_DATA: case SSH_MSG_CHANNEL_EXTENDED_DATA: cnum = nhgetl(p->payload + 1); ch = c->chans[cnum]; pl = emalloc9p(sizeof(Plist)); pl->pack = emalloc9p(sizeof(Packet)); memmove(pl->pack, p, sizeof(Packet)); if (p->payload[0] == SSH_MSG_CHANNEL_DATA) { pl->rem = nhgetl(p->payload + 5); pl->st = pl->pack->payload + 9; } else { pl->rem = nhgetl(p->payload + 9); pl->st = pl->pack->payload + 13; } pl->next = nil; if (ch->dataq == nil) ch->dataq = pl; else ch->datatl->next = pl; ch->datatl = pl; ch->inrqueue += pl->rem; nbsendul(ch->inchan, 1); break; case SSH_MSG_CHANNEL_EOF: cnum = nhgetl(p->payload + 1); ch = c->chans[cnum]; if (ch->state != Closed && ch->state != Closing) { ch->state = Eof; nbsendul(ch->inchan, 1); nbsendul(ch->reqchan, 1); } break; case SSH_MSG_CHANNEL_CLOSE: cnum = nhgetl(p->payload + 1); ch = c->chans[cnum]; if (ch->state != Closed && ch->state != Closing) { init_packet(p2); p2->c = c; add_byte(p2, SSH_MSG_CHANNEL_CLOSE); hnputl(p2->payload + 1, ch->otherid); p2->rlength += 4; n = finish_packet(p2); iowrite(c->rio, c->datafd, p2->nlength, n); } qlock(&c->l); if (ch->state != Closed) { ch->state = Closed; rwakeup(&ch->r); nbsendul(ch->inchan, 1); nbsendul(ch->reqchan, 1); chanclose(ch->inchan); chanclose(ch->reqchan); } qunlock(&c->l); for (i = 0; i < MAXCONN && (!c->chans[i] || c->chans[i]->state == Empty || c->chans[i]->state == Closed); ++i) ; if (i >= MAXCONN) return -1; break; case SSH_MSG_CHANNEL_REQUEST: cnum = nhgetl(p->payload + 1); ch = c->chans[cnum]; sshdebug(c, "queueing channel request for channel: %d", cnum); q = get_string(p, p->payload+5, buf, Arbbufsz, nil); pl = emalloc9p(sizeof(Plist)); pl->pack = emalloc9p(sizeof(Packet)); n = snprint((char *)pl->pack->payload, Maxpayload, "%s %c", buf, *q? 't': 'f'); sshdebug(c, "request message begins: %s", (char *)pl->pack->payload); memmove(pl->pack->payload + n, q + 1, p->rlength - (11 + n-2)); pl->rem = p->rlength - 11 + 2; pl->st = pl->pack->payload; pl->next = nil; if (ch->reqq == nil) ch->reqq = pl; else ch->reqtl->next = pl; ch->reqtl = pl; nbsendul(ch->reqchan, 1); break; case SSH_MSG_CHANNEL_SUCCESS: case SSH_MSG_CHANNEL_FAILURE: default: break; } return 0; } static void bail(Conn *c, Packet *p, Packet *p2, char *sts) { shutdown(c); free(p); free(p2); if (c->rio) { closeioproc(c->rio); c->rio = nil; } c->rpid = -1; threadexits(sts); } static void reader0(Conn *c, Packet *p, Packet *p2) { int i, n, nl, np, nm, nb; char buf[Arbbufsz]; nm = 0; nb = 4; if (c->decrypt != -1) nb = cryptos[c->decrypt]->blklen; sshdebug(c, "calling read for connection %d, state %d, nb %d, dc %d", c->id, c->state, nb, c->decrypt); if ((nl = ioreadn(c->rio, c->datafd, p->nlength, nb)) != nb) { sshdebug(c, "reader for connection %d exiting, got %d: %r", c->id, nl); bail(c, p, p2, "reader exiting"); } if (c->decrypt != -1) cryptos[c->decrypt]->decrypt(c->deccs, p->nlength, nb); p->rlength = nhgetl(p->nlength); sshdebug(c, "got message length: %ld", p->rlength); if (p->rlength > Maxpktpay) { sshdebug(c, "absurd packet length: %ld, unrecoverable decrypt failure", p->rlength); bail(c, p, p2, "absurd packet length"); } np = ioreadn(c->rio, c->datafd, p->nlength + nb, p->rlength + 4 - nb); if (c->inmac != -1) nm = ioreadn(c->rio, c->datafd, p->nlength + p->rlength + 4, SHA1dlen); /* SHA1dlen was magic 20 */ n = nl + np + nm; if (debug) { sshdebug(c, "got message of %d bytes %d padding", n, p->pad_len); if (p->payload[0] > SSH_MSG_CHANNEL_OPEN) { i = nhgetl(p->payload+1); if (c->chans[i]) sshdebug(c, " for channel %d win %lud", i, c->chans[i]->rwindow); else sshdebug(c, " for invalid channel %d", i); } sshdebug(c, " first byte: %d", p->payload[0]); } /* SHA1dlen was magic 20 */ if (np != p->rlength + 4 - nb || c->inmac != -1 && nm != SHA1dlen) { sshdebug(c, "got EOF/error on connection read: %d %d %r", np, nm); bail(c, p, p2, "error or eof"); } p->tlength = n; p->rlength = n - 4; if (undo_packet(p) < 0) { sshdebug(c, "bad packet in connection %d: exiting", c->id); bail(c, p, p2, "bad packet"); } if (c->state == Initting) { if (p->payload[0] != SSH_MSG_KEXINIT) { sshdebug(c, "missing KEX init packet: %d", p->payload[0]); bail(c, p, p2, "bad kex"); } if (c->rkexinit) free(c->rkexinit); c->rkexinit = new_packet(c); memmove(c->rkexinit, p, sizeof(Packet)); if (validatekex(c, p) < 0) { sshdebug(c, "kex crypto algorithm mismatch (Initting)"); bail(c, p, p2, "bad kex"); } sshdebug(c, "using %s Kex algorithm and %s PKA", kexes[c->kexalg]->name, pkas[c->pkalg]->name); if (c->role == Client) kexes[c->kexalg]->clientkex1(c, p); c->state = Negotiating; } else if (c->state == Negotiating) { if (negotiating(c, p, p2, buf, sizeof buf) < 0) bail(c, p, p2, "negotiating"); } else if (c->state == Authing) { switch (p->payload[0]) { case SSH_MSG_DISCONNECT: if (debug) { get_string(p, p->payload + 5, buf, Arbbufsz, nil); sshdebug(c, "got disconnect: %s", buf); } bail(c, p, p2, "msg disconnect"); case SSH_MSG_USERAUTH_REQUEST: switch (auth_req(p, c)) { case 0: /* success */ establish(c); break; case 1: /* ok to try again */ case -1: /* failure */ break; case -2: /* can't happen, now at least */ bail(c, p, p2, "in userauth request"); } break; case SSH_MSG_USERAUTH_FAILURE: qlock(&c->l); rwakeup(&c->r); qunlock(&c->l); break; case SSH_MSG_USERAUTH_SUCCESS: establish(c); break; case SSH_MSG_USERAUTH_BANNER: break; } } else if (c->state == Established) { if (established(c, p, p2, buf, sizeof buf) < 0) bail(c, p, p2, "from established state"); } else { sshdebug(c, "connection %d in bad state, reader exiting", c->id); bail(c, p, p2, "bad conn state"); } } void reader(void *a) { Conn *c; Packet *p, *p2; threadsetname("reader"); c = a; c->rpid = threadid(); sshdebug(c, "starting reader for connection %d, pid %d", c->id, c->rpid); threadsetname("reader"); p = new_packet(c); p2 = new_packet(c); c->rio = ioproc(); for(;;) reader0(c, p, p2); } int validatekex(Conn *c, Packet *p) { if (c->role == Server) return validatekexs(p); else return validatekexc(p); } int validatekexs(Packet *p) { uchar *q; char *toks[Maxtoks]; int i, j, n; char *buf; buf = emalloc9p(Bigbufsz); q = p->payload + 17; q = get_string(p, q, buf, Bigbufsz, nil); sshdebug(nil, "received KEX algs: %s", buf); n = gettokens(buf, toks, nelem(toks), ","); for (i = 0; i < n; ++i) for (j = 0; j < nelem(kexes); ++j) if (strcmp(toks[i], kexes[j]->name) == 0) goto foundk; sshdebug(nil, "kex algs not in kexes"); free(buf); return -1; foundk: p->c->kexalg = j; q = get_string(p, q, buf, Bigbufsz, nil); sshdebug(nil, "received host key algs: %s", buf); n = gettokens(buf, toks, nelem(toks), ","); for (i = 0; i < n; ++i) for (j = 0; j < nelem(pkas) && pkas[j] != nil; ++j) if (strcmp(toks[i], pkas[j]->name) == 0) goto foundpka; sshdebug(nil, "host key algs not in pkas"); free(buf); return -1; foundpka: p->c->pkalg = j; q = get_string(p, q, buf, Bigbufsz, nil); sshdebug(nil, "received C2S crypto algs: %s", buf); n = gettokens(buf, toks, nelem(toks), ","); for (i = 0; i < n; ++i) for (j = 0; j < nelem(cryptos); ++j) if (strcmp(toks[i], cryptos[j]->name) == 0) goto foundc1; sshdebug(nil, "c2s crypto algs not in cryptos"); free(buf); return -1; foundc1: p->c->ncscrypt = j; q = get_string(p, q, buf, Bigbufsz, nil); sshdebug(nil, "received S2C crypto algs: %s", buf); n = gettokens(buf, toks, nelem(toks), ","); for (i = 0; i < n; ++i) for (j = 0; j < nelem(cryptos); ++j) if (strcmp(toks[i], cryptos[j]->name) == 0) goto foundc2; sshdebug(nil, "s2c crypto algs not in cryptos"); free(buf); return -1; foundc2: p->c->nsccrypt = j; q = get_string(p, q, buf, Bigbufsz, nil); sshdebug(nil, "received C2S MAC algs: %s", buf); n = gettokens(buf, toks, nelem(toks), ","); for (i = 0; i < n; ++i) for (j = 0; j < nelem(macnames); ++j) if (strcmp(toks[i], macnames[j]) == 0) goto foundm1; sshdebug(nil, "c2s mac algs not in cryptos"); free(buf); return -1; foundm1: p->c->ncsmac = j; q = get_string(p, q, buf, Bigbufsz, nil); sshdebug(nil, "received S2C MAC algs: %s", buf); n = gettokens(buf, toks, nelem(toks), ","); for (i = 0; i < n; ++i) for (j = 0; j < nelem(macnames); ++j) if (strcmp(toks[i], macnames[j]) == 0) goto foundm2; sshdebug(nil, "s2c mac algs not in cryptos"); free(buf); return -1; foundm2: p->c->nscmac = j; q = get_string(p, q, buf, Bigbufsz, nil); q = get_string(p, q, buf, Bigbufsz, nil); q = get_string(p, q, buf, Bigbufsz, nil); q = get_string(p, q, buf, Bigbufsz, nil); free(buf); if (*q) return 1; return 0; } int validatekexc(Packet *p) { uchar *q; char *toks[Maxtoks]; int i, j, n; char *buf; buf = emalloc9p(Bigbufsz); q = p->payload + 17; q = get_string(p, q, buf, Bigbufsz, nil); n = gettokens(buf, toks, nelem(toks), ","); for (j = 0; j < nelem(kexes); ++j) for (i = 0; i < n; ++i) if (strcmp(toks[i], kexes[j]->name) == 0) goto foundk; free(buf); return -1; foundk: p->c->kexalg = j; q = get_string(p, q, buf, Bigbufsz, nil); n = gettokens(buf, toks, nelem(toks), ","); for (j = 0; j < nelem(pkas) && pkas[j] != nil; ++j) for (i = 0; i < n; ++i) if (strcmp(toks[i], pkas[j]->name) == 0) goto foundpka; free(buf); return -1; foundpka: p->c->pkalg = j; q = get_string(p, q, buf, Bigbufsz, nil); n = gettokens(buf, toks, nelem(toks), ","); for (j = 0; j < nelem(cryptos); ++j) for (i = 0; i < n; ++i) if (strcmp(toks[i], cryptos[j]->name) == 0) goto foundc1; free(buf); return -1; foundc1: p->c->ncscrypt = j; q = get_string(p, q, buf, Bigbufsz, nil); n = gettokens(buf, toks, nelem(toks), ","); for (j = 0; j < nelem(cryptos); ++j) for (i = 0; i < n; ++i) if (strcmp(toks[i], cryptos[j]->name) == 0) goto foundc2; free(buf); return -1; foundc2: p->c->nsccrypt = j; q = get_string(p, q, buf, Bigbufsz, nil); n = gettokens(buf, toks, nelem(toks), ","); for (j = 0; j < nelem(macnames); ++j) for (i = 0; i < n; ++i) if (strcmp(toks[i], macnames[j]) == 0) goto foundm1; free(buf); return -1; foundm1: p->c->ncsmac = j; q = get_string(p, q, buf, Bigbufsz, nil); n = gettokens(buf, toks, nelem(toks), ","); for (j = 0; j < nelem(macnames); ++j) for (i = 0; i < n; ++i) if (strcmp(toks[i], macnames[j]) == 0) goto foundm2; free(buf); return -1; foundm2: p->c->nscmac = j; q = get_string(p, q, buf, Bigbufsz, nil); q = get_string(p, q, buf, Bigbufsz, nil); q = get_string(p, q, buf, Bigbufsz, nil); q = get_string(p, q, buf, Bigbufsz, nil); free(buf); return *q != 0; } int memrandom(void *p, int n) { uchar *cp; for (cp = (uchar*)p; n > 0; n--) *cp++ = fastrand(); return 0; } /* * create a change uid capability */ char* mkcap(char *from, char *to) { int fd, fromtosz; char *cap, *key; uchar rand[SHA1dlen], hash[SHA1dlen]; fd = open("#¤/caphash", OWRITE); if (fd < 0) sshlog(nil, "can't open #¤/caphash: %r"); /* create the capability */ fromtosz = strlen(from) + 1 + strlen(to) + 1; cap = emalloc9p(fromtosz + sizeof(rand)*3 + 1); snprint(cap, fromtosz + sizeof(rand)*3 + 1, "%s@%s", from, to); memrandom(rand, sizeof(rand)); key = cap + fromtosz; enc64(key, sizeof(rand)*3, rand, sizeof(rand)); /* hash the capability */ hmac_sha1((uchar*)cap, strlen(cap), (uchar*)key, strlen(key), hash, nil); /* give the kernel the hash */ key[-1] = '@'; sshdebug(nil, "writing `%.*s' to caphash", SHA1dlen, hash); if (write(fd, hash, SHA1dlen) != SHA1dlen) { close(fd); free(cap); return nil; } close(fd); return cap; } /* * ask keyfs (assumes we are on an auth server) */ static AuthInfo * keyfsauth(char *me, char *user, char *pw, char *key1, char *key2) { int fd; char path[Arbpathlen]; AuthInfo *ai; if (passtokey(key1, pw) == 0) return nil; snprint(path, Arbpathlen, "/mnt/keys/%s/key", user); if ((fd = open(path, OREAD)) < 0) { werrstr("Invalid user %s", user); return nil; } if (read(fd, key2, DESKEYLEN) != DESKEYLEN) { close(fd); werrstr("Password mismatch 1"); return nil; } close(fd); if (memcmp(key1, key2, DESKEYLEN) != 0) { werrstr("Password mismatch 2"); return nil; } ai = emalloc9p(sizeof(AuthInfo)); ai->cuid = estrdup9p(user); ai->suid = estrdup9p(me); ai->cap = mkcap(me, user); ai->nsecret = 0; ai->secret = (uchar *)estrdup9p(""); return ai; } static void userauthfailed(Packet *p2) { add_byte(p2, SSH_MSG_USERAUTH_FAILURE); add_string(p2, "password,publickey"); add_byte(p2, 0); } static int authreqpk(Packet *p, Packet *p2, Conn *c, char *user, uchar *q, char *alg, char *blob, char *sig, char *service, char *me) { int n, thisway, nblob, nsig; char method[32]; sshdebug(c, "auth_req publickey for user %s", user); thisway = *q == '\0'; q = get_string(p, q+1, alg, Arbpathlen, nil); q = get_string(p, q, blob, Blobsz, &nblob); if (thisway) { /* * Should really check to see if this user can * be authed this way. */ for (n = 0; n < nelem(pkas) && pkas[n] != nil && strcmp(pkas[n]->name, alg) != 0; ++n) ; if (n >= nelem(pkas) || pkas[n] == nil) { userauthfailed(p2); return -1; } add_byte(p2, SSH_MSG_USERAUTH_PK_OK); add_string(p2, alg); add_block(p2, blob, nblob); return 1; } get_string(p, q, sig, Blobsz, &nsig); for (n = 0; n < nelem(pkas) && pkas[n] != nil && strcmp(pkas[n]->name, alg) != 0; ++n) ; if (n >= nelem(pkas) || pkas[n] == nil) { userauthfailed(p2); return -1; } add_block(p2, c->sessid, SHA1dlen); add_byte(p2, SSH_MSG_USERAUTH_REQUEST); add_string(p2, user); add_string(p2, service); add_string(p2, method); add_byte(p2, 1); add_string(p2, alg); add_block(p2, blob, nblob); if (pkas[n]->verify(c, p2->payload, p2->rlength - 1, user, sig, nsig) == 0) { init_packet(p2); p2->c = c; sshlog(c, "public key login failed"); userauthfailed(p2); return -1; } free(c->cap); c->cap = mkcap(me, user); init_packet(p2); p2->c = c; sshlog(c, "logged in by public key"); add_byte(p2, SSH_MSG_USERAUTH_SUCCESS); return 0; } int auth_req(Packet *p, Conn *c) { int n, ret; char *alg, *blob, *sig, *service, *me, *user, *pw, *path; char key1[DESKEYLEN], key2[DESKEYLEN], method[32]; uchar *q; AuthInfo *ai; Packet *p2; service = emalloc9p(Arbpathlen); me = emalloc9p(Arbpathlen); user = emalloc9p(Arbpathlen); pw = emalloc9p(Arbpathlen); alg = emalloc9p(Arbpathlen); path = emalloc9p(Arbpathlen); blob = emalloc9p(Blobsz); sig = emalloc9p(Blobsz); ret = -1; /* failure is default */ q = get_string(p, p->payload + 1, user, Arbpathlen, nil); free(c->user); c->user = estrdup9p(user); q = get_string(p, q, service, Arbpathlen, nil); q = get_string(p, q, method, sizeof method, nil); sshdebug(c, "got userauth request: %s %s %s", user, service, method); readfile("/dev/user", me, Arbpathlen); p2 = new_packet(c); if (strcmp(method, "publickey") == 0) ret = authreqpk(p, p2, c, user, q, alg, blob, sig, service, me); else if (strcmp(method, "password") == 0) { get_string(p, q + 1, pw, Arbpathlen, nil); // sshdebug(c, "%s", pw); /* bad idea to log passwords */ sshdebug(c, "auth_req password"); if (kflag) ai = keyfsauth(me, user, pw, key1, key2); else ai = auth_userpasswd(user, pw); if (ai == nil) { sshlog(c, "login failed: %r"); userauthfailed(p2); } else { sshdebug(c, "auth successful: cuid %s suid %s cap %s", ai->cuid, ai->suid, ai->cap); free(c->cap); if (strcmp(user, me) == 0) c->cap = estrdup9p("n/a"); else c->cap = estrdup9p(ai->cap); sshlog(c, "logged in by password"); add_byte(p2, SSH_MSG_USERAUTH_SUCCESS); auth_freeAI(ai); ret = 0; } } else userauthfailed(p2); n = finish_packet(p2); iowrite(c->dio, c->datafd, p2->nlength, n); free(service); free(me); free(user); free(pw); free(alg); free(blob); free(sig); free(path); free(p2); return ret; } int client_auth(Conn *c, Ioproc *io) { Packet *p2, *p3, *p4; char *r, *s; mpint *ek, *nk; int i, n; sshdebug(c, "client_auth"); if (!c->password && !c->authkey) return -1; p2 = new_packet(c); add_byte(p2, SSH_MSG_USERAUTH_REQUEST); add_string(p2, c->user); add_string(p2, c->service); if (c->password) { add_string(p2, "password"); add_byte(p2, 0); add_string(p2, c->password); sshdebug(c, "client_auth using password for svc %s", c->service); } else { sshdebug(c, "client_auth trying rsa public key"); add_string(p2, "publickey"); add_byte(p2, 1); add_string(p2, "ssh-rsa"); r = strstr(c->authkey, " ek="); s = strstr(c->authkey, " n="); if (!r || !s) { shutdown(c); free(p2); sshdebug(c, "client_auth no rsa key"); return -1; } ek = strtomp(r+4, nil, 16, nil); nk = strtomp(s+3, nil, 16, nil); p3 = new_packet(c); add_string(p3, "ssh-rsa"); add_mp(p3, ek); add_mp(p3, nk); add_block(p2, p3->payload, p3->rlength-1); p4 = new_packet(c); add_block(p4, c->sessid, SHA1dlen); add_byte(p4, SSH_MSG_USERAUTH_REQUEST); add_string(p4, c->user); add_string(p4, c->service); add_string(p4, "publickey"); add_byte(p4, 1); add_string(p4, "ssh-rsa"); add_block(p4, p3->payload, p3->rlength-1); mpfree(ek); mpfree(nk); free(p3); for (i = 0; pkas[i] && strcmp("ssh-rsa", pkas[i]->name) != 0; ++i) ; sshdebug(c, "client_auth rsa signing alg %d: %r", i); if ((p3 = pkas[i]->sign(c, p4->payload, p4->rlength-1)) == nil) { sshdebug(c, "client_auth rsa signing failed: %r"); free(p4); free(p2); return -1; } add_block(p2, p3->payload, p3->rlength-1); free(p3); free(p4); } n = finish_packet(p2); if (writeio(io, c->datafd, p2->nlength, n) != n) sshdebug(c, "client_auth write failed: %r"); free(p2); return 0; } /* should use auth_getkey or something similar */ char * factlookup(int nattr, int nreq, char *attrs[]) { Biobuf *bp; char *buf, *toks[Maxtoks], *res, *q; int ntok, nmatch, maxmatch; int i, j; res = nil; bp = Bopen("/mnt/factotum/ctl", OREAD); if (bp == nil) return nil; maxmatch = 0; while (buf = Brdstr(bp, '\n', 1)) { q = estrdup9p(buf); ntok = gettokens(buf, toks, nelem(toks), " "); nmatch = 0; for (i = 0; i < nattr; ++i) { for (j = 0; j < ntok; ++j) if (strcmp(attrs[i], toks[j]) == 0) { ++nmatch; break; } if (i < nreq && j >= ntok) break; } if (i >= nattr && nmatch > maxmatch) { free(res); res = q; maxmatch = nmatch; } else free(q); free(buf); } Bterm(bp); return res; } void shutdown(Conn *c) { Plist *p; SSHChan *sc; int i, ostate; sshdebug(c, "shutting down connection %d", c->id); ostate = c->state; if (c->clonefile->ref <= 2 && c->ctlfile->ref <= 2 && c->datafile->ref <= 2 && c->listenfile->ref <= 2 && c->localfile->ref <= 2 && c->remotefile->ref <= 2 && c->statusfile->ref <= 2) c->state = Closed; else { if (c->state != Closed) c->state = Closing; sshdebug(c, "clone %ld ctl %ld data %ld listen %ld " "local %ld remote %ld status %ld", c->clonefile->ref, c->ctlfile->ref, c->datafile->ref, c->listenfile->ref, c->localfile->ref, c->remotefile->ref, c->statusfile->ref); } if (ostate == Closed || ostate == Closing) { c->state = Closed; return; } if (c->role == Server && c->remote) sshlog(c, "closing connection"); hangupconn(c); if (c->dio) { closeioproc(c->dio); c->dio = nil; } c->decrypt = -1; c->inmac = -1; c->nchan = 0; free(c->otherid); free(c->s2ccs); c->s2ccs = nil; free(c->c2scs); c->c2scs = nil; free(c->remote); c->remote = nil; if (c->x) { mpfree(c->x); c->x = nil; } if (c->e) { mpfree(c->e); c->e = nil; } free(c->user); c->user = nil; free(c->service); c->service = nil; c->otherid = nil; qlock(&c->l); rwakeupall(&c->r); qunlock(&c->l); for (i = 0; i < MAXCONN; ++i) { sc = c->chans[i]; if (sc == nil) continue; free(sc->ann); sc->ann = nil; if (sc->state != Empty && sc->state != Closed) { sc->state = Closed; sc->lreq = nil; while (sc->dataq != nil) { p = sc->dataq; sc->dataq = p->next; free(p->pack); free(p); } while (sc->reqq != nil) { p = sc->reqq; sc->reqq = p->next; free(p->pack); free(p); } qlock(&c->l); rwakeupall(&sc->r); nbsendul(sc->inchan, 1); nbsendul(sc->reqchan, 1); chanclose(sc->inchan); chanclose(sc->reqchan); qunlock(&c->l); } } qlock(&availlck); rwakeup(&availrend); qunlock(&availlck); sshdebug(c, "done processing shutdown of connection %d", c->id); }