#include <u.h>
#include <libc.h>
#include <mp.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <auth.h>
#include <authsrv.h>
#include <libsec.h>
#include <ip.h>
#include "sshtun.h"
void stend(Srv *);
void server(char *, char *);
void stopen(Req *);
void stlisten1(void *);
void stlisten2(void *);
void stread(Req *);
void readreqrem(void *);
void readdata(void *);
void stwrite(Req *);
void writectl(void *);
void writereqrem(void *);
void writedata(void *);
void stclunk(Fid *);
void stflush(Req *);
void filedup(Req *, File *);
Conn *alloc_conn(void);
SSHChan *alloc_chan(Conn *);
int dohandshake(Conn *, char *);
void send_kexinit(Conn *);
void reader(void *);
int validatekex(Conn *, Packet *);
int validatekexs(Packet *);
int validatekexc(Packet *);
int auth_req(Packet *, Conn *);
int client_auth(Conn *, Ioproc *);
char *factlookup(int, int, char *[]);
void shutdown(Conn *);
Srv sshtunsrv = {
.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",
};
File *rootfile, *clonefile, *ctlfile, *keysfile;
Conn *connections[MAXCONN];
char *mntpt = "/net";
int debug;
int kflag;
int slfd;
char uid[32];
MBox keymbox;
QLock availlck;
Rendez availrend;
void
usage(void)
{
fprint(2, "usage: sshtun [-d] [-k] [-m mntpt] [-s srvpt]\n");
exits("usage");
}
void
threadmain(int argc, char *argv[])
{
char *srvpt = nil;
int fd, n;
slfd = open("/dev/syslog", OWRITE);
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;
default:
usage();
break;
} ARGEND;
fd = open("/dev/user", OREAD);
if (fd < 0)
strcpy(uid, "none");
else {
n = read(fd, uid, 31);
if (n < 0)
strcpy(uid, "none");
else
uid[n] = '\0';
close(fd);
}
keymbox.mchan = chancreate(4, 0);
availrend.l = &availlck;
dh_init(pkas);
if (rfork(RFNOTEG) < 0)
fprint(2, "Failed to set process attributes: %r\n");
server(mntpt, srvpt);
}
Ioproc *io9p;
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;
sshtunsrv.tree = alloctree(uid, uid, 0777, nil);
rootfile = createfile(sshtunsrv.tree->root, "ssh", uid, 0555|DMDIR, (void*)RootFile);
clonefile = createfile(rootfile, "clone", uid, 0666, (void*)CloneFile);
ctlfile = createfile(rootfile, "ctl", uid, 0666, (void*)CtlFile);
keysfile = createfile(rootfile, "keys", uid, 0600, (void *)ReqRemFile);
threadpostmountsrv(&sshtunsrv, srvpt, mntpt, MAFTER);
p = smprint("%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 = smprint("/srv/%s", srvpt);
dirwstat(p, &d);
free(p);
}
}
void
stopen(Req *r)
{
Conn *c;
SSHChan *sc;
char *p;
int lev, xconn, fnum, fd;
char buf[10];
fnum = (uintptr)r->fid->file->aux;
lev = fnum >> LEVSHIFT;
switch (fnum & FileMask) {
case CloneFile:
switch (lev) {
case 0:
p = smprint("%s/tcp/clone", mntpt);
fd = open(p, ORDWR);
free(p);
if (fd < 0) {
responderror(r);
return;
}
c = alloc_conn();
if (c == nil) {
respond(r, "No more connections");
return;
}
c->ctlfd = fd;
filedup(r, c->ctlfile);
if (debug)
fprint(2, "new connection: %d\n", c->id);
break;
case 1:
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
respond(r, "Invalid connection");
return;
}
sc = alloc_chan(c);
if (sc == nil) {
respond(r, "No more channels");
return;
}
filedup(r, sc->ctl);
break;
default:
snprint(buf, 10, "bad %d", lev);
readstr(r, buf);
break;
}
respond(r, nil);
break;
case ListenFile:
switch (lev) {
case 1:
r->aux = (void *)threadcreate(stlisten1, r, 8192);
break;
case 2:
r->aux = (void *)threadcreate(stlisten2, r, 8192);
break;
default:
respond(r, "not possible");
break;
}
break;
default:
respond(r, nil);
break;
}
}
void
stlisten1(void *a)
{
Req *r;
Conn *c, *cl;
Ioproc *io;
char *msg;
int fnum, xconn, fd, n;
char buf[10], path[40];
r = a;
fnum = (uintptr)r->fid->file->aux;
xconn = (fnum >> CONNSHIFT) & ConnMask;
cl = connections[xconn];
if (cl == nil) {
respond(r, "Invalid connection");
threadexits(nil);
}
memset(buf, '\0', sizeof(buf));
io = ioproc();
seek(cl->ctlfd, 0, 0);
if ((n = ioread(io, cl->ctlfd, buf, 10)) <= 0)
fprint(2, "read failed: %r\n");
buf[n] = '\0';
cl->state = Listening;
snprint(path, 40, "%s/tcp/%s/listen", mntpt, buf);
while (1) {
fd = ioopen(io, path, ORDWR);
if (fd < 0) {
r->aux = 0;
responderror(r);
closeioproc(io);
shutdown(cl);
threadexits(nil);
}
c = alloc_conn();
if (c)
break;
n = ioread(io, fd, buf, 10);
if (n <= 0) {
r->aux = 0;
responderror(r);
closeioproc(io);
shutdown(cl);
threadexits(nil);
}
else {
buf[n] = '\0';
msg = smprint("reject %s No available connections", buf);
iowrite(io, fd, msg, strlen(msg));
free(msg);
}
close(fd);
}
c->ctlfd = fd;
filedup(r, c->ctlfile);
if (debug)
fprint(2, "**** responding to listen open ***\n");
r->aux = 0;
respond(r, nil);
closeioproc(io);
threadexits(nil);
}
void
stlisten2(void *a)
{
Req *r;
Packet *p2;
Ioproc *io;
Conn *c;
SSHChan *sc;
int i, n, xconn, fnum;
r = a;
fnum = (uintptr)r->fid->file->aux;
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
respond(r, "Invalid connection");
threadexits(nil);
}
if (c->state == Closed || c->state == Closing) {
r->aux = 0;
respond(r, "listen on a closed connection");
threadexits(nil);
}
sc = c->chans[fnum & ConnMask];
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) {
r->aux = 0;
respond(r, "Listen on a closed connection");
threadexits(nil);
}
c->chans[i]->state = Established;
p2 = new_packet(c);
c->chans[i]->rwindow = 32*1024;
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, 32*1024);
hnputl(p2->payload + 13, 8192);
p2->rlength = 18;
n = finish_packet(p2);
filedup(r, c->chans[i]->ctl);
io = ioproc();
n = iowrite(io, c->datafd, p2->nlength, n);
free(p2);
closeioproc(io);
if (debug)
fprint(2, "*** Responding to chan listen open ***\n");
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 > 8192)
n = 8192;
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) {
sc->rwindow += 32*1024;
if (debug)
fprint(2, "Increasing receive window to %lud, inq %lud\n", 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, 32*1024);
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 fd, n, lev, cnum, xconn, fnum;
char buf[256], path[40];
fnum = (uintptr)r->fid->file->aux;
lev = fnum >> LEVSHIFT;
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
if (lev != 0 || (fnum & FileMask) != ReqRemFile) {
respond(r, "Invalid connection");
return;
}
cnum = 0;
sc = nil;
}
else {
cnum = fnum & ConnMask;
sc = c->chans[cnum];
}
switch (fnum & FileMask) {
case CtlFile:
case ListenFile:
if (r->ifcall.offset != 0) {
respond(r, nil);
break;
}
switch (lev) {
case 0:
readstr(r, st_names[c->state]);
break;
case 1:
snprint(buf, 256, "%d", xconn);
readstr(r, buf);
break;
case 2:
snprint(buf, 256, "%d", cnum);
readstr(r, buf);
break;
default:
snprint(buf, 256, "Internal error: level %d", lev);
respond(r, buf);
return;
break;
}
respond(r, nil);
break;
case CloneFile:
if (r->ifcall.offset != 0) {
respond(r, nil);
break;
}
readstr(r, "Congratulations, you've achieved the impossible\n");
respond(r, nil);
break;
case DataFile:
if (lev == 0) {
respond(r, nil);
break;
}
if (lev == 1) {
if (c->cap)
readstr(r, c->cap);
respond(r, nil);
break;
}
r->aux = (void *)threadcreate(readdata, r, 8192);
break;
case LocalFile:
if (lev == 1) {
if (c->ctlfd >= 0) {
n = pread(c->ctlfd, buf, 10, 0);
buf[n] = '\0';
snprint(path, 40, "%s/tcp/%s/local", mntpt, buf);
fd = open(path, OREAD);
n = pread(fd, buf, 255, 0);
close(fd);
buf[n] = '\0';
readstr(r, buf);
}
else
readstr(r, "::!0\n");
}
respond(r, nil);
break;
case ReqRemFile:
r->aux = (void *)threadcreate(readreqrem, r, 8192);
break;
case StatusFile:
switch (lev) {
case 0:
readstr(r, "Impossible");
break;
case 1:
if (c->state < 0 || c->state > Closed)
readstr(r, "Unknown");
else
readstr(r, st_names[c->state]);
break;
case 2:
if (sc->state < 0 || sc->state > Closed)
readstr(r, "Unknown");
else
readstr(r, st_names[sc->state]);
break;
}
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, fnum;
char buf[256], path[40];
r = a;
fnum = (uintptr)r->fid->file->aux;
lev = fnum >> LEVSHIFT;
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
if (lev != 0) {
respond(r, "Invalid connection");
return;
}
sc = nil;
}
else {
cnum = fnum & ConnMask;
sc = c->chans[cnum];
}
switch (lev) {
case 0:
if (r->ifcall.offset == 0 && keymbox.state != Empty) {
r->aux = 0;
respond(r, "Key file collision");
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;
while (1) {
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 1:
if (c->ctlfd >= 0) {
io = ioproc();
seek(c->ctlfd, 0, 0);
n = ioread(io, c->ctlfd, buf, 10);
if (n < 0) {
r->aux = 0;
responderror(r);
closeioproc(io);
break;
}
buf[n] = '\0';
snprint(path, 40, "%s/tcp/%s/remote", mntpt, buf);
if ((fd = ioopen(io, path, OREAD)) < 0 || (n = ioread(io, fd, buf, 255)) < 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 2:
if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->reqq == nil && sc->dataq == nil) {
if (debug)
fprint(2, "Sending EOF1 to channel request listener\n");
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) {
if (debug)
fprint(2, "Sending EOF2 to channel request listener\n");
r->aux = 0;
respond(r, nil);
threadexits(nil);
}
}
n = r->ifcall.count;
if (sc->reqq->rem < n)
n = sc->reqq->rem;
if (n > 8192)
n = 8192;
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, fnum;
r = a;
fnum = (uintptr)r->fid->file->aux;
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
respond(r, "Invalid connection");
threadexits(nil);
}
cnum = fnum & ConnMask;
sc = c->chans[cnum];
if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->dataq == nil) {
if (debug)
fprint(2, "Sending EOF1 to channel listener\n");
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) {
if (debug)
fprint(2, "Got intterrupt/error in readdata %r\n");
r->aux = 0;
responderror(r);
threadexits(nil);
}
if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->dataq == nil) {
if (debug)
fprint(2, "Sending EOF2 to channel listener\n");
r->aux = 0;
respond(r, nil);
threadexits(nil);
}
}
getdata(c, sc, r);
threadexits(nil);
}
void
stwrite(Req *r)
{
Conn *c;
SSHChan *ch;
int lev, fnum, xconn;
fnum = (uintptr)r->fid->file->aux;
lev = fnum >> LEVSHIFT;
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
respond(r, "Invalid connection");
return;
}
ch = c->chans[fnum & ConnMask];
switch (fnum & FileMask) {
case CloneFile:
case CtlFile:
r->aux = (void *)threadcreate(writectl, r, 8192);
break;
case DataFile:
r->ofcall.count = r->ifcall.count;
if (lev < 2) {
respond(r, nil);
break;
}
if (c->state == Closed || c->state == Closing || ch->state == Closed || ch->state == Closing) {
respond(r, nil);
break;
}
r->aux = (void *)threadcreate(writedata, r, 8192);
break;
case ReqRemFile:
r->aux = (void *)threadcreate(writereqrem, r, 8192);
break;
default:
respond(r, nil);
break;
}
}
void
writectl(void *a)
{
Req *r;
Packet *p;
Conn *c;
SSHChan *ch;
char *q, *buf, *toks[4],*attrs[5];
int n, ntok, lev, fnum, xconn;
char path[40], buf2[10];
r = a;
fnum = (uintptr)r->fid->file->aux;
lev = fnum >> LEVSHIFT;
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
respond(r, "Invalid connection");
threadexits(nil);
}
ch = c->chans[fnum & ConnMask];
if (r->ifcall.count <= 10)
buf = emalloc9p(11);
else
buf = emalloc9p(r->ifcall.count + 1);
memmove(buf, r->ifcall.data, r->ifcall.count);
buf[r->ifcall.count] = '\0';
ntok = tokenize(buf, toks, 4);
switch (lev) {
case 0:
break;
case 1:
if (strcmp(toks[0], "connect") == 0) {
if (ntok < 2) {
r->aux = 0;
free(buf);
respond(r, "Invalid connect request");
threadexits(nil);
}
memset(buf2, '\0', sizeof(buf2));
pread(c->ctlfd, buf2, 10, 0);
fprint(c->ctlfd, "connect %s %s", toks[1], ntok > 3 ? toks[2] : "");
c->role = Client;
/* Override the PKA list; we can take any in */
pkas[0] = &rsa_pka;
pkas[1] = &dss_pka;
pkas[2] = nil;
q = estrdup9p(buf2);
if (dohandshake(c, q) < 0) {
r->aux = 0;
respond(r, "handshake failed");
free(q);
free(buf);
threadexits(nil);
}
free(q);
keymbox.state = Empty;
nbsendul(keymbox.mchan, 1);
break;
}
if (c->state == Closed || c->state == Closing) {
r->aux = 0;
respond(r, "connection closed");
free(buf);
threadexits(nil);
}
if (strcmp(toks[0], "ssh-userauth") == 0) {
if (ntok < 3 || ntok > 4) {
r->aux = 0;
respond(r, "Invalid connection command");
free(buf);
threadexits(nil);
}
if (!c->service)
c->service = estrdup9p(toks[0]);
if (c->user)
free(c->user);
c->user = estrdup9p(toks[2]);
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]);
}
else {
if (c->password) {
free(c->password);
c->password = nil;
}
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);
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) {
r->aux = 0;
respond(r, "no auth info");
free(buf);
threadexits(nil);
}
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);
if (c->dio) {
if (iowrite(c->dio, c->datafd, p->nlength, n) < 0) {
r->aux = 0;
responderror(r);
free(p);
free(buf);
threadexits(nil);
}
}
else {
if (write(c->datafd, p->nlength, n) < 0) {
r->aux = 0;
responderror(r);
free(p);
free(buf);
threadexits(nil);
}
}
free(p);
}
else {
if (client_auth(c, c->dio) < 0) {
r->aux = 0;
respond(r, "auth failure");
free(buf);
threadexits(nil);
}
}
qlock(&c->l);
if (c->state != Established)
rsleep(&c->r);
qunlock(&c->l);
if (c->state != Established) {
r->aux = 0;
respond(r, "Authentication failed");
free(buf);
threadexits(nil);
}
break;
}
else if (strcmp(toks[0], "ssh-connection") == 0) {
}
else if (strcmp(toks[0], "hangup") == 0) {
if (c->rpid >= 0) {
threadint(c->rpid);
}
shutdown(c);
}
else if (strcmp(toks[0], "announce") == 0) {
if (debug)
print("Got %s argument for announce\n", toks[1]);
write(c->ctlfd, r->ifcall.data, r->ifcall.count);
}
else if (strcmp(toks[0], "accept") == 0) {
memset(buf2, '\0', sizeof(buf2));
pread(c->ctlfd, buf2, 10, 0);
fprint(c->ctlfd, "accept %s", buf2);
c->role = Server;
q = estrdup9p(buf2);
if (dohandshake(c, q) < 0) {
r->aux = 0;
respond(r, "handshake failed");
free(q);
shutdown(c);
free(buf);
threadexits(nil);
}
free(q);
}
else if (strcmp(toks[0], "reject") == 0) {
memset(buf2, '\0', sizeof(buf2));
pread(c->ctlfd, buf2, 10, 0);
snprint(path, 40, "%s/tcp/%s/data", mntpt, buf2);
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 2:
if (c->state == Closed || c->state == Closing) {
r->aux = 0;
respond(r, "connection closed");
free(buf);
threadexits(nil);
}
if (strcmp(toks[0], "connect") == 0) {
p = new_packet(c);
add_byte(p, SSH_MSG_CHANNEL_OPEN);
if (ntok > 1)
add_string(p, toks[1]);
else
add_string(p, "session");
add_uint32(p, ch->id);
add_uint32(p, 32*1024);
add_uint32(p, 8192);
/* 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);
break;
}
else if (strcmp(toks[0], "global") == 0) {
}
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] || 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) {
if (debug)
print("Got %s argument for announce\n", toks[1]);
if (ch->ann)
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
writereqrem(void *a)
{
Req *r;
Packet *p;
Conn *c;
SSHChan *ch;
char *q, *buf, *toks[4];
int n, ntok, lev, fnum, xconn;
char *cmd;
r = a;
fnum = (uintptr)r->fid->file->aux;
lev = fnum >> LEVSHIFT;
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
respond(r, "Invalid connection");
threadexits(nil);
}
ch = c->chans[fnum & ConnMask];
if (r->ifcall.count <= 10)
buf = emalloc9p(11);
else
buf = emalloc9p(r->ifcall.count + 1);
memmove(buf, r->ifcall.data, r->ifcall.count);
buf[r->ifcall.count] = '\0';
ntok = tokenize(buf, toks, 4);
if (lev == 0) {
if (keymbox.msg)
free(keymbox.msg);
keymbox.msg = buf;
nbsendul(keymbox.mchan, 1);
r->ofcall.count = r->ifcall.count;
r->aux = 0;
respond(r,nil);
threadexits(nil);
}
r->ofcall.count = r->ifcall.count;
if (c->state == Closed || c->state == Closing || ch->state == Closed || ch->state == Closing) {
r->aux = 0;
respond(r, nil);
free(buf);
threadexits(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);
if (debug)
fprint(2, "Sending shell request: rlength=%lud twindow=%lud\n", 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 = malloc(1024);
q = seprint(cmd, cmd+1024, "%s", toks[1]);
for (n = 2; n < ntok; ++n) {
q = seprint(q, cmd+1024, " %s", toks[n]);
if (q == nil)
break;
}
add_string(p, cmd);
free(cmd);
}
else {
r->aux = 0;
respond(r, "invalid request command");
free(buf);
threadexits(nil);
}
n = finish_packet(p);
iowrite(c->dio, c->datafd, p->nlength, n);
free(p);
r->aux = 0;
respond(r, nil);
free(buf);
threadexits(nil);
}
void
writedata(void *a)
{
Req *r;
Packet *p;
Conn *c;
SSHChan *ch;
int n, fnum, xconn;
r = a;
fnum = (uintptr)r->fid->file->aux;
xconn = (fnum >> CONNSHIFT) & ConnMask;
c = connections[xconn];
if (c == nil) {
respond(r, "Invalid connection");
threadexits(nil);
}
ch = c->chans[fnum & ConnMask];
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) {
iowrite(c->dio, c->datafd, p->nlength, n);
r->aux = 0;
respond(r, nil);
free(p);
threadexits(nil);
}
qlock(&ch->xmtlock);
while (ch->sent + p->rlength > ch->twindow)
rsleep(&ch->xmtrendez);
qunlock(&ch->xmtlock);
iowrite(c->dio, c->datafd, p->nlength, n);
free(p);
r->aux = 0;
respond(r, nil);
threadexits(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, fnum, lev, cnum, chnum;
if (f == nil || f->file == nil)
return;
fnum = (uintptr)f->file->aux;
lev = fnum >> LEVSHIFT;
cnum = (fnum >> CONNSHIFT) & ConnMask;
chnum = fnum & ConnMask;
if (debug)
fprint(2, "Got destroy fid on file: %x %d %d %d: %s\n", fnum, lev, cnum, chnum, f->file->name);
if (lev == 0 && fnum == ReqRemFile) {
if (keymbox.state != Empty) {
keymbox.state = Empty;
//nbsendul(keymbox.mchan, 1);
}
keymbox.msg = nil;
return;
}
c = connections[cnum];
if (c == nil)
return;
if (lev == 1 && (fnum & FileMask) == CtlFile
&& (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 == 2) {
if ((fnum & FileMask) == ListenFile && sc->state == Listening) {
qlock(&c->l);
if (sc->state != Closed) {
sc->state = Closed;
chanclose(sc->inchan);
chanclose(sc->reqchan);
}
qunlock(&c->l);
}
else if ((fnum & FileMask) == DataFile && sc->state != Empty
&& sc->state != Closed && sc->state != Closing) {
if (f->file != sc->data && f->file != sc->request) {
fprint(2, "Great evil is upon us destroying a fid we didn't create\n");
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)
{
int fnum;
fnum = (uintptr)r->oldreq->fid->file->aux;
if (debug)
fprint(2, "Got flush on file %x %d %d %d: %s %p\n", fnum, fnum >> LEVSHIFT, (fnum >> CONNSHIFT) & ConnMask, fnum & ConnMask, r->oldreq->fid->file->name, r->oldreq->aux);
if (r->oldreq->aux) {
if (r->oldreq->ifcall.type == Topen && (fnum & FileMask) == ListenFile && (fnum >> LEVSHIFT) == 1) {
threadint((uintptr)r->oldreq->aux);
}
else if(r->oldreq->ifcall.type == Tread && (fnum & FileMask) == DataFile && (fnum >> LEVSHIFT) == 2) {
threadint((uintptr)r->oldreq->aux);
}
else if(r->oldreq->ifcall.type == Tread && (fnum & FileMask) == ReqRemFile) {
threadint((uintptr)r->oldreq->aux);
}
else {
threadkill((uintptr)r->oldreq->aux);
r->oldreq->aux = 0;
respond(r->oldreq, "interrupted");
}
}
else
respond(r->oldreq, "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)
{
static QLock aclock;
Conn *c;
int sconn, slev, i, s, firstnil;
char buf[20];
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) {
connections[firstnil] = emalloc9p(sizeof (Conn));
memset(connections[firstnil], 0, sizeof (Conn));
i = firstnil;
}
else {
qunlock(&aclock);
return nil;
}
}
sconn = i << CONNSHIFT;
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->user = nil;
c->service = nil;
c->nchan = 0;
c->ctlfd = -1;
c->datafd = -1;
c->rpid = -1;
c->inseq = 0;
c->outseq = 0;
c->cscrypt = -1;
c->sccrypt = -1;
c->csmac = -1;
c->scmac = -1;
c->ncscrypt = -1;
c->nsccrypt = -1;
c->ncsmac = -1;
c->nscmac = -1;
c->encrypt = -1;
c->decrypt = -1;
c->outmac = -1;
c->inmac = -1;
if (c->e) {
mpfree(c->e);
c->e = nil;
}
if (c->x) {
mpfree(c->x);
c->x = nil;
}
slev = 1 << LEVSHIFT;
snprint(buf, 20, "%d", i);
if (c->dir == nil) {
c->dir = createfile(rootfile, buf, uid, 0555|DMDIR, (void *)(slev | sconn));
c->clonefile = createfile(c->dir, "clone", uid, 0666, (void *)(slev | CloneFile | sconn));
c->ctlfile = createfile(c->dir, "ctl", uid, 0666, (void *)(slev | CtlFile | sconn));
c->datafile = createfile(c->dir, "data", uid, 0666, (void *)(slev | DataFile | sconn));
c->listenfile = createfile(c->dir, "listen", uid, 0666, (void *)(slev | ListenFile | sconn));
c->localfile = createfile(c->dir, "local", uid, 0444, (void *)(slev | LocalFile | sconn));
c->remotefile = createfile(c->dir, "remote", uid, 0444, (void *)(slev | ReqRemFile | sconn));
c->statusfile = createfile(c->dir, "status", uid, 0444, (void *)(slev | StatusFile | sconn));
}
// c->skexinit = nil;
// c->rkexinit = nil;
c->got_sessid = 0;
c->otherid = nil;
c->inik = nil;
c->outik = nil;
c->s2ccs = nil;
c->c2scs = nil;
c->enccs = nil;
c->deccs = nil;
qunlock(&aclock);
return c;
}
SSHChan *
alloc_chan(Conn *c)
{
SSHChan *sc;
Plist *p;
int cnum, slev, sconn;
char buf[10];
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, 10, "%d", cnum);
memset(&sc->r, '\0', sizeof(Rendez));
sc->r.l = &c->l;
sc->id = cnum;
sc->otherid = -1;
sc->state = Empty;
sc->waker = -1;
sc->conn = c->id;
sc->sent = 0;
sc->twindow = 0;
sc->rwindow = 0;
sc->inrqueue = 0;
sc->ann = nil;
sc->lreq = nil;
slev = 2 << LEVSHIFT;
sconn = c->id << CONNSHIFT;
if (sc->dir == nil) {
sc->dir = createfile(c->dir, buf, uid, 0555|DMDIR, (void *)(slev | sconn | cnum));
sc->ctl = createfile(sc->dir, "ctl", uid, 0666, (void *)(slev | CtlFile | sconn | cnum));
sc->data = createfile(sc->dir, "data", uid, 0666, (void *)(slev | DataFile | sconn | cnum));
sc->listen = createfile(sc->dir, "listen", uid, 0666, (void *)(slev | ListenFile | sconn | cnum));
sc->request = createfile(sc->dir, "request", uid, 0666,
(void *)(slev | ReqRemFile | sconn | cnum));
sc->status = createfile(sc->dir, "status", uid, 0444, (void *)(slev | StatusFile | sconn | cnum));
}
c->nchan++;
sc->dataq = nil;
sc->datatl = nil;
while (sc->reqq != nil) {
p = sc->reqq;
sc->reqq = p->next;
free(p->pack);
free(p);
}
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;
}
int
dohandshake(Conn *c, char *tcpchan)
{
Ioproc *io;
char *p;
int fd, n;
char path[256], buf[32];
io = ioproc();
snprint(path, 256, "%s/tcp/%s/remote", mntpt, tcpchan);
fd = ioopen(io, path, OREAD);
n = ioread(io, fd, buf, 31);
if (n > 0) {
buf[n] = 0;
p = strchr(buf, '!');
if (p)
*p = 0;
if (c->remote)
free(c->remote);
c->remote = estrdup9p(buf);
}
ioclose(io, fd);
snprint(path, 256, "%s/tcp/%s/data", mntpt, tcpchan);
fd = ioopen(io, path, ORDWR);
if (fd < 0) {
closeioproc(io);
return -1;
}
c->datafd = fd;
/* exchange versions--we're snobbishly only doing SSH2 */
snprint(path, 256, "%s\r\n", MYID);
iowrite(io, fd, path, strlen(path));
p = path;
n = 0;
do {
if (ioread(io, fd, p, 1) < 0) {
fprint(2, "Read failure in ID exchange: %r\n");
break;
}
++n;
} while (*p++ != '\n');
if (n < 5) {
close(fd);
snprint(path, 256, "%s/tcp/%s/ctl", mntpt, tcpchan);
fd = ioopen(io, path, OWRITE);
iowrite(io, fd, "hangup", 6);
ioclose(io, fd);
closeioproc(io);
return -1;
}
*p = 0;
if (debug)
fprint(2, "id string: %d:%s", n, path);
if (strncmp(path, "SSH-2", 5) != 0 && strncmp(path, "SSH-1.99", 8) != 0) {
ioclose(io, fd);
snprint(path, 256, "%s/tcp/%s/ctl", mntpt, tcpchan);
fd = ioopen(io,path, OWRITE);
iowrite(io, fd, "hangup", 6);
ioclose(io, fd);
closeioproc(io);
return -1;
}
closeioproc(io);
if (c->otherid)
free(c->otherid);
c->otherid = estrdup9p(path);
for (n = strlen(c->otherid) - 1; c->otherid[n] == '\r' || c->otherid[n] == '\n'; --n)
c->otherid[n] = '\0';
c->state = Initting;
/* start the reader thread */
if (c->rpid < 0)
c->rpid = threadcreate(reader, c, 8192);
/*
* 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)
return -1;
if (c->role == Client && c->state != Negotiating)
return -1;
return 0;
}
void
send_kexinit(Conn *c)
{
Packet *ptmp;
char *p, *e;
int msglen;
int i;
char *buf;
if (debug)
fprint(2, "Initializing kexinit packet\n");
if (c->skexinit != nil)
free(c->skexinit);
c->skexinit = new_packet(c);
buf = emalloc9p(1024);
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+1023;
p = seprint(buf, e, "%s", kexes[0]->name);
for (i = 1; i < nelem(kexes); ++i)
p = seprint(p, e, ",%s", kexes[i]->name);
if (debug)
fprint(2, "Sent KEX algs: %s\n", 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);
if (debug)
fprint(2, "Sent host key algs: %s\n", 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);
if (debug)
fprint(2, "Sent crypto algs: %s\n", 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]);
if (debug)
fprint(2, "Sent MAC algs: %s\n", 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);
}
void
reader(void *a)
{
Packet *p, *p2;
Plist *pl;
Conn *c;
SSHChan *ch;
uchar *q;
int i, n,nl, np, nm, nb, cnum;
char buf[256];
c = a;
c->rpid = threadid();
if (debug)
fprint(2, "Starting reader for connection %d, pid:%d\n", c->id, c->rpid);
threadsetname("reader");
p = new_packet(c);
p2 = new_packet(c);
c->rio = ioproc();
while (1) {
nm = 0;
nb = 4;
if (c->decrypt != -1)
nb = cryptos[c->decrypt]->blklen;
if (debug)
fprint(2, "calling read for connection %d, state %d, nb %d, dc %d\n",
c->id, c->state, nb, c->decrypt);
if ((nl = ioreadn(c->rio, c->datafd, p->nlength, nb)) != nb) {
if (debug)
fprint(2, "Reader for connection %d exiting, nl=%d: %r\n", c->id, nl);
goto bail;
}
if (c->decrypt != -1)
cryptos[c->decrypt]->decrypt(c->deccs, p->nlength, nb);
p->rlength = nhgetl(p->nlength);
if (debug)
fprint(2, "Got message length: %ld\n", p->rlength);
if (p->rlength > 35000) {
if (debug)
fprint(2, "Absurd packet length: %ld, unrecoverable decrypt failure\n", p->rlength);
goto bail;
}
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, 20);
n = nl + np + nm;
if (debug) {
fprint(2, "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])
fprint(2, " for channel %d win %lud", i, c->chans[i]->rwindow);
else
fprint(2, " for invalid channel %d", i);
}
fprint(2, ": first byte: %d\n", p->payload[0]);
}
if (np != p->rlength + 4 - nb || c->inmac != -1 && nm != 20) {
if (debug)
fprint(2, "Got EOF/error on connection read: %d %d %r\n", np, nm);
goto bail;
}
p->tlength = n;
p->rlength = n - 4;
if (undo_packet(p) < 0) {
if (debug)
fprint(2, "Bad packet in connection %d: exiting\n", c->id);
goto bail;
}
if (c->state == Initting) {
if (p->payload[0] != SSH_MSG_KEXINIT) {
if (debug)
fprint(2, "Missing KEX init packet: %d\n", p->payload[0]);
goto bail;
}
if (c->rkexinit)
free(c->rkexinit);
c->rkexinit = new_packet(c);
memmove(c->rkexinit, p, sizeof(Packet));
if (validatekex(c, p) < 0) {
if (debug)
fprint(2, "Algorithm mismatch\n");
goto bail;
}
if (debug)
fprint(2, "Using %s Kex algorithm and %s PKA\n",
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) {
switch (p->payload[0]) {
case SSH_MSG_DISCONNECT:
if (debug) {
get_string(p, p->payload + 5, buf, 256, nil);
fprint(2, "Got disconnect: %s\n", buf);
}
goto bail;
break;
case SSH_MSG_NEWKEYS:
/*
* If we're just updating, go straight to established,
* otherwise wait for authentication
*/
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;
}
if (debug)
fprint(2, "Using %s for encryption and %s for decryption\n",
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_NEWKEYS);
n = finish_packet(p2);
iowrite(c->rio, c->datafd, p2->nlength, n);
qlock(&c->l);
rwakeup(&c->r);
qunlock(&c->l);
}
else{
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);
threadexits(nil);
}
break;
case SSH_MSG_SERVICE_REQUEST:
get_string(p, p->payload + 1, buf, 256, nil);
if (debug)
fprint(2, "Got service request: %s\n", buf);
if (strcmp(buf, "ssh-userauth") == 0 || strcmp(buf, "ssh-connection") == 0) {
init_packet(p2);
p2->c = c;
if (slfd > 0)
fprint(slfd, "ssh connection from %s\n", c->remote);
else
syslog(1, "ssh", "ssh connection from %s", c->remote);
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);
goto bail;
}
break;
case SSH_MSG_SERVICE_ACCEPT:
get_string(p, p->payload + 1, buf, 256, nil);
if (c->service && strcmp(c->service, "ssh-userauth") == 0) {
free(c->service);
c->service = estrdup9p("ssh-connection");
}
if (debug)
fprint(2, "Got service accept: %s: responding with %s %s\n", 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;
default:
break;
}
}
else if (c->state == Authing) {
switch (p->payload[0]) {
case SSH_MSG_DISCONNECT:
if (debug) {
get_string(p, p->payload + 5, buf, 256, nil);
fprint(2, "Got disconnect: %s\n", buf);
}
goto bail;
break;
case SSH_MSG_USERAUTH_REQUEST:
switch (auth_req(p, c)) {
case 0:
qlock(&c->l);
c->state = Established;
rwakeup(&c->r);
qunlock(&c->l);
break;
case -1:
break;
case -2:
goto bail;
break;
}
break;
case SSH_MSG_USERAUTH_FAILURE:
qlock(&c->l);
rwakeup(&c->r);
qunlock(&c->l);
break;
case SSH_MSG_USERAUTH_SUCCESS:
qlock(&c->l);
c->state = Established;
rwakeup(&c->r);
qunlock(&c->l);
break;
case SSH_MSG_USERAUTH_BANNER:
break;
}
}
else if (c->state == Established) {
if (debug >1) {
fprint(2, "In Established state, got:\n");
dump_packet(p);
}
switch (p->payload[0]) {
case SSH_MSG_DISCONNECT:
if (debug) {
get_string(p, p->payload + 5, buf, 256, nil);
fprint(2, "Got disconnect: %s\n", buf);
}
goto bail;
break;
case SSH_MSG_IGNORE:
break;
case SSH_MSG_UNIMPLEMENTED:
break;
case SSH_MSG_DEBUG:
if (debug || p->payload[1]) {
get_string(p, p->payload + 2, buf, 256, nil);
fprint(2, "Got debug message: %s\n", 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) {
if (debug)
fprint(2, "Algorithm mismatch\n");
goto bail;
}
if (debug)
fprint(2, "Using %s Kex algorithm and %s PKA\n",
kexes[c->kexalg]->name, pkas[c->pkalg]->name);
c->state = Negotiating;
break;
case SSH_MSG_GLOBAL_REQUEST:
break;
case SSH_MSG_REQUEST_SUCCESS:
break;
case SSH_MSG_REQUEST_FAILURE:
break;
case SSH_MSG_CHANNEL_OPEN:
q = get_string(p, p->payload + 1, buf, 256, nil);
if (debug)
fprint(2, "Searching for a listener for channel type %s\n", buf);
ch = alloc_chan(c);
if (ch == nil) {
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);
break;
}
if (debug)
fprint(2, "alloced channel %d for listener\n", ch->id);
qlock(&c->l);
ch->otherid = nhgetl(q);
ch->twindow = nhgetl(q+4);
if (debug)
fprint(2, "Got lock in channel open\n");
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) {
if (debug)
fprint(2, "No listener: sleeping\n");
ch->state = Opening;
if (ch->ann)
free(ch->ann);
ch->ann = estrdup9p(buf);
if (debug)
fprint(2, "Waiting for someone to announce %s\n", ch->ann);
rsleep(&ch->r);
}
else{
if (debug)
fprint(2, "Found listener on channel %d\n", 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);
goto bail;
break;
case SSH_MSG_CHANNEL_WINDOW_ADJUST:
cnum = nhgetl(p->payload + 1);
ch = c->chans[cnum];
ch->twindow += nhgetl(p->payload + 5);
if (debug)
fprint(2, "New twindow for channel: %d: %lud\n", 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) {
goto bail;
}
break;
case SSH_MSG_CHANNEL_REQUEST:
cnum = nhgetl(p->payload + 1);
ch = c->chans[cnum];
if (debug)
fprint(2, "Queueing channel request for channel: %d\n", cnum);
q = get_string(p, p->payload+5, buf, 256, nil);
pl = emalloc9p(sizeof(Plist));
pl->pack = emalloc9p(sizeof(Packet));
n = snprint((char *)pl->pack->payload, 32768, "%s %c", buf, *q ? 't': 'f');
if (debug)
fprint(2, "request message begins: %s\n", (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:
break;
case SSH_MSG_CHANNEL_FAILURE:
break;
default:
break;
}
}
else {
if (debug)
fprint(2, "Connection %d in invalid state, reader exiting\n", c->id);
bail:
shutdown(c);
free(p);
free(p2);
if (c->rio) {
closeioproc(c->rio);
c->rio = nil;
}
c->rpid = -1;
threadexits(nil);
}
}
}
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[128];
int i, j, n;
char *buf;
buf = emalloc9p(1024);
q = p->payload + 17;
q = get_string(p, q, buf, 1024, nil);
if (debug)
fprint(2, "Received KEX algs: %s\n", buf);
n = gettokens(buf, toks, 128, ",");
for (i = 0; i < n; ++i)
for (j = 0; j < nelem(kexes); ++j)
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, 1024, nil);
if (debug)
fprint(2, "Received host key algs: %s\n", buf);
n = gettokens(buf, toks, 128, ",");
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;
free(buf);
return -1;
foundpka:
p->c->pkalg = j;
q = get_string(p, q, buf, 1024, nil);
if (debug)
fprint(2, "Received C2S crypto algs: %s\n", buf);
n = gettokens(buf, toks, 128, ",");
for (i = 0; i < n; ++i)
for (j = 0; j < nelem(cryptos); ++j)
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, 1024, nil);
if (debug)
fprint(2, "Received S2C crypto algs: %s\n", buf);
n = gettokens(buf, toks, 128, ",");
for (i = 0; i < n; ++i)
for (j = 0; j < nelem(cryptos); ++j)
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, 1024, nil);
if (debug)
fprint(2, "Received C2S MAC algs: %s\n", buf);
n = gettokens(buf, toks, 128, ",");
for (i = 0; i < n; ++i)
for (j = 0; j < nelem(macnames); ++j)
if (strcmp(toks[i], macnames[j]) == 0)
goto foundm1;
free(buf);
return -1;
foundm1:
p->c->ncsmac = j;
q = get_string(p, q, buf, 1024, nil);
if (debug)
fprint(2, "Received S2C MAC algs: %s\n", buf);
n = gettokens(buf, toks, 128, ",");
for (i = 0; i < n; ++i)
for (j = 0; j < nelem(macnames); ++j)
if (strcmp(toks[i], macnames[j]) == 0)
goto foundm2;
free(buf);
return -1;
foundm2:
p->c->nscmac = j;
q = get_string(p, q, buf, 1024, nil);
q = get_string(p, q, buf, 1024, nil);
q = get_string(p, q, buf, 1024, nil);
q = get_string(p, q, buf, 1024, nil);
free(buf);
if (*q)
return 1;
return 0;
}
int
validatekexc(Packet *p)
{
uchar *q;
char *toks[128];
int i, j, n;
char *buf;
buf = emalloc9p(1024);
q = p->payload + 17;
q = get_string(p, q, buf, 1024, nil);
n = gettokens(buf, toks, 128, ",");
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, 1024, nil);
n = gettokens(buf, toks, 128, ",");
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, 1024, nil);
n = gettokens(buf, toks, 128, ",");
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, 1024, nil);
n = gettokens(buf, toks, 128, ",");
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, 1024, nil);
n = gettokens(buf, toks, 128, ",");
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, 1024, nil);
n = gettokens(buf, toks, 128, ",");
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, 1024, nil);
q = get_string(p, q, buf, 1024, nil);
q = get_string(p, q, buf, 1024, nil);
q = get_string(p, q, buf, 1024, nil);
free(buf);
if (*q)
return 1;
return 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)
{
uchar rand[20];
char *cap;
char *key;
int fd, nfrom, nto;
uchar hash[SHA1dlen];
fd = open("#¤/caphash", OWRITE);
/* create the capability */
nto = strlen(to);
nfrom = strlen(from);
cap = emalloc9p(nfrom+1+nto+1+sizeof(rand)*3+1);
snprint(cap, nfrom+1+nto+1+sizeof(rand)*3+1, "%s@%s", from, to);
memrandom(rand, sizeof(rand));
key = cap+nfrom+1+nto+1;
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] = '@';
if (write(fd, hash, SHA1dlen) < 0) {
close(fd);
free(cap);
return nil;
}
close(fd);
return cap;
}
int
auth_req(Packet *p, Conn *c)
{
Packet *p2;
AuthInfo *ai;
uchar *q;
char *service, *me, *user, *pw, *path;
char *alg, *blob, *sig;
int n, fd, retval, nblob, nsig;
char method[32];
char key1[DESKEYLEN], key2[DESKEYLEN];
service = emalloc9p(128);
me = emalloc9p(128);
user = emalloc9p(128);
pw = emalloc9p(128);
alg = emalloc9p(128);
blob = emalloc9p(512);
sig = emalloc9p(512);
path = emalloc9p(128);
retval = 0;
q = get_string(p, p->payload + 1, user, 128, nil);
if (c->user)
free(c->user);
c->user = estrdup9p(user);
q = get_string(p, q, service, 128, nil);
q = get_string(p, q, method, 32, nil);
if (debug) {
fprint(2, "Got userauth request: ");
fprint(2, " %s ", user);
fprint(2, " %s ", service);
fprint(2, " %s\n", method);
}
fd = open("/dev/user", OREAD);
n = read(fd, me, 127);
me[n] = '\0';
close(fd);
p2 = new_packet(c);
if (strcmp(method, "publickey") == 0) {
if (*q == 0) {
/* Should really check to see if this user can be authed this way */
q = get_string(p, q+1, alg, 128, nil);
get_string(p, q, blob, 512, &nblob);
for (n = 0; n < nelem(pkas) && pkas[n] != nil && strcmp(pkas[n]->name, alg) != 0; ++n) ;
if (n >= nelem(pkas) || pkas[n] == nil) {
add_byte(p2, SSH_MSG_USERAUTH_FAILURE);
add_string(p2, "password,publickey");
add_byte(p2, 0);
retval = -1;
}
else {
add_byte(p2, SSH_MSG_USERAUTH_PK_OK);
add_string(p2, alg);
add_block(p2, blob, nblob);
retval = -1;
}
}
else {
q = get_string(p, q+1, alg, 128, nil);
q = get_string(p, q, blob, 512, &nblob);
get_string(p, q, sig, 512, &nsig);
for (n = 0; n < nelem(pkas) && pkas[n] != nil && strcmp(pkas[n]->name, alg) != 0; ++n) ;
if (n >= nelem(pkas) || pkas[n] == nil) {
add_byte(p2, SSH_MSG_USERAUTH_FAILURE);
add_string(p2, "password,publickey");
add_byte(p2, 0);
retval = -1;
}
else {
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)) {
if (c->cap != nil)
free(c->cap);
c->cap = mkcap(me, user);
init_packet(p2);
p2->c = c;
if (slfd > 0)
fprint(slfd, "ssh logged in as %s\n", user);
else
syslog(1, "ssh", "ssh logged in as %s", user);
add_byte(p2, SSH_MSG_USERAUTH_SUCCESS);
}
else {
init_packet(p2);
p2->c = c;
if (slfd > 0)
fprint(slfd, "ssh public key login failure for %s\n", user);
else
syslog(1, "ssh", "ssh public key login failure for %s", user);
add_byte(p2, SSH_MSG_USERAUTH_FAILURE);
add_string(p2, "password,publickey");
add_byte(p2, 0);
retval = -1;
}
}
}
}
else if (strcmp(method, "password") == 0) {
++q;
get_string(p, q, pw, 128, nil);
if (debug > 1)
fprint(2, "%s\n", pw);
ai = nil;
if (kflag) {
if (passtokey(key1, pw) == 0)
goto answer;
snprint(path, 128, "/mnt/keys/%s/key", user);
if ((fd = open(path, OREAD)) < 0) {
werrstr("Invalid user");
goto answer;
}
if (read(fd, key2, DESKEYLEN) != DESKEYLEN) {
werrstr("Password mismatch");
goto answer;
}
close(fd);
if (memcmp(key1, key2, DESKEYLEN) != 0) {
werrstr("Password mismatch");
goto answer;
}
ai = emalloc9p(sizeof(AuthInfo));
ai->cuid = estrdup9p(user);
ai->suid = estrdup9p(me);
ai->cap = mkcap(me, user);
ai->nsecret = 0;
ai->secret = (uchar *)estrdup9p("");
}
else
ai = auth_userpasswd(user, pw);
answer:
if (ai == nil) {
if (debug)
fprint(2, "Auth error: %r\n");
if (slfd > 0)
fprint(slfd, "ssh login failure for %s: %r\n", user);
else
syslog(1, "ssh", "ssh login failure for %s: %r", user);
add_byte(p2, SSH_MSG_USERAUTH_FAILURE);
add_string(p2, "password,publickey");
add_byte(p2, 0);
retval = -1;
}
else{
if (debug)
fprint(2, "Auth successful: cuid is %s suid is %s cap is %s\n", ai->cuid, ai->suid, ai->cap);
if (c->cap != nil)
free(c->cap);
if (strcmp(user, me) == 0)
c->cap = estrdup9p("n/a");
else
c->cap = estrdup9p(ai->cap);
if (slfd > 0)
fprint(slfd, "ssh logged in as %s\n", user);
else
syslog(1, "ssh", "ssh logged in as %s", user);
add_byte(p2, SSH_MSG_USERAUTH_SUCCESS);
auth_freeAI(ai);
}
}
else {
add_byte(p2, SSH_MSG_USERAUTH_FAILURE);
add_string(p2, "password,publickey");
add_byte(p2, 0);
retval = -1;
}
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 retval;
}
int
client_auth(Conn *c, Ioproc *io)
{
Packet *p2, *p3, *p4;
char *r, *s;
mpint *ek, *nk;
int i, n;
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);
}
else {
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);
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); ++i) ;
if ((p3 = pkas[i]->sign(c, p4->payload, p4->rlength-1)) == nil) {
free(p4);
free(p2);
return -1;
}
add_block(p2, p3->payload, p3->rlength-1);
free(p3);
free(p4);
}
n = finish_packet(p2);
if (io)
iowrite(io, c->datafd, p2->nlength, n);
else
write(c->datafd, p2->nlength, n);
free(p2);
return 0;
}
char *
factlookup(int nattr, int nreq, char *attrs[])
{
Biobuf *bp;
char *buf, *toks[32], *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, 32, " ");
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;
if (debug)
fprint(2, "Shutting down connection %d\n", 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;
if (debug)
fprint(2, "clone %ld ctl %ld data %ld listen %ld local %ld remote %ld status %ld\n",
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) {
if (slfd > 0)
fprint(slfd, "closing ssh connection from %s\n", c->remote);
else
syslog(1, "ssh", "closing ssh connection from %s", c->remote);
}
fprint(c->ctlfd, "hangup");
close(c->ctlfd);
close(c->datafd);
if (c->dio) {
closeioproc(c->dio);
c->dio = nil;
}
c->decrypt = -1;
c->inmac = -1;
c->ctlfd = -1;
c->datafd = -1;
c->nchan = 0;
free(c->otherid);
if (c->s2ccs) {
free(c->s2ccs);
c->s2ccs = nil;
}
if (c->c2scs) {
free(c->c2scs);
c->c2scs = nil;
}
if (c->remote) {
free(c->remote);
c->remote = nil;
}
if (c->x) {
mpfree(c->x);
c->x = nil;
}
if (c->e) {
mpfree(c->e);
c->e = nil;
}
if (c->user) {
free(c->user);
c->user = nil;
}
if (c->service) {
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;
if (sc->ann) {
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);
if (debug)
fprint(2, "Done processing shutdown of connection %d\n", c->id);
}
|