Plan 9 from Bell Labs’s /usr/web/sources/contrib/quanstro/root/sys/src/cmd/ssh2/ssh.c

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


#include <u.h>
#include <libc.h>
#include <auth.h>

extern int getgeom(int *cols, int *lines, int *width, int *height);

int isatty(int);
int doauth(int, char *);

char *user, *remote;
char *netdir, *subsystem;
int debug = 0;
static int stripcr = 0;
static int mflag = 0;
static int cooked = 0;
static int iflag = -1;
static int nopw = 0, nopka = 0;
static int chpid;
static int reqfd, dfd1, cfd1, dfd2, cfd2, consfd, kconsfd, cctlfd, notefd, keyfd, netpid, kbdpid;

void
usage(void)
{
	fprint(2, "usage: ssh [-CdkKmr] [-s subsystem] [-l user] [-n dir] [-z attr=val] addr [cmd [args]]\n");
	exits("usage");
}

int
handler(void *, char *note)
{
	char *p;
	int fd;

	if(strstr(note, "interrupt") != nil)
		return 1;
	if (chpid) {
		p = smprint("/proc/%d/note", chpid);
		fd = open(p, OWRITE);
		free(p);
		fprint(fd, "interrupt");
		close(fd);
	}
	if (iflag){
		if(!cooked)
			fprint(cctlfd, "rawoff");
		close(cctlfd);
		close(consfd);
	}
	fprint(reqfd, "close");
	close(reqfd);
	close(dfd2);
	close(dfd1);
	close(cfd2);
	close(cfd1);
	write(notefd, "kill", 4);
	close(notefd);
	return 1;
}

int
cmdmode(void)
{
	int n, m;
	char buf[256];

	while (1) {
reprompt:
		write(1, "\n>>> ", 5);
		n = 0;
		do {
			m = read(0, buf + n, 255 - n);
			write(1, buf + n, m);
			n += m;
			buf[n] = '\0';
			if (buf[n-1] == 0x15)
				goto reprompt;
		} while (buf[n-1] != '\n' && buf[n-1] != '\r');
		switch (buf[0]) {
		case '\n':
		case '\r':
			break;
		case 'q':
			return 1;
		case 'c':
			return 0;
		case 'C':
			cooked = 1 - cooked;
			if(cooked)
				fprint(cctlfd, "rawoff");
			else
				fprint(cctlfd, "rawon");
			return 0;
		case 'r':
			stripcr = 1 - stripcr;
			return 0;
		default:
			print("C - toggle cooked (local echo) mode\n");
			print("c - continue\n");
			print("h - help\n");
			print("q - quit\n");
			print("r - toggle carriage return stripping\n");
			break;
		}
	}
}

static int
wasintr(void)
{
	char err[ERRMAX];

	rerrstr(err, sizeof err);
	return strstr(err, "interrupt") != nil;
}

