code: 9ferno

ref: b502a62da2ec6058923db94f87ecc2d29db2fa77
dir: /appl/cmd/telnet.b/

View raw version
implement Telnet;

include "sys.m";
	sys: Sys;

include "draw.m";

include "dial.m";
	dial: Dial;
	Connection: import dial;

Telnet: module
{
	init:	fn(ctxt: ref Draw->Context, args: list of string);
};

Debug: con 0;

Inbuf: adt {
	fd:	ref Sys->FD;
	out:	ref Outbuf;
	buf:	array of byte;
	ptr:	int;
	nbyte:	int;
};

Outbuf: adt {
	buf:	array of byte;
	ptr:	int;
};

BS:		con 8;		# ^h backspace character
BSW:		con 23;		# ^w bacspace word
BSL:		con 21;		# ^u backspace line
EOT:		con 4;		# ^d end of file
ESC:		con 27;		# hold mode

net:	ref Connection;
stdin, stdout, stderr: ref Sys->FD;

# control characters
Se:			con 240;	# end subnegotiation
NOP:			con 241;
Mark:		con 242;	# data mark
Break:		con 243;
Interrupt:		con 244;
Abort:		con 245;	# TENEX ^O
AreYouThere:	con 246;
Erasechar:	con 247;	# erase last character
Eraseline:		con 248;	# erase line
GoAhead:		con 249;	# half duplex clear to send
Sb:			con 250;	# start subnegotiation
Will:			con 251;
Wont:		con 252;
Do:			con 253;
Dont:		con 254;
Iac:			con 255;

# options
Binary, Echo, SGA, Stat, Timing,
Det, Term, EOR, Uid, Outmark,
Ttyloc, M3270, Padx3, Window, Speed,
Flow, Line, Xloc, Extend: con iota;

Opt: adt
{
	name:	string;
	code:	int;
	noway:	int;	
	remote:	int;		# remote value
	local:	int;		# local value
};

opt := array[] of
{
	Binary =>		Opt("binary",			0,	0,	0, 	0),
	Echo	=>		Opt("echo",			1,  	0, 	0,	0),
	SGA	=>		Opt("suppress go ahead",	3,  	0, 	0,	0),
	Stat =>		Opt("status",			5,  	1, 	0,	0),
	Timing =>		Opt("timing",			6,  	1, 	0,	0),
	Det=>		Opt("det",				20, 	1, 	0,	0),
	Term =>		Opt("terminal",			24, 	0, 	0,	0),
	EOR =>		Opt("end of record",		25, 	1, 	0,	0),
	Uid =>		Opt("uid",				26, 	1, 	0,	0),
	Outmark => 	Opt("outmark",			27, 	1, 	0,	0),
	Ttyloc =>		Opt("ttyloc",			28, 	1, 	0,	0),
	M3270 =>		Opt("3270 mode",		29, 	1, 	0,	0),
	Padx3 =>		Opt("pad x.3",			30, 	1, 	0,	0),
	Window =>	Opt("window size",		31, 	1, 	0,	0),
	Speed =>		Opt("speed",			32, 	1, 	0,	0),
	Flow	=>		Opt("flow control",		33, 	1, 	0,	0),
	Line	=>		Opt("line mode",		34, 	1, 	0,	0),
	Xloc	=>		Opt("X display loc",		35, 	1, 	0,	0),
	Extend =>		Opt("Extended",		255,	1, 	0,	0),
};

usage()
{
	sys->fprint(stderr, "usage: telnet host [port]\n");
	raise "fail:usage";
}

init(nil: ref Draw->Context, argv: list of string)
{
	sys = load Sys Sys->PATH;
	stderr = sys->fildes(2);
	stdout = sys->fildes(1);
	stdin = sys->fildes(0);
	dial = load Dial Dial->PATH;

	if (len argv < 2)
		usage();
	argv = tl argv;
	host := hd argv;
	argv = tl argv;
	port := "23";
	if(argv != nil)
		port = hd argv;
	connect(host, port);
}

