511 lines · 10.5 KB · C
RawDownload
/*  * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * ssh server - serve SSH protocol v2 *	/net/ssh does most of the work; we copy bytes back and forth */#include <u.h>#include <libc.h>#include <ip.h>#include <auth.h>#include "ssh2.h" char *confine(char *, char *);char *get_string(char *, char *);void newchannel(int, char *, int);void runcmd(int, int, char *, char *, char *, char *); int errfd, toppid, sflag, tflag, prevent;int debug;char *idstring;char *netdir;				/* /net/ssh/<conn> */char *nsfile = nil;char *restdir;char *shell;char *srvpt;char *uname; voidusage(void){	fprint(2, "usage: %s [-i id] [-s shell] [-r dir] [-R dir] [-S srvpt] "		"[-n ns] [-t] [netdir]\n", argv0);	exits("usage");} static intgetctlfd(void){	int ctlfd;	char *name; 	name = smprint("%s/clone", netdir);	ctlfd = -1;	if (name)		ctlfd = open(name, ORDWR);	if (ctlfd < 0) {		syslog(0, "ssh", "server can't clone: %s: %r", name);		exits("open clone");	}	free(name);	return ctlfd;} static intgetdatafd(int ctlfd){	int fd;	char *name; 	name = smprint("%s/data", netdir);	fd = -1;	if (name)		fd = open(name, OREAD);	if (fd < 0) {		syslog(0, "ssh", "can't open %s: %r", name);		hangup(ctlfd);		exits("open data");	}	free(name);	return fd;} static voidauth(char *buf, int n, int ctlfd){	int fd; 	fd = open("#¤/capuse", OWRITE);	if (fd < 0) {		syslog(0, "ssh", "server can't open capuse: %r");		hangup(ctlfd);		exits("capuse");	}	if (write(fd, buf, n) != n) {		syslog(0, "ssh", "server write `%.*s' to capuse failed: %r",			n, buf);		hangup(ctlfd);		exits("capuse");	}	close(fd);} /* * mount tunnel if there isn't one visible. */static voidmounttunnel(int ctlfd){	int fd;	char *p, *np, *q; 	if (access(netdir, AEXIST) >= 0)		return; 	p = smprint("/srv/%s", srvpt? srvpt: "ssh");	np = strdup(netdir);	if (p == nil || np == nil)		sysfatal("out of memory");	q = strstr(np, "/ssh");	if (q != nil)		*q = '\0';	fd = open(p, ORDWR);	if (fd < 0) {		syslog(0, "ssh", "can't open %s: %r", p);		hangup(ctlfd);		exits("open");	}	if (mount(fd, -1, np, MBEFORE, "") < 0) {		syslog(0, "ssh", "can't mount %s in %s: %r", p, np);		hangup(ctlfd);		exits("can't mount");	}	free(p);	free(np);} static intauthnewns(int ctlfd, char *buf, int size, int n){	char *p, *q; 	USED(size);	if (n <= 0)		return 0;	buf[n] = '\0';	if (strcmp(buf, "n/a") == 0)		return 0; 	auth(buf, n, ctlfd); 	p = strchr(buf, '@');	if (p == nil)		return 0;	++p;	q = strchr(p, '@');	if (q) {		*q = '\0';		uname = strdup(p);	}	if (!tflag && newns(p, nsfile) < 0) {		syslog(0, "ssh", "server: newns(%s,%s) failed: %r", p, nsfile);		return -1;	}	return 0;} static voidlistenloop(char *listfile, int ctlfd, char *buf, int size){	int fd, n; 	while ((fd = open(listfile, ORDWR)) >= 0) {		n = read(fd, buf, size - 1);		fprint(errfd, "read from listen file returned %d\n", n);		if (n <= 0) {			syslog(0, "ssh", "read on listen failed: %r");			break;		}		buf[n >= 0? n: 0] = '\0';		fprint(errfd, "read %s\n", buf); 		switch (fork()) {		case 0:					/* child */			close(ctlfd);			newchannel(fd, netdir, atoi(buf));  /* never returns */		case -1:			syslog(0, "ssh", "fork failed: %r");			hangup(ctlfd);			exits("fork");		}		close(fd);	}	if (fd < 0)		syslog(0, "ssh", "listen failed: %r");} voidmain(int argc, char *argv[]){	char *listfile;	int ctlfd, fd, n;	char buf[Arbpathlen], path[Arbpathlen]; 	rfork(RFNOTEG);	toppid = getpid();	shell = "/bin/rc -il";	ARGBEGIN {	case 'd':		debug++;		break;	case 'i':		idstring = EARGF(usage());		break;	case 'n':		nsfile = EARGF(usage());		break;	case 'R':		prevent = 1;		/* fall through */	case 'r':		restdir = EARGF(usage());		break;	case 's':		sflag = 1;		shell = EARGF(usage());		break;	case 'S':		srvpt = EARGF(usage());		break;	case 't':		tflag = 1;		break;	default:		usage();		break;	} ARGEND; 	errfd = -1;	if (debug)		errfd = 2; 	/* work out network connection's directory */	if (argc >= 1)		netdir = argv[0];	else				/* invoked by listen1 */		netdir = getenv("net");	if (netdir == nil) {		syslog(0, "ssh", "server netdir is nil");		exits("nil netdir");	}	syslog(0, "ssh", "server netdir is %s", netdir); 	uname = getenv("user");	if (uname == nil)		uname = "none"; 	/* extract dfd and cfd from netdir */	ctlfd = getctlfd();	fd = getdatafd(ctlfd); 	n = read(fd, buf, sizeof buf - 1);	if (n < 0) {		syslog(0, "ssh", "server read error for data file: %r");		hangup(ctlfd);		exits("read cap");	}	close(fd);	authnewns(ctlfd, buf, sizeof buf, n); 	/* get connection number in buf */	n = read(ctlfd, buf, sizeof buf - 1);	buf[n >= 0? n: 0] = '\0'; 	/* tell netssh our id string */	fd2path(ctlfd, path, sizeof path);	if (0 && idstring) {			/* was for coexistence */		syslog(0, "ssh", "server conn %s, writing \"id %s\" to %s",			buf, idstring, path);		fprint(ctlfd, "id %s", idstring);	} 	/* announce */	fprint(ctlfd, "announce session"); 	/* construct listen file name */	listfile = smprint("%s/%s/listen", netdir, buf);	if (listfile == nil) {		syslog(0, "ssh", "out of memory");		exits("out of memory");	}	syslog(0, "ssh", "server listen is %s", listfile); 	mounttunnel(ctlfd);	listenloop(listfile, ctlfd, buf, sizeof buf);	hangup(ctlfd);	exits(nil);} /* an abbreviation.  note the assumed variables. */#define REPLY(s)	if (want_reply) fprint(reqfd, s); static voidforkshell(char *cmd, int reqfd, int datafd, int want_reply){	switch (fork()) {	case 0:		if (sflag)			snprint(cmd, sizeof cmd, "-s%s", shell);		else			cmd[0] = '\0';		USED(cmd);		syslog(0, "ssh", "server starting ssh shell for %s", uname);		/* runcmd doesn't return */		runcmd(reqfd, datafd, "con", "/bin/ip/telnetd", "-nt", nil);	case -1:		REPLY("failure");		syslog(0, "ssh", "server can't fork: %r");		exits("fork");	}} static voidforkcmd(char *cmd, char *p, int reqfd, int datafd, int want_reply){	char *q; 	switch (fork()) {	case 0:		if (restdir && chdir(restdir) < 0) {			syslog(0, "ssh", "can't chdir(%s): %r", restdir);			exits("can't chdir");		}		if (!prevent || (q = getenv("sshsession")) &&		    strcmp(q, "allow") == 0)			get_string(p+1, cmd);		else			confine(p+1, cmd);		syslog(0, "ssh", "server running `%s' for %s", cmd, uname);		/* runcmd doesn't return */		runcmd(reqfd, datafd, "rx", "/bin/rc", "-lc", cmd);	case -1:		REPLY("failure");		syslog(0, "ssh", "server can't fork: %r");		exits("fork");	}} voidnewchannel(int fd, char *conndir, int channum){	char *p, *reqfile, *datafile;	int n, reqfd, datafd, want_reply, already_done;	char buf[Maxpayload], cmd[Bigbufsz]; 	close(fd); 	already_done = 0;	reqfile = smprint("%s/%d/request", conndir, channum);	if (reqfile == nil)		sysfatal("out of memory");	reqfd = open(reqfile, ORDWR);	if (reqfd < 0) {		syslog(0, "ssh", "can't open request file %s: %r", reqfile);		exits("net");	}	datafile = smprint("%s/%d/data", conndir, channum);	if (datafile == nil)		sysfatal("out of memory");	datafd = open(datafile, ORDWR);	if (datafd < 0) {		syslog(0, "ssh", "can't open data file %s: %r", datafile);		exits("net");	}	while ((n = read(reqfd, buf, sizeof buf - 1)) > 0) {		fprint(errfd, "read from request file returned %d\n", n);		for (p = buf; p < buf + n && *p != ' '; ++p)			;		*p++ = '\0';		want_reply = (*p == 't');		/* shell, exec, and various flavours of failure */		if (strcmp(buf, "shell") == 0) {			if (already_done) {				REPLY("failure");				continue;			}			forkshell(cmd, reqfd, datafd, want_reply);			already_done = 1;			REPLY("success");		} else if (strcmp(buf, "exec") == 0) {			if (already_done) {				REPLY("failure");				continue;			}			forkcmd(cmd, p, reqfd, datafd, want_reply);			already_done = 1;			REPLY("success");		} else if (strcmp(buf, "pty-req") == 0 ||		    strcmp(buf, "window-change") == 0) {			REPLY("success");		} else if (strcmp(buf, "x11-req") == 0 ||		    strcmp(buf, "env") == 0 || strcmp(buf, "subsystem") == 0) {			REPLY("failure");		} else if (strcmp(buf, "xon-xoff") == 0 ||		    strcmp(buf, "signal") == 0 ||		    strcmp(buf, "exit-status") == 0 ||		    strcmp(buf, "exit-signal") == 0) {			;		} else			syslog(0, "ssh", "server unknown channel request: %s",				buf);	}	if (n < 0)		syslog(0, "ssh", "server read failed: %r");	exits(nil);} char *get_string(char *q, char *s){	int n; 	n = nhgetl(q);	q += 4;	memmove(s, q, n);	s[n] = '\0';	q += n;	return q;} char *confine(char *q, char *s){	int i, n, m;	char *p, *e, *r, *buf, *toks[Maxtoks]; 	n = nhgetl(q);	q += 4;	buf = malloc(n+1);	if (buf == nil)		return nil;	memmove(buf, q, n);	buf[n]  = 0;	m = tokenize(buf, toks, nelem(toks));	e = s + n + 1;	for (i = 0, r = s; i < m; ++i) {		p = strrchr(toks[i], '/');		if (p == nil)			r = seprint(r, e, "%s ", toks[i]);		else if (p[0] != '\0' && p[1] != '\0')			r = seprint(r, e, "%s ", p+1);		else			r = seprint(r, e, ". ");	}	free(buf);	q += n;	return q;} voidruncmd(int reqfd, int datafd, char *svc, char *cmd, char *arg1, char *arg2){	char *p;	int fd, cmdpid, child; 	cmdpid = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG|RFENVG);	switch (cmdpid) {	case -1:		syslog(0, "ssh", "fork failed: %r");		exits("fork");	case 0:		if (restdir == nil) {			p = smprint("/usr/%s", uname);			if (p && access(p, AREAD) == 0 && chdir(p) < 0) {				syslog(0, "ssh", "can't chdir(%s): %r", p);				exits("can't chdir");			}			free(p);		}		p = strrchr(cmd, '/');		if (p)			++p;		else			p = cmd; 		dup(datafd, 0);		dup(datafd, 1);		dup(datafd, 2);		close(datafd);		putenv("service", svc);		fprint(errfd, "starting %s\n", cmd);		execl(cmd, p, arg1, arg2, nil); 		syslog(0, "ssh", "cannot exec %s: %r", cmd);		exits("exec");	default:		close(datafd);		fprint(errfd, "waiting for child %d\n", cmdpid);		while ((child = waitpid()) != cmdpid && child != -1)			fprint(errfd, "child %d passed\n", child);		if (child == -1)			fprint(errfd, "wait failed: %r\n"); 		syslog(0, "ssh", "server closing ssh session for %s", uname);

Lines 1–500 of 511.