void
main(int argc, char *argv[])
{
	char *p, *q, *path;
	char *whichkey;
	int cols, lines, width, height;
	int conn, eofs, chan, n, i, lstart, nfd;
	char buf[32*1024];

	keyfd = -1;
	whichkey = nil;
	ARGBEGIN {
	case 'C':
		cooked = 1;
		break;
	case 'd':
		debug++;
		break;
	case 'l':
		user = EARGF(usage());
		break;
	case 'r':
		stripcr = 1;
		break;
	case 'I':
		iflag = 0;
		break;
	case 'i':		/* Used by scp */
		iflag = 1;
		break;
	case 'v':
	case 'a':
	case 'x':
		break;
	case 'k':
		nopka = 1;
		break;
	case 'K':
		nopw = 1;
		break;
	case 'm':
		mflag = 1;
		break;
	case 'n':
		netdir = EARGF(usage());
		break;
	case 's':		/* Used by sftpfs */
		subsystem = EARGF(usage());
		break;
	case 'z':
		whichkey = EARGF(usage());
		break;
	default:
		usage();
		break;
	} ARGEND;
	if (argc == 0)
		usage();
	if (iflag == -1)
		iflag = isatty(0);
	if (subsystem)
		iflag = 0;
	remote = *argv;
	++argv;
	--argc;
	if (q = strchr(remote, '@')) {
		*q = 0;
		user = remote;
		remote = q+1;
	}
	if (!netdir) {
		q = strchr(remote, '!');
		if (q ) {
			n = q-remote;
			netdir = malloc(n+1);
			strncpy(netdir, remote, n);
			netdir[n] = '\0';
			p = strrchr(netdir, '/');
			if (p) {
				if (strcmp(p+1, "ssh") == 0)
					*p = '\0';
				else
					remote = smprint("%s/ssh", netdir);
			}
			else {
				free(netdir);
				netdir = nil;
			}
		}
	}
	if (!user)
		user = getuser();
	if (netdir)
		p = smprint("%s/ssh", netdir);
	else
		p = smprint("/net/ssh");
	if (access(p, OREAD) < 0) {
		if ((n = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG)) == 0) {
			if (netdir)
				execl("/bin/sshtun", "sshtun", "-m", netdir, nil);
			else
				execl("/bin/sshtun", "sshtun", nil);
			exits(nil);
		}
		do {
			i = waitpid();
		} while (i != n && i >= 0);
	}
	free(p);
	if ((n = rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)) == 0) {
		if (netdir)
			p = smprint("%s/ssh/keys", netdir);
		else
			p = smprint("/net/ssh/keys");
		keyfd = open(p, ORDWR);
		free(p);
		if (keyfd < 0) {
			// fprint(2, "failed to open sskeys: %r\n");
			chpid = 0;
			exits(nil);
		}
		if(iflag)
			kconsfd = open("/dev/cons", ORDWR);
		if (kconsfd < 0)
			nopw = 1;
		n = read(keyfd, buf, 5);
		buf[5] = 0;
		if (n < 0)
			exits(nil);
		n = strtol(buf+1, nil, 10);
		n = readn(keyfd, buf+5, n);
		buf[n+5] = 0;
		switch (*buf) {
		case 'f':
			if (kconsfd >= 0)
				fprint(kconsfd, "%s\n", buf+5);
		case 'o':
			close(keyfd);
			if (kconsfd >= 0)
				close(kconsfd);
			break;
		default:
			if (kconsfd >= 0) {
				if (*buf == 'c') {
					fprint(kconsfd, "The following key has been offered by the server:\n");
					write(kconsfd, buf+5, n);
					fprint(kconsfd, "\n\n");
					fprint(kconsfd, "Add this key? (yes, no, session) ");
				}
				else {
					fprint(kconsfd, "The following key does NOT match the known key(s) for the server:\n");
					write(kconsfd, buf+5, n);
					fprint(kconsfd, "\n\n");
					fprint(kconsfd, "Add this key? (yes, no, session, replace) ");
				}
				n = read(kconsfd, buf, 10);
				write(keyfd, buf, n);
				seek(keyfd, 0, 2);
				readn(keyfd, buf, 5);
				buf[5] = 0;
				n = strtol(buf+1, nil, 10);
				n = readn(keyfd, buf+5, n);
				buf[n+5] = 0;
				switch (*buf) {
				case 'b':
				case 'f':
					fprint(kconsfd, "%s\n", buf+5);
				case 'o':
					close(keyfd);
					close(kconsfd);
				}
			}
			else {
				fprint(keyfd, "n");
				close(keyfd);
			}
		}
		chpid = 0;
		exits(nil);
	}
	chpid = n;
	atnotify(handler,1);
	if (netdir)
		p = smprint("%s/ssh", netdir);
	else
		p = smprint("ssh");
	q = netmkaddr(remote, p, "22");
	free(p);
	dfd1 = dial(q, nil, nil, &cfd1);
	if (dfd1 < 0) {
		fprint(2, "%s: dial: %r\n", argv0);
		if (chpid) {
			p = smprint("/proc/%d/note", chpid);
			nfd = open(p, OWRITE);
			fprint(nfd, "interrupt");
		}
		exits(nil);
	}
	seek(cfd1, 0, 0);
	n = read(cfd1, buf, 10);
	buf[n] = 0;
	conn = atoi(buf);
	if(iflag){
		consfd = open("/dev/cons", ORDWR);
		cctlfd = open("/dev/consctl", OWRITE);
	}
	if(iflag && !cooked && subsystem == nil)
		fprint(cctlfd, "rawon");
	if (doauth(cfd1, whichkey) < 0)
		goto bail;

	if (netdir)
		path = smprint("%s/ssh/%d!session",netdir, conn);
	else
		path = smprint("/net/ssh/%d!session", conn);

	dfd2 = dial(path, nil, nil, &cfd2);
	if (dfd2 < 0) {
		fprint(2, "%s: dial: %r\n", argv0);
		goto bail;
	}
	n = read(cfd2, buf, 10);
	buf[n] = 0;
	chan = atoi(buf);
	free(path);
	if (netdir)
		path = smprint("%s/ssh/%d/%d/request", netdir, conn, chan);
	else
		path = smprint("/net/ssh/%d/%d/request", conn, chan);

	reqfd = open(path, OWRITE);
	if(subsystem){
		fprint(reqfd, "subsystem %s", subsystem);
	}
	else if (argc == 0) {
		strcpy(buf, "dumb");
		if ((i = open("/env/TERM", OREAD)) >= 0){
			n = read(i, buf, 32);
			buf[n] = 0;
			close(i);
		}
		if(getgeom(&cols, &lines, &width, &height) == 0)
			fprint(reqfd, "shell %q %d %d %d %d %d",
				buf, cols, lines, width, height, cooked);
		else
			fprint(reqfd, "shell %s", buf);
	}
	else {
		q = buf;
		for (i = 0; i < argc; ++i) {
			q = seprint(q, buf+1024, " %s", argv[i]);
			if (q == nil)
				break;
		}
		if (q != nil)
			fprint(reqfd, "exec%s", buf);
		else {
			fprint(2, "Command too long\n");
			fprint(reqfd, "close");
			goto bail;
		}
	}
	switch (rfork(RFPROC|RFMEM|RFNOWAIT|RFNOTEG)) {
	case 0:
		netpid = getpid();
		while (1) {
			n = read(dfd2, buf, 32*1024);
			if (n <= 0)
				break;
			if (stripcr) {
				for (i = 0, p = buf, q = buf; i < n; ++i, ++q)
					if (*q != '\r')
						*p++ = *q;
			}
			else
				p = buf + n;
			write(1, buf, p-buf);
		}
		postnote(PNPROC, kbdpid, "kill");
		fprint(2, "Connection closed by server\n");
		break;
	case -1:
		fprint(2, "fork error: %r\n");
		goto bail;
	default:
		eofs = 0;
		lstart = 1;
		kbdpid = getpid();
		while (1) {
			n = read(0, buf, 32*1024);
			if (cooked && n < 0 && wasintr()) {
				buf[0] = 0x7f;
				n = 1;
			}
			if (cooked && n == 0) {
				if(eofs++ > 32)
					break;
				buf[0] = 0x04;
				n = 1;
			}
			else
				eofs = 0;

			if (n <= 0)
				break;
			if (!mflag && lstart && buf[0] == 0x1c) {
				if (cmdmode())
					break;
				else
					continue;
			}
			lstart = (buf[n-1] == '\n' || buf[n-1] == '\r');
			write(dfd2, buf, n);
		}
		postnote(PNPROC, netpid, "kill");
		fprint(2, "EOF on client side\n");
		break;
	}
