Browse Source

Re-add telnet and rlogin

This reverts commit ccdb6fd39c34ffdaf39ed685fa615c5671ed17e3.

Signed-off-by: Graham MacDonald <grahamamacdonald@gmail.com>
Graham MacDonald 3 months ago
parent
commit
61aa17b816
7 changed files with 1696 additions and 1 deletions
  1. 2 0
      .gitignore
  2. 1 1
      rc/bin/cpurc
  3. 3 0
      sys/src/cmd/ip/ip.json
  4. 52 0
      sys/src/cmd/ip/rlogind.c
  5. 585 0
      sys/src/cmd/ip/telnet.c
  6. 396 0
      sys/src/cmd/ip/telnet.h
  7. 657 0
      sys/src/cmd/ip/telnetd.c

+ 2 - 0
.gitignore

@@ -80,3 +80,5 @@ util/harvey.raw
 /home
 
 **/*.h.gch
+
+.vscode

+ 1 - 1
rc/bin/cpurc

@@ -59,7 +59,7 @@ rm -f '#¤/caphash'
 # }
 
 # start listeners if it hasn't already been done (dicey check)
-if(! netstat -n | grep -s 'tcp.*Listen.* (7|9|21|113|565|17007|17009|17010) .*')
+if(! netstat -n | grep -s 'tcp.*Listen.* (7|9|21|23|113|565|17007|17009|17010) .*')
 	aux/listen -q tcp
 
 if(! ps|grep -s timesync) {

+ 3 - 0
sys/src/cmd/ip/ip.json

@@ -27,6 +27,9 @@
 			"rarpd.c",
 			"rexexec.c",
 			"rip.c",
+			"rlogind.c",
+			"telnet.c",
+			"telnetd.c",
 			"tftpd.c",
 			"traceroute.c",
 			"udpecho.c",

+ 52 - 0
sys/src/cmd/ip/rlogind.c

@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#include <u.h>
+#include <libc.h>
+
+void	getstr(int, char*, int);
+
+void
+main(void)
+{
+	char luser[128], ruser[128], term[128], err[128];
+
+	getstr(0, err, sizeof(err));
+	getstr(0, ruser, sizeof(ruser));
+	getstr(0, luser, sizeof(luser));
+	getstr(0, term, sizeof(term));
+	write(0, "", 1);
+
+	if(luser[0] == '\0')
+		strncpy(luser, ruser, sizeof luser);
+	luser[sizeof luser-1] = '\0';
+	syslog(0, "telnet", "rlogind %s", luser);
+	execl("/bin/ip/telnetd", "telnetd", "-n", "-u", luser, nil);
+	fprint(2, "can't exec con service: %r\n");
+	exits("can't exec");
+}
+
+void
+getstr(int fd, char *str, int len)
+{
+	char c;
+	int n;
+
+	while(--len > 0){
+		n = read(fd, &c, 1);
+		if(n < 0)
+			return;
+		if(n == 0)
+			continue;
+		*str++ = c;
+		if(c == 0)
+			break;
+	}
+	*str = '\0';
+}

+ 585 - 0
sys/src/cmd/ip/telnet.c

@@ -0,0 +1,585 @@
+/*
+ * 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.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "telnet.h"
+
+int ctl = -1;		/* control fd (for break's) */
+int consctl = -1;	/* consctl fd */
+
+int ttypid;		/* pid's if the 2 processes (used to kill them) */
+int netpid;
+int interrupted;
+int localecho;
+int notkbd;
+
+static char *srv;
+
+typedef struct Comm Comm;
+struct Comm {
+	int returns;
+	int stopped;
+};
+Comm *comm;
+
+int	dodial(char*);
+void	fromkbd(int);
+void	fromnet(int);
+int	menu(Biobuf*,  int);
+void	notifyf(void*, char*);
+void	rawoff(void);
+void	rawon(void);
+void	telnet(int);
+char*	system(int, char*);
+int	echochange(Biobuf*, int);
+int	termsub(Biobuf*, uint8_t*, int);
+int	xlocsub(Biobuf*, uint8_t*, int);
+void*	share(uint32_t);
+
+static int islikeatty(int);
+
+void
+usage(void)
+{
+	fatal("usage: telnet [-Cdnr] [-s srv] net!host[!service]", 0, 0);
+}
+
+void
+main(int argc, char *argv[])
+{
+	int returns;
+
+	returns = 1;
+	ARGBEGIN{
+	case 'C':
+		opt[Echo].noway = 1;
+		break;
+	case 'd':
+		debug = 1;
+		break;
+	case 'n':
+		notkbd = 1;
+		break;
+	case 'r':
+		returns = 0;
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(argc != 1)
+		usage();
+
+	/* options we need routines for */
+	opt[Echo].change = echochange;
+	opt[Term].sub = termsub;
+	opt[Xloc].sub = xlocsub;
+
+	comm = share(sizeof(comm));
+	comm->returns = returns;
+
+	telnet(dodial(argv[0]));
+}
+
+/*
+ *  dial and return a data connection
+ */
+int
+dodial(char *dest)
+{
+	char *name;
+	int data;
+	char devdir[NETPATHLEN];
+
+	name = netmkaddr(dest, "tcp", "telnet");
+	data = dial(name, 0, devdir, 0);
+	if(data < 0)
+		fatal("%s: %r", name, 0);
+	fprint(2, "connected to %s on %s\n", name, devdir);
+	return data;
+}
+
+void
+post(char *srv, int fd)
+{
+	int f;
+	char buf[32];
+
+	f = create(srv, OWRITE, 0666);
+	if(f < 0)
+		sysfatal("create %s: %r", srv);
+	snprint(buf, sizeof buf, "%d", fd);
+	if(write(f, buf, strlen(buf)) != strlen(buf))
+		sysfatal("write %s: %r", srv);
+	close(f);
+}
+
+/*
+ *  two processes pass bytes back and forth between the
+ *  terminal and the network.
+ */
+void
+telnet(int net)
+{
+	int pid;
+	int p[2];
+	char *svc;
+
+	rawoff();
+	svc = nil;
+	if (srv) {
+		if(pipe(p) < 0)
+			sysfatal("pipe: %r");
+		if (srv[0] != '/')
+			svc = smprint("/srv/%s", srv);
+		else
+			svc = srv;
+		post(svc, p[0]);
+		close(p[0]);
+		dup(p[1], 0);
+		dup(p[1], 1);
+		/* pipe is now std in & out */
+	}
+	ttypid = getpid();
+	switch(pid = rfork(RFPROC|RFFDG|RFMEM)){
+	case -1:
+		perror("con");
+		exits("fork");
+	case 0:
+		rawoff();
+		notify(notifyf);
+		fromnet(net);
+		if (svc)
+			remove(svc);
+		sendnote(ttypid, "die");
+		exits(0);
+	default:
+		netpid = pid;
+		notify(notifyf);
+		fromkbd(net);
+		if(notkbd)
+			for(;;)
+				sleep(1000); // sleep(0) is a cpuhog
+		if (svc)
+			remove(svc);
+		sendnote(netpid, "die");
+		exits(0);
+	}
+}
+
+/*
+ *  Read the keyboard and write it to the network.  '^\' gets us into
+ *  the menu.
+ */
+void
+fromkbd(int net)
+{
+	Biobuf ib, ob;
+	int c, likeatty;
+	int eofs;
+
+	Binit(&ib, 0, OREAD);
+	Binit(&ob, net, OWRITE);
+
+	likeatty = islikeatty(0);
+	eofs = 0;
+	for(;;){
+		c = Bgetc(&ib);
+
+		/*
+		 *  with raw off, all ^D's get turned into Eof's.
+		 *  change them back.
+		 *  10 in a row implies that the terminal is really gone so
+		 *  just hang up.
+		 */
+		if(c < 0){
+			if(notkbd)
+				return;
+			if(eofs++ > 10)
+				return;
+			c = 004;
+		} else
+			eofs = 0;
+
+		/*
+		 *  if not in binary mode, look for the ^\ escape to menu.
+		 *  also turn \n into \r\n
+		 */
+		if(likeatty || !opt[Binary].local){
+			if(c == 0034){ /* CTRL \ */
+				if(Bflush(&ob) < 0)
+					return;
+				if(menu(&ib, net) < 0)
+					return;
+				continue;
+			}
+		}
+		if(!opt[Binary].local){
+			if(c == '\n'){
+				/*
+				 *  This is a very strange use of the SGA option.
+				 *  I did this because some systems that don't
+				 *  announce a willingness to supress-go-ahead
+				 *  need the \r\n sequence to recognize input.
+				 *  If someone can explain this to me, please
+				 *  send me mail. - presotto
+				 */
+				if(opt[SGA].remote){
+					c = '\r';
+				} else {
+					if(Bputc(&ob, '\r') < 0)
+						return;
+				}
+			}
+		}
+		if(Bputc(&ob, c) < 0)
+			return;
+		if(Bbuffered(&ib) == 0)
+			if(Bflush(&ob) < 0)
+				return;
+	}
+}
+
+/*
+ *  Read from the network and write to the screen.  If 'stopped' is set
+ *  spin and don't read.  Filter out spurious carriage returns.
+ */
+void
+fromnet(int net)
+{
+	int c;
+	int crnls = 0, freenl = 0, eofs;
+	Biobuf ib, ob;
+
+	Binit(&ib, net, OREAD);
+	Binit(&ob, 1, OWRITE);
+	eofs = 0;
+	for(;;){
+		if(Bbuffered(&ib) == 0)
+			Bflush(&ob);
+		if(interrupted){
+			interrupted = 0;
+			send2(net, Iac, Interrupt);
+		}
+		c = Bgetc(&ib);
+		if(c < 0){
+			if(eofs++ >= 2)
+				return;
+			continue;
+		}
+		eofs = 0;
+		switch(c){
+		case '\n':	/* skip nl after string of cr's */
+			if(!opt[Binary].local && !comm->returns){
+				++crnls;
+				if(freenl == 0)
+					break;
+				freenl = 0;
+				continue;
+			}
+			break;
+		case '\r':	/* first cr becomes nl, remainder dropped */
+			if(!opt[Binary].local && !comm->returns){
+				if(crnls++ == 0){
+					freenl = 1;
+					c = '\n';
+					break;
+				}
+				continue;
+			}
+			break;
+		case 0:		/* remove nulls from crnl string */
+			if(crnls)
+				continue;
+			break;
+
+		case Iac:
+			crnls = 0;
+			freenl = 0;
+			c = Bgetc(&ib);
+			if(c == Iac)
+				break;
+			if(Bflush(&ob) < 0)
+				return;
+			if(control(&ib, c) < 0)
+				return;
+			continue;
+
+		default:
+			crnls = 0;
+			freenl = 0;
+			break;
+		}
+		if(Bputc(&ob, c) < 0)
+			return;
+	}
+}
+
+/*
+ *  turn keyboard raw mode on
+ */
+void
+rawon(void)
+{
+	if(debug)
+		fprint(2, "rawon\n");
+	if(consctl < 0)
+		consctl = open("/dev/consctl", OWRITE);
+	if(consctl < 0){
+		fprint(2, "%s: can't open consctl: %r\n", argv0);
+		return;
+	}
+	write(consctl, "rawon", 5);
+}
+
+/*
+ *  turn keyboard raw mode off
+ */
+void
+rawoff(void)
+{
+	if(debug)
+		fprint(2, "rawoff\n");
+	if(consctl < 0)
+		consctl = open("/dev/consctl", OWRITE);
+	if(consctl < 0){
+		fprint(2, "%s: can't open consctl: %r\n", argv0);
+		return;
+	}
+	write(consctl, "rawoff", 6);
+}
+
+/*
+ *  control menu
+ */
+#define STDHELP	"\t(b)reak, (i)nterrupt, (q)uit, (r)eturns, (!cmd), (.)continue\n"
+
+int
+menu(Biobuf *bp, int net)
+{
+	char *cp;
+	int done;
+
+	comm->stopped = 1;
+
+	rawoff();
+	fprint(2, ">>> ");
+	for(done = 0; !done; ){
+		cp = Brdline(bp, '\n');
+		if(cp == 0){
+			comm->stopped = 0;
+			return -1;
+		}
+		cp[Blinelen(bp)-1] = 0;
+		switch(*cp){
+		case '!':
+			system(Bfildes(bp), cp+1);
+			done = 1;
+			break;
+		case '.':
+			done = 1;
+			break;
+		case 'q':
+			comm->stopped = 0;
+			return -1;
+		case 'o':
+			switch(*(cp+1)){
+			case 'd':
+				send3(net, Iac, Do, atoi(cp+2));
+				break;
+			case 'w':
+				send3(net, Iac, Will, atoi(cp+2));
+				break;
+			}
+			break;
+		case 'r':
+			comm->returns = !comm->returns;
+			done = 1;
+			break;
+		case 'i':
+			send2(net, Iac, Interrupt);
+			break;
+		case 'b':
+			send2(net, Iac, Break);
+			break;
+		default:
+			fprint(2, STDHELP);
+			break;
+		}
+		if(!done)
+			fprint(2, ">>> ");
+	}
+
+	rawon();
+	comm->stopped = 0;
+	return 0;
+}
+
+/*
+ *  ignore interrupts
+ */
+void
+notifyf(void *a, char *msg)
+{
+	USED(a);
+	if(strcmp(msg, "interrupt") == 0){
+		interrupted = 1;
+		noted(NCONT);
+	}
+	if(strcmp(msg, "hangup") == 0)
+		noted(NCONT);
+	noted(NDFLT);
+}
+
+/*
+ *  run a command with the network connection as standard IO
+ */
+char *
+system(int fd, char *cmd)
+{
+	int pid;
+	int p;
+	static Waitmsg msg;
+
+	if((pid = fork()) == -1){
+		perror("con");
+		return "fork failed";
+	}
+	else if(pid == 0){
+		dup(fd, 0);
+		close(ctl);
+		close(fd);
+		if(*cmd)
+			execl("/bin/rc", "rc", "-c", cmd, nil);
+		else
+			execl("/bin/rc", "rc", nil);
+		perror("con");
+		exits("exec");
+	}
+	for(p = waitpid(); p >= 0; p = waitpid()){
+		if(p == pid)
+			return msg.msg;
+	}
+	return "lost child";
+}
+
+/*
+ *  suppress local echo if the remote side is doing it
+ */
+int
+echochange(Biobuf *bp, int cmd)
+{
+	USED(bp);
+
+	switch(cmd){
+	case Will:
+		rawon();
+		break;
+	case Wont:
+		rawoff();
+		break;
+	}
+	return 0;
+}
+
+/*
+ *  send terminal type to the other side
+ */
+int
+termsub(Biobuf *bp, uint8_t *sub, int n)
+{
+	unsigned char buf[64];
+	char *term;
+	unsigned char *p = buf;
+
+	if(n < 1)
+		return 0;
+	if(sub[0] == 1){
+		*p++ = Iac;
+		*p++ = Sb;
+		*p++ = opt[Term].code;
+		*p++ = 0;
+		term = getenv("TERM");
+		if(term == 0 || *term == 0)
+			term = "p9win";
+		strncpy((char *)p, term, sizeof(buf) - (p - buf) - 2);
+		buf[sizeof(buf)-2] = 0;
+		p += strlen((char *)p);
+		*p++ = Iac;
+		*p++ = Se;
+		return iwrite(Bfildes(bp), buf, p-buf);
+	}
+	return 0;
+}
+
+/*
+ *  send an x display location to the other side
+ */
+int
+xlocsub(Biobuf *bp, uint8_t *sub, int n)
+{
+	unsigned char buf[64];
+	char *term;
+	unsigned char *p = buf;
+
+	if(n < 1)
+		return 0;
+	if(sub[0] == 1){
+		*p++ = Iac;
+		*p++ = Sb;
+		*p++ = opt[Xloc].code;
+		*p++ = 0;
+		term = getenv("XDISP");
+		if(term == 0 || *term == 0)
+			term = "unknown";
+		strncpy((char *)p, term, p - buf - 2);
+		p += strlen(term);
+		*p++ = Iac;
+		*p++ = Se;
+		return iwrite(Bfildes(bp), buf, p-buf);
+	}
+	return 0;
+}
+
+static int
+islikeatty(int fd)
+{
+	char buf[64];
+
+	if(fd2path(fd, buf, sizeof buf) != 0)
+		return 0;
+
+	/* might be /mnt/term/dev/cons */
+	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
+}
+
+/*
+ *  create a shared segment.  Make is start 2 meg higher than the current
+ *  end of process memory.
+ */
+void*
+share(uint32_t len)
+{
+	uint8_t *vastart;
+
+	vastart = sbrk(0);
+	if(vastart == (void*)-1)
+		return 0;
+	vastart += 2*1024*1024;
+
+	if(segattach(0, "shared", vastart, len) == (void*)-1)
+		return 0;
+
+	return vastart;
+}

+ 396 - 0
sys/src/cmd/ip/telnet.h

@@ -0,0 +1,396 @@
+/*
+ * 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.
+ */
+
+typedef struct Opt	Opt;
+
+int debug;
+#define DPRINT if(debug)fprint
+
+enum
+{
+	/* control characters */
+	Se=		240,		/* end subnegotiation */
+	NOP=		241,
+	Mark=		242,		/* data mark */
+	Break=		243,
+	Interrupt=	244,
+	Abort=		245,		/* TENEX ^O */
+	AreYouThere=	246,
+	Erasechar=	247,		/* erase last character */
+	Eraseline=	248,		/* erase line */
+	GoAhead=	249,		/* half duplex clear to send */
+	Sb=		250,		/* start subnegotiation */
+	Will=		251,
+	Wont=		252,
+	Do=		253,
+	Dont=		254,
+	Iac=		255,
+
+	/* options */
+	Binary=		0,
+	Echo,
+	SGA,
+	Stat,
+	Timing,
+	Det,
+	Term,
+	EOR,
+	Uid,
+	Outmark,
+	Ttyloc,
+	M3270,
+	Padx3,
+	Window,
+	Speed,
+	Flow,
+	Line,
+	Xloc,
+	Extend,
+};
+
+struct Opt
+{
+	char	*name;
+	int	code;
+	char	noway;
+	int	(*change)(Biobuf*, int);	/* routine for status change */
+	int	(*sub)(Biobuf*, uint8_t*, int n);	/* routine for subnegotiation */
+	char	remote;				/* remote value */
+	char	local;				/* local value */
+};
+
+Opt opt[] =
+{
+	[Binary]	= { "binary",			0,  0, },
+	[Echo]		= { "echo",			1,  0, },
+	[SGA]		= { "suppress Go Ahead",	3,  0, },
+	[Stat]		= { "status",			5,  1, },
+	[Timing]	= { "timing",			6,  1, },
+	[Det]		= { "det",			20, 1, },
+	[Term]		= { "terminal",			24, 0, },
+	[EOR]		= { "end of record",		25, 1, },
+	[Uid]		= { "uid",			26, 1, },
+	[Outmark]	= { "outmark",			27, 1, },
+	[Ttyloc]	= { "ttyloc",			28, 1, },
+	[M3270]		= { "3270 mode",		29, 1, },
+	[Padx3]		= { "pad x.3",			30, 1, },
+	[Window]	= { "window size",		31, 1, },
+	[Speed]		= { "speed",			32, 1, },
+	[Flow]		= { "flow control",		33, 1, },
+	[Line]		= { "line mode",		34, 1, },
+	[Xloc]		= { "X display loc",		35, 0, },
+	[Extend]	= { "Extended",			255, 1, },
+};
+
+int	control(Biobuf*, int);
+Opt*	findopt(int);
+int	will(Biobuf*);
+int	wont(Biobuf*);
+int	doit(Biobuf*);
+int	dont(Biobuf*);
+int	sub(Biobuf*);
+int	send2(int, int, int);
+int	send3(int, int, int, int);
+int	sendnote(int, char*);
+void	fatal(char*, void*, void*);
+char*	syserr(void);
+int	wasintr(void);
+long	iread(int, void*, int);
+long	iwrite(int, void*, int);
+void	binit(Biobuf*, int);
+void	berase(Biobuf*);
+void	bkill(Biobuf*);
+
+/*
+ *  parse telnet control messages
+ */
+int
+control(Biobuf *bp, int c)
+{
+	if(c < 0)
+		return -1;
+	switch(c){
+	case AreYouThere:
+		fprint(Bfildes(bp), "Plan 9 telnet, version 1\r\n");
+		break;
+	case Sb:
+		return sub(bp);
+	case Will:
+		return will(bp);
+	case Wont:
+		return wont(bp);
+	case Do:
+		return doit(bp);
+	case Dont:
+		return dont(bp);
+	case Se:
+		fprint(2, "telnet: SE without an SB\n");
+		break;
+	default:
+		break;
+	}
+	return 0;
+}
+
+Opt*
+findopt(int c)
+{
+	Opt *o;
+
+	for(o = opt; o <= &opt[Extend]; o++)
+		if(o->code == c)
+			return o;
+	return 0;
+}
+
+int
+will(Biobuf *bp)
+{
+	Opt *o;
+	int c;
+	int rv = 0;
+
+	c = Bgetc(bp);
+	if(c < 0)
+		return -1;
+	DPRINT(2, "will %d\n", c);
+	o = findopt(c);
+	if(o == 0){
+		send3(Bfildes(bp), Iac, Dont, c);
+		return 0;
+	}
+	if(o->noway)
+		send3(Bfildes(bp), Iac, Dont, c);
+	else if(o->remote == 0)
+		rv |= send3(Bfildes(bp), Iac, Do, c);
+	if(o->remote == 0){
+		if(o->change)
+			rv |= (*o->change)(bp, Will);
+	}
+	o->remote = 1;
+	return rv;
+}
+
+int
+wont(Biobuf *bp)
+{
+	Opt *o;
+	int c;
+	int rv = 0;
+
+	c = Bgetc(bp);
+	if(c < 0)
+		return -1;
+	DPRINT(2, "wont %d\n", c);
+	o = findopt(c);
+	if(o == 0)
+		return 0;
+	if(o->remote){
+		if(o->change)
+			rv |= (*o->change)(bp, Wont);
+		rv |= send3(Bfildes(bp), Iac, Dont, c);
+	}
+	o->remote = 0;
+	return rv;
+}
+
+int
+doit(Biobuf *bp)
+{
+	Opt *o;
+	int c;
+	int rv = 0;
+
+	c = Bgetc(bp);
+	if(c < 0)
+		return -1;
+	DPRINT(2, "do %d\n", c);
+	o = findopt(c);
+	if(o == 0 || o->noway){
+		send3(Bfildes(bp), Iac, Wont, c);
+		return 0;
+	}
+	if(o->noway)
+		return 0;
+	if(o->local == 0){
+		if(o->change)
+			rv |= (*o->change)(bp, Do);
+		rv |= send3(Bfildes(bp), Iac, Will, c);
+	}
+	o->local = 1;
+	return rv;
+}
+
+int
+dont(Biobuf *bp)
+{
+	Opt *o;
+	int c;
+	int rv = 0;
+
+	c = Bgetc(bp);
+	if(c < 0)
+		return -1;
+	DPRINT(2, "dont %d\n", c);
+	o = findopt(c);
+	if(o == 0)
+		return 0;
+	if(o->noway)
+		return 0;
+	if(o->local){
+		o->local = 0;
+		if(o->change)
+			rv |= (*o->change)(bp, Dont);
+		rv |= send3(Bfildes(bp), Iac, Wont, c);
+	}
+	o->local = 0;
+	return rv;
+}
+
+/* read in a subnegotiation message and pass it to a routine for that option */
+int
+sub(Biobuf *bp)
+{
+	uint8_t subneg[128];
+	uint8_t *p;
+	Opt *o;
+	int c;
+
+	p = subneg;
+	for(;;){
+		c = Bgetc(bp);
+		if(c == Iac){
+			c = Bgetc(bp);
+			if(c == Se)
+				break;
+			if(p < &subneg[sizeof(subneg)])
+				*p++ = Iac;
+		}
+		if(c < 0)
+			return -1;
+		if(p < &subneg[sizeof(subneg)])
+			*p++ = c;
+	}
+	if(p == subneg)
+		return 0;
+	DPRINT(2, "sub %d %d n = %d\n", subneg[0], subneg[1], (int)(p - subneg - 1));
+	o = findopt(subneg[0]);
+	if(o == 0 || o->sub == 0)
+		return 0;
+	return (*o->sub)(bp, subneg+1, p - subneg - 1);
+}
+
+void
+sendd(int c0, int c1)
+{
+	char *t = 0;
+
+	switch(c0){
+	case Will:
+		t = "Will";
+		break;
+	case Wont:
+		t = "Wont";
+		break;
+	case Do:
+		t = "Do";
+		break;
+	case Dont:
+		t = "Dont";
+		break;
+	}
+	if(t)
+		DPRINT(2, "r %s %d\n", t, c1);
+}
+
+int
+send2(int f, int c0, int c1)
+{
+	uint8_t buf[2];
+
+	buf[0] = c0;
+	buf[1] = c1;
+	return iwrite(f, buf, 2) == 2 ? 0 : -1;
+}
+
+int
+send3(int f, int c0, int c1, int c2)
+{
+	uint8_t buf[3];
+
+	buf[0] = c0;
+	buf[1] = c1;
+	buf[2] = c2;
+	sendd(c1, c2);
+	return iwrite(f, buf, 3) == 3 ? 0 : -1;
+}
+
+int
+sendnote(int pid, char *msg)
+{
+	int fd;
+	char name[128];
+
+	sprint(name, "/proc/%d/note", pid);
+	fd = open(name, OWRITE);
+	if(fd < 0)
+		return -1;
+	if(write(fd, msg, strlen(msg))!=strlen(msg))
+		return -1;
+	return close(fd);
+}
+
+void
+fatal(char *fmt, void *a0, void *a1)
+{
+	char buf[128];
+
+	sprint(buf, fmt, a0, a1);
+	fprint(2, "%s: %s\n", argv0, buf);
+	exits(buf);
+}
+
+char*
+syserr(void)
+{
+	static char err[ERRMAX];
+
+	errstr(err, sizeof err);
+	return err;
+}
+
+int
+wasintr(void)
+{
+	return strcmp(syserr(), "interrupted") == 0;
+}
+
+long
+iread(int f, void *a, int n)
+{
+	long m;
+
+	for(;;){
+		m = read(f, a, n);
+		if(m >= 0 || !wasintr())
+			break;
+	}
+	return m;
+}
+
+long
+iwrite(int f, void *a, int n)
+{
+	long m;
+
+	m = write(f, a, n);
+	if(m < 0 && wasintr())
+		return n;
+	return m;
+}

+ 657 - 0
sys/src/cmd/ip/telnetd.c

@@ -0,0 +1,657 @@
+/*
+ * 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.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+
+#include "../ip/telnet.h"
+
+/*  console state (for consctl) */
+typedef struct Consstate	Consstate;
+struct Consstate{
+	int raw;
+	int hold;
+};
+Consstate *cons;
+
+int notefd;		/* for sending notes to the child */
+int noproto;		/* true if we shouldn't be using the telnet protocol */
+int trusted;		/* true if we need not authenticate - current user
+				is ok */
+int nonone = 1;		/* don't allow none logins */
+int noworldonly;	/* only noworld accounts */
+
+enum
+{
+	Maxpath=	256,
+	Maxuser=	64,
+	Maxvar=		32,
+};
+
+/* input and output buffers for network connection */
+Biobuf	netib;
+Biobuf	childib;
+char	remotesys[Maxpath];	/* name of remote system */
+
+int	alnum(int);
+int	conssim(void);
+int	fromchild(char*, int);
+int	fromnet(char*, int);
+int	termchange(Biobuf*, int);
+int	termsub(Biobuf*, uint8_t*, int);
+int	xlocchange(Biobuf*, int);
+int	xlocsub(Biobuf*, uint8_t*, int);
+int	challuser(char*);
+int	noworldlogin(char*);
+void*	share(uint32_t);
+int	doauth(char*);
+
+#define TELNETLOG "telnet"
+
+void
+logit(char *fmt, ...)
+{
+	va_list arg;
+	char buf[8192];
+
+	va_start(arg, fmt);
+	vseprint(buf, buf + sizeof(buf) / sizeof(*buf), fmt, arg);
+	va_end(arg);
+	syslog(0, TELNETLOG, "(%s) %s", remotesys, buf);
+}
+
+void
+getremote(char *dir)
+{
+	int fd, n;
+	char remfile[Maxpath];
+
+	sprint(remfile, "%s/remote", dir);
+	fd = open(remfile, OREAD);
+	if(fd < 0)
+		strcpy(remotesys, "unknown2");
+	n = read(fd, remotesys, sizeof(remotesys)-1);
+	if(n>0)
+		remotesys[n-1] = 0;
+	else
+		strcpy(remotesys, remfile);
+	close(fd);
+}
+
+void
+main(int argc, char *argv[])
+{
+	char buf[1024];
+	int fd;
+	char user[Maxuser];
+	int tries = 0;
+	int childpid;
+	int n, eofs;
+
+	memset(user, 0, sizeof(user));
+	ARGBEGIN {
+	case 'n':
+		opt[Echo].local = 1;
+		noproto = 1;
+		break;
+	case 'p':
+		noproto = 1;
+		break;
+	case 'a':
+		nonone = 0;
+		break;
+	case 't':
+		trusted = 1;
+		strncpy(user, getuser(), sizeof(user)-1);
+		break;
+	case 'u':
+		strncpy(user, ARGF(), sizeof(user)-1);
+		break;
+	case 'd':
+		debug = 1;
+		break;
+	case 'N':
+		noworldonly = 1;
+		break;
+	} ARGEND
+
+	if(argc)
+		getremote(argv[argc-1]);
+	else
+		strcpy(remotesys, "unknown");
+
+	/* options we need routines for */
+	opt[Term].change = termchange;
+	opt[Term].sub = termsub;
+	opt[Xloc].sub = xlocsub;
+
+	/* setup default telnet options */
+	if(!noproto){
+		send3(1, Iac, Will, opt[Echo].code);
+		send3(1, Iac, Do, opt[Term].code);
+		send3(1, Iac, Do, opt[Xloc].code);
+	}
+
+	/* shared data for console state */
+	cons = share(sizeof(Consstate));
+	if(cons == 0)
+		fatal("shared memory", 0, 0);
+
+	/* authenticate and create new name space */
+	Binit(&netib, 0, OREAD);
+	if (!trusted){
+		while(doauth(user) < 0)
+			if(++tries == 5){
+				logit("failed as %s: %r", user);
+				print("authentication failure:%r\r\n");
+				exits("authentication");
+			}
+	}
+	logit("logged in as %s", user);
+	putenv("service", "con");
+
+	/* simulate /dev/consctl and /dev/cons using pipes */
+	fd = conssim();
+	if(fd < 0)
+		fatal("simulating", 0, 0);
+	Binit(&childib, fd, OREAD);
+
+	/* start a shell in a different process group */
+	switch(childpid = rfork(RFPROC|RFNAMEG|RFFDG|RFNOTEG)){
+	case -1:
+		fatal("fork", 0, 0);
+	case 0:
+		close(fd);
+		fd = open("/dev/cons", OREAD);
+		dup(fd, 0);
+		close(fd);
+		fd = open("/dev/cons", OWRITE);
+		dup(fd, 1);
+		dup(fd, 2);
+		close(fd);
+		segdetach(cons);
+		execl("/bin/rc", "rc", "-il", nil);
+		fatal("/bin/rc", 0, 0);
+	default:
+		sprint(buf, "/proc/%d/notepg", childpid);
+		notefd = open(buf, OWRITE);
+		break;
+	}
+
+	/* two processes to shuttle bytes twixt children and network */
+	switch(fork()){
+	case -1:
+		fatal("fork", 0, 0);
+	case 0:
+		eofs = 0;
+		for(;;){
+			n = fromchild(buf, sizeof(buf));
+			if(n <= 0){
+				if(eofs++ > 2)
+					break;
+				continue;
+			}
+			eofs = 0;
+			if(write(1, buf, n) != n)
+				break;
+		}
+		break;
+	default:
+		while((n = fromnet(buf, sizeof(buf))) >= 0)
+			if(write(fd, buf, n) != n)
+				break;
+		break;
+	}
+
+	/* kill off all server processes */
+	sprint(buf, "/proc/%d/notepg", getpid());
+	fd = open(buf, OWRITE);
+	write(fd, "die", 3);
+	exits(0);
+}
+
+void
+prompt(char *p, char *b, int n, int raw)
+{
+	char *e;
+	int i;
+	int echo;
+
+	echo = opt[Echo].local;
+	if(raw)
+		opt[Echo].local = 0;
+	print("%s: ", p);
+	for(e = b+n; b < e;){
+		i = fromnet(b, e-b);
+		if(i <= 0)
+			exits("fromnet: hungup");
+		b += i;
+		if(*(b-1) == '\n' || *(b-1) == '\r'){
+			*(b-1) = 0;
+			break;
+		}
+	}
+	if(raw)
+		opt[Echo].local = echo;
+}
+
+/*
+ *  challenge user
+ */
+int
+challuser(char *user)
+{
+	char nchall[64];
+	char response[64];
+	Chalstate *ch;
+	AuthInfo *ai;
+
+	if(strcmp(user, "none") == 0){
+		if(nonone)
+			return -1;
+		newns("none", nil);
+		return 0;
+	}
+	if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
+		return -1;
+	snprint(nchall, sizeof nchall, "challenge: %s\r\nresponse", ch->chal);
+	prompt(nchall, response, sizeof response, 0);
+	ch->resp = response;
+	ch->nresp = strlen(response);
+	ai = auth_response(ch);
+	auth_freechal(ch);
+	if(ai == nil){
+		rerrstr(response, sizeof response);
+		print("!%s\n", response);
+		return -1;
+	}
+	if(auth_chuid(ai, nil) < 0)
+		return -1;
+	return 0;
+}
+/*
+ *  use the in the clear apop password to change user id
+ */
+int
+noworldlogin(char *user)
+{
+	char password[256];
+
+	prompt("password", password, sizeof(password), 1);
+	if(login(user, password, "/lib/namespace.noworld") < 0)
+		return -1;
+	rfork(RFNOMNT);	/* sandbox */
+	return 0;
+}
+
+int
+doauth(char *user)
+{
+	if(*user == 0)
+		prompt("user", user, Maxuser, 0);
+	if(noworld(user))
+		return noworldlogin(user);
+	if(noworldonly)
+		return -1;
+	return challuser(user);
+
+}
+
+/*
+ *  Process some input from the child, add protocol if needed.  If
+ *  the input buffer goes empty, return.
+ */
+int
+fromchild(char *bp, int len)
+{
+	int c;
+	char *start;
+
+	for(start = bp; bp-start < len-1; ){
+		c = Bgetc(&childib);
+		if(c < 0){
+			if(bp == start)
+				return -1;
+			else
+				break;
+		}
+		if(cons->raw == 0 && c == '\n')
+			*bp++ = '\r';
+		*bp++ = c;
+		if(Bbuffered(&childib) == 0)
+			break;
+	}
+	return bp-start;
+}
+
+/*
+ *  Read from the network up to a '\n' or some other break.
+ *
+ *  If in binary mode, buffer characters but don't
+ *
+ *  The following characters are special:
+ *	'\r\n's and '\r's get turned into '\n's.
+ *	^H erases the last character buffered.
+ *	^U kills the whole line buffered.
+ *	^W erases the last word
+ *	^D causes a 0-length line to be returned.
+ *	Intr causes an "interrupt" note to be sent to the children.
+ */
+#define ECHO(c) { *ebp++ = (c); }
+int
+fromnet(char *bp, int len)
+{
+	int c;
+	char echobuf[1024];
+	char *ebp;
+	char *start;
+	static int crnl;
+	static int doeof;
+
+
+	/* simulate an EOF as a 0 length input */
+	if(doeof){
+		doeof = 0;
+		return 0;
+	}
+
+	for(ebp = echobuf,start = bp; bp-start < len && ebp-echobuf < sizeof(echobuf); ){
+		c = Bgetc(&netib);
+		if(c < 0){
+			if(bp == start)
+				return -1;
+			else
+				break;
+		}
+
+		/* telnet protocol only */
+		if(!noproto){
+			/* protocol messages */
+			switch(c){
+			case Iac:
+				crnl = 0;
+				c = Bgetc(&netib);
+				if(c == Iac)
+					break;
+				control(&netib, c);
+				continue;
+			}
+
+		}
+
+		/* \r\n or \n\r become \n  */
+		if(c == '\r' || c == '\n'){
+			if(crnl && crnl != c){
+				crnl = 0;
+				continue;
+			}
+			if(cons->raw == 0 && opt[Echo].local){
+				ECHO('\r');
+				ECHO('\n');
+			}
+			crnl = c;
+			if(cons->raw == 0)
+				*bp++ = '\n';
+			else
+				*bp++ = c;
+			break;
+		} else
+			crnl = 0;
+
+		/* raw processing (each character terminates */
+		if(cons->raw){
+			*bp++ = c;
+			break;
+		}
+
+		/* in binary mode, there are no control characters */
+		if(opt[Binary].local){
+			if(opt[Echo].local)
+				ECHO(c);
+			*bp++ = c;
+			continue;
+		}
+
+		/* cooked processing */
+		switch(c){
+		case 0x00:
+			if(noproto)		/* telnet ignores nulls */
+				*bp++ = c;
+			continue;
+		case 0x04:
+			if(bp != start)
+				doeof = 1;
+			goto out;
+
+		case 0x08:	/* ^H */
+			if(start < bp)
+				bp--;
+			if(opt[Echo].local)
+				ECHO(c);
+			break;
+
+		case 0x15:	/* ^U */
+			bp = start;
+			if(opt[Echo].local){
+				ECHO('^');
+				ECHO('U');
+				ECHO('\r');
+				ECHO('\n');
+			}
+			break;
+
+		case 0x17:	/* ^W */
+			if (opt[Echo].local) {
+				while (--bp >= start && !alnum(*bp))
+					ECHO('\b');
+				while (bp >= start && alnum(*bp)) {
+					ECHO('\b');
+					bp--;
+				}
+				bp++;
+			}
+			break;
+
+		case 0x7f:	/* Del */
+			write(notefd, "interrupt", 9);
+			bp = start;
+			break;
+
+		default:
+			if(opt[Echo].local)
+				ECHO(c);
+			*bp++ = c;
+		}
+		if(ebp != echobuf)
+			write(1, echobuf, ebp-echobuf);
+		ebp = echobuf;
+	}
+out:
+	if(ebp != echobuf)
+		write(1, echobuf, ebp-echobuf);
+	return bp - start;
+}
+
+int
+termchange(Biobuf *bp, int cmd)
+{
+	unsigned char buf[8];
+	unsigned char *p = buf;
+
+	if(cmd != Will)
+		return 0;
+
+	/* ask other side to send term type info */
+	*p++ = Iac;
+	*p++ = Sb;
+	*p++ = opt[Term].code;
+	*p++ = 1;
+	*p++ = Iac;
+	*p++ = Se;
+	return iwrite(Bfildes(bp), buf, p-buf);
+}
+
+int
+termsub(Biobuf *bp, uint8_t *sub, int n)
+{
+	char term[Maxvar];
+
+	USED(bp);
+	if(n-- < 1 || sub[0] != 0)
+		return 0;
+	if(n >= sizeof term)
+		n = sizeof term;
+	strncpy(term, (char*)sub, n);
+	putenv("TERM", term);
+	return 0;
+}
+
+int
+xlocchange(Biobuf *bp, int cmd)
+{
+	unsigned char buf[8];
+	unsigned char *p = buf;
+
+	if(cmd != Will)
+		return 0;
+
+	/* ask other side to send x display info */
+	*p++ = Iac;
+	*p++ = Sb;
+	*p++ = opt[Xloc].code;
+	*p++ = 1;
+	*p++ = Iac;
+	*p++ = Se;
+	return iwrite(Bfildes(bp), buf, p-buf);
+}
+
+int
+xlocsub(Biobuf *bp, uint8_t *sub, int n)
+{
+	char xloc[Maxvar];
+
+	USED(bp);
+	if(n-- < 1 || sub[0] != 0)
+		return 0;
+	if(n >= sizeof xloc)
+		n = sizeof xloc;
+	strncpy(xloc, (char*)sub, n);
+	putenv("DISPLAY", xloc);
+	return 0;
+}
+
+/*
+ *  create a shared segment.  Make is start 2 meg higher than the current
+ *  end of process memory.
+ */
+void*
+share(uint32_t len)
+{
+	uint8_t *vastart;
+
+	vastart = sbrk(0);
+	if(vastart == (void*)-1)
+		return 0;
+	vastart += 2*1024*1024;
+
+	if(segattach(0, "shared", vastart, len) == (void*)-1)
+		return 0;
+
+	return vastart;
+}
+
+/*
+ *  bind a pipe onto consctl and keep reading it to
+ *  get changes to console state.
+ */
+int
+conssim(void)
+{
+	int i, n;
+	int fd;
+	int tries;
+	char buf[128];
+	char *field[10];
+
+	/* a pipe to simulate the /dev/cons */
+	if(bind("#|", "/mnt/cons/cons", MREPL) < 0)
+		fatal("/dev/cons1", 0, 0);
+	if(bind("/mnt/cons/cons/data1", "/dev/cons", MREPL) < 0)
+		fatal("/dev/cons2", 0, 0);
+
+	/* a pipe to simulate consctl */
+	if(bind("#|", "/mnt/cons/consctl", MBEFORE) < 0
+	|| bind("/mnt/cons/consctl/data1", "/dev/consctl", MREPL) < 0)
+		fatal("/dev/consctl", 0, 0);
+
+	/* a process to read /dev/consctl and set the state in cons */
+	switch(fork()){
+	case -1:
+		fatal("forking", 0, 0);
+	case 0:
+		break;
+	default:
+		return open("/mnt/cons/cons/data", ORDWR);
+	}
+
+	for(tries = 0; tries < 100; tries++){
+		cons->raw = 0;
+		cons->hold = 0;
+		fd = open("/mnt/cons/consctl/data", OREAD);
+		if(fd < 0)
+			continue;
+		tries = 0;
+		for(;;){
+			n = read(fd, buf, sizeof(buf)-1);
+			if(n <= 0)
+				break;
+			buf[n] = 0;
+			n = getfields(buf, field, 10, 1, " ");
+			for(i = 0; i < n; i++){
+				if(strcmp(field[i], "rawon") == 0) {
+					if(debug) fprint(2, "raw = 1\n");
+					cons->raw = 1;
+				} else if(strcmp(field[i], "rawoff") == 0) {
+					if(debug) fprint(2, "raw = 0\n");
+					cons->raw = 0;
+				} else if(strcmp(field[i], "holdon") == 0) {
+					cons->hold = 1;
+					if(debug) fprint(2, "raw = 1\n");
+				} else if(strcmp(field[i], "holdoff") == 0) {
+					cons->hold = 0;
+					if(debug) fprint(2, "raw = 0\n");
+				}
+			}
+		}
+		close(fd);
+	}
+	exits(0);
+	return -1;
+}
+
+int
+alnum(int c)
+{
+	/*
+	 * Hard to get absolutely right.  Use what we know about ASCII
+	 * and assume anything above the Latin control characters is
+	 * potentially an alphanumeric.
+	 */
+	if(c <= ' ')
+		return 0;
+	if(0x7F<=c && c<=0xA0)
+		return 0;
+	if(strchr("!\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", c))
+		return 0;
+	return 1;
+}