ccfd: ref Sys->FD;
connect(addr: string, port: string)
{
	dest := dial->netmkaddr(addr, "tcp", port);
	net = dial->dial(dest, nil);
	if(net == nil) {
		sys->fprint(stderr, "telnet: can't dial %s: %r\n", dest);
		raise "fail:dial";
	}
	sys->fprint(stderr, "telnet: connected to %s\n", addr);

	raw(1);
	pidch := chan of int;
	finished := chan of int;
	spawn fromnet(pidch, finished);
	spawn fromuser(pidch, finished);
	pids := array[2] of {* => <-pidch};
	kill(pids[<-finished == pids[0]]);
	raw(0);
}


fromuser(pidch, finished: chan of int)
{
	pidch <-= sys->pctl(0, nil);
	b := array[1024] of byte;
	while((n := sys->read(stdin, b, len b)) > 0) {
		if (opt[Echo].remote == 0)
			sys->write(stdout, b, n);
		sys->write(net.dfd, b, n);
	}
	sys->fprint(stderr, "telnet: error reading stdin: %r\n");
	finished <-= sys->pctl(0, nil);
}

getc(b: ref Inbuf): int
{
	if(b.nbyte == 0) {
		if(b.out != nil)
			flushout(b.out);
		b.nbyte = sys->read(b.fd, b.buf, len b.buf);
		if(b.nbyte <= 0)
			return -1;
		b.ptr = 0;
	}
	b.nbyte--;
	return int b.buf[b.ptr++];
}

putc(b: ref Outbuf, c: int)
{
	b.buf[b.ptr++] = byte c;
	if(b.ptr == len b.buf)
		flushout(b);
}

flushout(b: ref Outbuf)
{
	sys->write(stdout, b.buf, b.ptr);
	b.ptr = 0;
}

BUFSIZE: con 2048;
fromnet(pidch, finished: chan of int)
{
	pidch <-= sys->pctl(0, nil);
	conout := ref Outbuf(array[BUFSIZE] of byte, 0);
	netinp := ref Inbuf(net.dfd, conout, array[BUFSIZE] of byte, 0, 0);

loop:	for(;;) {
		c := getc(netinp);	
		case c {
		-1 =>
			break loop;
		Iac  =>
			c = getc(netinp);
			if(c != Iac) {
				flushout(conout);
				if(control(netinp, c) < 0)
					break loop;
			} else
				putc(conout, c);
		* =>
			putc(conout, c);
		}
	}
	sys->fprint(stderr, "telnet: remote host closed connection\n");
	finished <-= sys->pctl(0, nil);
}

control(bp: ref Inbuf, c: int): int
{
	r := 0;
	case c {
	AreYouThere =>
		sys->fprint(net.dfd, "Inferno telnet\r\n");
	Sb =>
		r = sub(bp);
	Will =>
		r = will(bp);
	Wont =>
		r = wont(bp);
	Do =>
		r = doit(bp);
	Dont =>
		r = dont(bp);
	Se =>
		sys->fprint(stderr, "telnet: SE without an SB\n");
	-1 =>
		r = -1;
	}

	return r;
}

sub(bp: ref Inbuf): int
{
	subneg: string;
	i := 0;
	for(;;){
		c := getc(bp);
		if(c == Iac) {
			c = getc(bp);
			if(c == Se)
				break;
			subneg[i++] = Iac;
		}
		if(c < 0)
			return -1;
		subneg[i++] = c;
	}
	if(i == 0)
		return 0;

	if (Debug)
		sys->fprint(stderr, "telnet: sub(%s, %d, n = %d)\n", optname(subneg[0]), subneg[1], i);

	for(i = 0; i < len opt; i++)
		if(opt[i].code == subneg[0])
			break;

	if(i >= len opt)
		return 0;

	case i {
	Term =>
		sbsend(opt[Term].code, array of byte "network");	
	}

	return 0;
}

sbsend(code: int, data: array of byte): int
{
	buf := array[4+len data+2] of byte;
	o := 4+len data;

	buf[0] = byte Iac;
	buf[1] = byte Sb;
	buf[2] = byte code;
	buf[3] = byte 0;
	buf[4:] = data;
	buf[o] = byte Iac;
	o++;
	buf[o] = byte Se;

	return sys->write(net.dfd, buf, len buf);
}