bail:
	if(iflag){
		if(! cooked)
			fprint(cctlfd, "rawoff");
		close(cctlfd);
		close(consfd);
	}
	fprint(reqfd, "close");
	close(reqfd);
	close(dfd2);
	close(dfd1);
	close(cfd2);
	close(cfd1);
	write(notefd, "kill", 4);
	close(notefd);
	exits(nil);
}

int
isatty(int fd)
{
	char buf[64];

	buf[0] = '\0';
	fd2path(fd, buf, sizeof buf);
	if(strlen(buf)>=9 && strcmp(buf+strlen(buf)-9, "/dev/cons")==0)
		return 1;
	return 0;
}

int
doauth(int cfd1, char *whichkey)
{
	UserPasswd *up;
	int n;

 	if (!nopka) {
		if (whichkey)
			n = fprint(cfd1, "ssh-userauth K %s %s", user, whichkey);
		else
			n = fprint(cfd1, "ssh-userauth K %s", user);
		if (n >= 0)
			return 0;
	}
	if (nopw)
		return -1;
	up = auth_getuserpasswd(iflag ? auth_getkey : nil, "proto=pass service=ssh server=%q user=%q",
		remote, user);
	if (up == nil) {
		fprint(2, "Failure to get password: %r\n");
		return -1;
	}
	n = fprint(cfd1, "ssh-userauth k %s %q", user, up->passwd);
	if (n >= 0)
		return 0;
	fprint(2, "auth %r\n");
	return -1;
}

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].