Plan 9 from Bell Labs’s /usr/web/sources/contrib/blstuart/ssh/sshtun.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


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

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].