will(bp: ref Inbuf): int
{
	c := getc(bp);
	if(c < 0)
		return -1;

	if (Debug)
		sys->fprint(stderr, "telnet: will(%s)\n", optname(c));

	for(i := 0; i < len opt; i++)
		if(opt[i].code == c)
			break;

	if(i >= len opt) {
		send3(bp, Iac, Dont, c);
		return 0;
	}

	rv := 0;
	if(opt[i].noway)
		send3(bp, Iac, Dont, c);
	else
	if(opt[i].remote == 0)
		rv |= send3(bp, Iac, Do, c);

	if(opt[i].remote == 0)
		rv |= change(bp, i, Will);
	opt[i].remote = 1;
	return rv;
}

wont(bp: ref Inbuf): int
{
	c := getc(bp);
	if(c < 0)
		return -1;

	if (Debug)
		sys->fprint(stderr, "telnet: wont(%s)\n", optname(c));

	for(i := 0; i < len opt; i++)
		if(opt[i].code == c)
			break;

	if(i >= len opt)
		return 0;

	rv := 0;
	if(opt[i].remote) {
		rv |= change(bp, i, Wont);
		rv |= send3(bp, Iac, Dont, c);
	}
	opt[i].remote = 0;
	return rv;
}

doit(bp: ref Inbuf): int
{
	c := getc(bp);
	if(c < 0)
		return -1;

	if (Debug)
		sys->fprint(stderr, "telnet: do(%s)\n", optname(c));

	for(i := 0; i < len opt; i++)
		if(opt[i].code == c)
			break;

	if(i >= len opt || opt[i].noway) {
		send3(bp, Iac, Wont, c);
		return 0;
	}
	rv := 0;
	if(opt[i].local == 0) {
		rv |= change(bp, i, Do);
		rv |= send3(bp, Iac, Will, c);
	}
	opt[i].local = 1;
	return rv;
}

dont(bp: ref Inbuf): int
{
	c := getc(bp);
	if(c < 0)
		return -1;

	if (Debug)
		sys->fprint(stderr, "telnet: dont(%s)\n", optname(c));

	for(i := 0; i < len opt; i++)
		if(opt[i].code == c)
			break;

	if(i >= len opt || opt[i].noway)
		return 0;

	rv := 0;
	if(opt[i].local){
		opt[i].local = 0;
		rv |= change(bp, i, Dont);
		rv |= send3(bp, Iac, Wont, c);
	}
	opt[i].local = 0;
	return rv;
}

change(bp: ref Inbuf, o: int, what: int): int
{
	if(bp != nil)
		{}
	if(o != 0)
		{}
	if(what != 0)
		{}
	return 0;
}

send3(bp: ref Inbuf, c0: int, c1: int, c2: int): int
{
	if (Debug)
		sys->fprint(stderr, "telnet: reply(%s(%s))\n", negname(c1), optname(c2));
		
	buf := array[3] of byte;

	buf[0] = byte c0;
	buf[1] = byte c1;
	buf[2] = byte c2;

	if (sys->write(bp.fd, buf, 3) != 3)
		return -1;
	return 0;
}

kill(pid: int): int
{
	fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
	if (fd == nil)
		return -1;
	if (sys->write(fd, array of byte "kill", 4) != 4)
		return -1;
	return 0;
}

negname(c: int): string
{
	t := "Unknown";
	case c {
	Will =>	t = "will";
	Wont =>	t = "wont";
	Do =>	t = "do";
	Dont =>	t = "dont";
	}
	return t;
}

optname(c: int): string
{
	for (i := 0; i < len opt; i++)
		if (opt[i].code == c)
			return opt[i].name;
	return "unknown";
}

raw(on: int)
{
	if(ccfd == nil) {
		ccfd = sys->open("/dev/consctl", Sys->OWRITE);
		if(ccfd == nil) {
			sys->fprint(stderr, "telnet: cannot open /dev/consctl: %r\n");
			return;
		}
	}
	if(on)
		sys->fprint(ccfd, "rawon");
	else
		sys->fprint(ccfd, "rawoff");
}