1/* vi: set sw=4 ts=4: */
2/*
3 * telnet implementation for busybox
4 *
5 * Author: Tomi Ollila <too@iki.fi>
6 * Copyright (C) 1994-2000 by Tomi Ollila
7 *
8 * Created: Thu Apr  7 13:29:41 1994 too
9 * Last modified: Fri Jun  9 14:34:24 2000 too
10 *
11 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
12 *
13 * HISTORY
14 * Revision 3.1  1994/04/17  11:31:54  too
15 * initial revision
16 * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org>
17 * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan
18 * <jam@ltsp.org>
19 * Modified 2004/02/11 to add ability to pass the USER variable to remote host
20 * by Fernando Silveira <swrh@gmx.net>
21 *
22 */
23
24#include <termios.h>
25#include <arpa/telnet.h>
26#include <netinet/in.h>
27#include "libbb.h"
28
29#ifdef DOTRACE
30#define TRACE(x, y) do { if (x) printf y; } while (0)
31#else
32#define TRACE(x, y)
33#endif
34
35enum {
36	DATABUFSIZE = 128,
37	IACBUFSIZE  = 128,
38
39	CHM_TRY = 0,
40	CHM_ON = 1,
41	CHM_OFF = 2,
42
43	UF_ECHO = 0x01,
44	UF_SGA = 0x02,
45
46	TS_0 = 1,
47	TS_IAC = 2,
48	TS_OPT = 3,
49	TS_SUB1 = 4,
50	TS_SUB2 = 5,
51};
52
53typedef unsigned char byte;
54
55struct globals {
56	int	netfd; /* console fd:s are 0 and 1 (and 2) */
57	short	iaclen; /* could even use byte */
58	byte	telstate; /* telnet negotiation state from network input */
59	byte	telwish;  /* DO, DONT, WILL, WONT */
60	byte    charmode;
61	byte    telflags;
62	byte	gotsig;
63	byte	do_termios;
64#if ENABLE_FEATURE_TELNET_TTYPE
65	char	*ttype;
66#endif
67#if ENABLE_FEATURE_TELNET_AUTOLOGIN
68	const char *autologin;
69#endif
70#if ENABLE_FEATURE_AUTOWIDTH
71	int	win_width, win_height;
72#endif
73	/* same buffer used both for network and console read/write */
74	char    buf[DATABUFSIZE];
75	/* buffer to handle telnet negotiations */
76	char    iacbuf[IACBUFSIZE];
77	struct termios termios_def;
78	struct termios termios_raw;
79};
80#define G (*(struct globals*)&bb_common_bufsiz1)
81void BUG_telnet_globals_too_big(void);
82#define INIT_G() do { \
83	if (sizeof(G) > COMMON_BUFSIZE) \
84		BUG_telnet_globals_too_big(); \
85	/* memset(&G, 0, sizeof G); - already is */ \
86} while (0)
87
88/* Function prototypes */
89static void rawmode(void);
90static void cookmode(void);
91static void do_linemode(void);
92static void will_charmode(void);
93static void telopt(byte c);
94static int subneg(byte c);
95
96static void iacflush(void)
97{
98	write(G.netfd, G.iacbuf, G.iaclen);
99	G.iaclen = 0;
100}
101
102#define write_str(fd, str) write(fd, str, sizeof(str) - 1)
103
104static void doexit(int ev)
105{
106	cookmode();
107	exit(ev);
108}
109
110static void conescape(void)
111{
112	char b;
113
114	if (G.gotsig)	/* came from line  mode... go raw */
115		rawmode();
116
117	write_str(1, "\r\nConsole escape. Commands are:\r\n\n"
118			" l	go to line mode\r\n"
119			" c	go to character mode\r\n"
120			" z	suspend telnet\r\n"
121			" e	exit telnet\r\n");
122
123	if (read(0, &b, 1) <= 0)
124		doexit(1);
125
126	switch (b) {
127	case 'l':
128		if (!G.gotsig) {
129			do_linemode();
130			goto rrturn;
131		}
132		break;
133	case 'c':
134		if (G.gotsig) {
135			will_charmode();
136			goto rrturn;
137		}
138		break;
139	case 'z':
140		cookmode();
141		kill(0, SIGTSTP);
142		rawmode();
143		break;
144	case 'e':
145		doexit(0);
146	}
147
148	write_str(1, "continuing...\r\n");
149
150	if (G.gotsig)
151		cookmode();
152
153 rrturn:
154	G.gotsig = 0;
155
156}
157
158static void handlenetoutput(int len)
159{
160	/*	here we could do smart tricks how to handle 0xFF:s in output
161	 *	stream  like writing twice every sequence of FF:s (thus doing
162	 *	many write()s. But I think interactive telnet application does
163	 *	not need to be 100% 8-bit clean, so changing every 0xff:s to
164	 *	0x7f:s
165	 *
166	 *	2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com)
167	 *	I don't agree.
168	 *	first - I cannot use programs like sz/rz
169	 *	second - the 0x0D is sent as one character and if the next
170	 *		 char is 0x0A then it's eaten by a server side.
171	 *	third - whay doy you have to make 'many write()s'?
172	 *		I don't understand.
173	 *	So I implemented it. It's realy useful for me. I hope that
174	 *	others people will find it interesting to.
175	 */
176
177	int i, j;
178	byte * p = (byte*)G.buf;
179	byte outbuf[4*DATABUFSIZE];
180
181	for (i = len, j = 0; i > 0; i--, p++)
182	{
183		if (*p == 0x1d)
184		{
185			conescape();
186			return;
187		}
188		outbuf[j++] = *p;
189		if (*p == 0xff)
190			outbuf[j++] = 0xff;
191		else if (*p == 0x0d)
192			outbuf[j++] = 0x00;
193	}
194	if (j > 0)
195		write(G.netfd, outbuf, j);
196}
197
198static void handlenetinput(int len)
199{
200	int i;
201	int cstart = 0;
202
203	for (i = 0; i < len; i++)
204	{
205		byte c = G.buf[i];
206
207		if (G.telstate == 0) /* most of the time state == 0 */
208		{
209			if (c == IAC)
210			{
211				cstart = i;
212				G.telstate = TS_IAC;
213			}
214		}
215		else
216			switch (G.telstate)
217			 {
218			 case TS_0:
219				 if (c == IAC)
220					 G.telstate = TS_IAC;
221				 else
222					 G.buf[cstart++] = c;
223				 break;
224
225			 case TS_IAC:
226				 if (c == IAC) /* IAC IAC -> 0xFF */
227				 {
228					 G.buf[cstart++] = c;
229					 G.telstate = TS_0;
230					 break;
231				 }
232				 /* else */
233				 switch (c)
234				 {
235				 case SB:
236					 G.telstate = TS_SUB1;
237					 break;
238				 case DO:
239				 case DONT:
240				 case WILL:
241				 case WONT:
242					 G.telwish =  c;
243					 G.telstate = TS_OPT;
244					 break;
245				 default:
246					 G.telstate = TS_0;	/* DATA MARK must be added later */
247				 }
248				 break;
249			 case TS_OPT: /* WILL, WONT, DO, DONT */
250				 telopt(c);
251				 G.telstate = TS_0;
252				 break;
253			 case TS_SUB1: /* Subnegotiation */
254			 case TS_SUB2: /* Subnegotiation */
255				 if (subneg(c))
256					 G.telstate = TS_0;
257				 break;
258			 }
259	}
260	if (G.telstate)
261	{
262		if (G.iaclen)			iacflush();
263		if (G.telstate == TS_0)	G.telstate = 0;
264
265		len = cstart;
266	}
267
268	if (len)
269		write(1, G.buf, len);
270}
271
272static void putiac(int c)
273{
274	G.iacbuf[G.iaclen++] = c;
275}
276
277static void putiac2(byte wwdd, byte c)
278{
279	if (G.iaclen + 3 > IACBUFSIZE)
280		iacflush();
281
282	putiac(IAC);
283	putiac(wwdd);
284	putiac(c);
285}
286
287#if ENABLE_FEATURE_TELNET_TTYPE
288static void putiac_subopt(byte c, char *str)
289{
290	int	len = strlen(str) + 6;   // ( 2 + 1 + 1 + strlen + 2 )
291
292	if (G.iaclen + len > IACBUFSIZE)
293		iacflush();
294
295	putiac(IAC);
296	putiac(SB);
297	putiac(c);
298	putiac(0);
299
300	while (*str)
301		putiac(*str++);
302
303	putiac(IAC);
304	putiac(SE);
305}
306#endif
307
308#if ENABLE_FEATURE_TELNET_AUTOLOGIN
309static void putiac_subopt_autologin(void)
310{
311	int len = strlen(G.autologin) + 6;	// (2 + 1 + 1 + strlen + 2)
312	const char *user = "USER";
313
314	if (G.iaclen + len > IACBUFSIZE)
315		iacflush();
316
317	putiac(IAC);
318	putiac(SB);
319	putiac(TELOPT_NEW_ENVIRON);
320	putiac(TELQUAL_IS);
321	putiac(NEW_ENV_VAR);
322
323	while (*user)
324		putiac(*user++);
325
326	putiac(NEW_ENV_VALUE);
327
328	while (*G.autologin)
329		putiac(*G.autologin++);
330
331	putiac(IAC);
332	putiac(SE);
333}
334#endif
335
336#if ENABLE_FEATURE_AUTOWIDTH
337static void putiac_naws(byte c, int x, int y)
338{
339	if (G.iaclen + 9 > IACBUFSIZE)
340		iacflush();
341
342	putiac(IAC);
343	putiac(SB);
344	putiac(c);
345
346	putiac((x >> 8) & 0xff);
347	putiac(x & 0xff);
348	putiac((y >> 8) & 0xff);
349	putiac(y & 0xff);
350
351	putiac(IAC);
352	putiac(SE);
353}
354#endif
355
356static char const escapecharis[] ALIGN1 = "\r\nEscape character is ";
357
358static void setConMode(void)
359{
360	if (G.telflags & UF_ECHO) {
361		if (G.charmode == CHM_TRY) {
362			G.charmode = CHM_ON;
363			printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis);
364			rawmode();
365		}
366	} else {
367		if (G.charmode != CHM_OFF) {
368			G.charmode = CHM_OFF;
369			printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis);
370			cookmode();
371		}
372	}
373}
374
375static void will_charmode(void)
376{
377	G.charmode = CHM_TRY;
378	G.telflags |= (UF_ECHO | UF_SGA);
379	setConMode();
380
381	putiac2(DO, TELOPT_ECHO);
382	putiac2(DO, TELOPT_SGA);
383	iacflush();
384}
385
386static void do_linemode(void)
387{
388	G.charmode = CHM_TRY;
389	G.telflags &= ~(UF_ECHO | UF_SGA);
390	setConMode();
391
392	putiac2(DONT, TELOPT_ECHO);
393	putiac2(DONT, TELOPT_SGA);
394	iacflush();
395}
396
397static void to_notsup(char c)
398{
399	if (G.telwish == WILL)
400		putiac2(DONT, c);
401	else if (G.telwish == DO)
402		putiac2(WONT, c);
403}
404
405static void to_echo(void)
406{
407	/* if server requests ECHO, don't agree */
408	if (G.telwish == DO) {
409		putiac2(WONT, TELOPT_ECHO);
410		return;
411	}
412	if (G.telwish == DONT)
413		return;
414
415	if (G.telflags & UF_ECHO) {
416		if (G.telwish == WILL)
417			return;
418	} else if (G.telwish == WONT)
419		return;
420
421	if (G.charmode != CHM_OFF)
422		G.telflags ^= UF_ECHO;
423
424	if (G.telflags & UF_ECHO)
425		putiac2(DO, TELOPT_ECHO);
426	else
427		putiac2(DONT, TELOPT_ECHO);
428
429	setConMode();
430	write_str(1, "\r\n");  /* sudden modec */
431}
432
433static void to_sga(void)
434{
435	/* daemon always sends will/wont, client do/dont */
436
437	if (G.telflags & UF_SGA) {
438		if (G.telwish == WILL)
439			return;
440	} else if (G.telwish == WONT)
441		return;
442
443	if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */
444		putiac2(DO, TELOPT_SGA);
445	else
446		putiac2(DONT, TELOPT_SGA);
447}
448
449#if ENABLE_FEATURE_TELNET_TTYPE
450static void to_ttype(void)
451{
452	/* Tell server we will (or won't) do TTYPE */
453
454	if (G.ttype)
455		putiac2(WILL, TELOPT_TTYPE);
456	else
457		putiac2(WONT, TELOPT_TTYPE);
458}
459#endif
460
461#if ENABLE_FEATURE_TELNET_AUTOLOGIN
462static void to_new_environ(void)
463{
464	/* Tell server we will (or will not) do AUTOLOGIN */
465
466	if (G.autologin)
467		putiac2(WILL, TELOPT_NEW_ENVIRON);
468	else
469		putiac2(WONT, TELOPT_NEW_ENVIRON);
470}
471#endif
472
473#if ENABLE_FEATURE_AUTOWIDTH
474static void to_naws(void)
475{
476	/* Tell server we will do NAWS */
477	putiac2(WILL, TELOPT_NAWS);
478}
479#endif
480
481static void telopt(byte c)
482{
483	switch (c) {
484	case TELOPT_ECHO:
485		to_echo(); break;
486	case TELOPT_SGA:
487		to_sga(); break;
488#if ENABLE_FEATURE_TELNET_TTYPE
489	case TELOPT_TTYPE:
490		to_ttype(); break;
491#endif
492#if ENABLE_FEATURE_TELNET_AUTOLOGIN
493	case TELOPT_NEW_ENVIRON:
494		to_new_environ(); break;
495#endif
496#if ENABLE_FEATURE_AUTOWIDTH
497	case TELOPT_NAWS:
498		to_naws();
499		putiac_naws(c, G.win_width, G.win_height);
500		break;
501#endif
502	default:
503		to_notsup(c);
504		break;
505	}
506}
507
508/* subnegotiation -- ignore all (except TTYPE,NAWS) */
509static int subneg(byte c)
510{
511	switch (G.telstate) {
512	case TS_SUB1:
513		if (c == IAC)
514			G.telstate = TS_SUB2;
515#if ENABLE_FEATURE_TELNET_TTYPE
516		else
517		if (c == TELOPT_TTYPE)
518			putiac_subopt(TELOPT_TTYPE, G.ttype);
519#endif
520#if ENABLE_FEATURE_TELNET_AUTOLOGIN
521		else
522		if (c == TELOPT_NEW_ENVIRON)
523			putiac_subopt_autologin();
524#endif
525		break;
526	case TS_SUB2:
527		if (c == SE)
528			return TRUE;
529		G.telstate = TS_SUB1;
530		/* break; */
531	}
532	return FALSE;
533}
534
535static void fgotsig(int sig)
536{
537	G.gotsig = sig;
538}
539
540
541static void rawmode(void)
542{
543	if (G.do_termios)
544		tcsetattr(0, TCSADRAIN, &G.termios_raw);
545}
546
547static void cookmode(void)
548{
549	if (G.do_termios)
550		tcsetattr(0, TCSADRAIN, &G.termios_def);
551}
552
553int telnet_main(int argc, char** argv);
554int telnet_main(int argc, char** argv)
555{
556	char *host;
557	int port;
558	int len;
559#ifdef USE_POLL
560	struct pollfd ufds[2];
561#else
562	fd_set readfds;
563	int maxfd;
564#endif
565
566	INIT_G();
567
568#if ENABLE_FEATURE_AUTOWIDTH
569	get_terminal_width_height(0, &G.win_width, &G.win_height);
570#endif
571
572#if ENABLE_FEATURE_TELNET_TTYPE
573	G.ttype = getenv("TERM");
574#endif
575
576	if (tcgetattr(0, &G.termios_def) >= 0) {
577		G.do_termios = 1;
578		G.termios_raw = G.termios_def;
579		cfmakeraw(&G.termios_raw);
580	}
581
582	if (argc < 2)
583		bb_show_usage();
584
585#if ENABLE_FEATURE_TELNET_AUTOLOGIN
586	if (1 & getopt32(argv, "al:", &G.autologin))
587		G.autologin = getenv("USER");
588	argv += optind;
589#else
590	argv++;
591#endif
592	if (!*argv)
593		bb_show_usage();
594	host = *argv++;
595	port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23);
596	if (*argv) /* extra params?? */
597		bb_show_usage();
598
599	G.netfd = create_and_connect_stream_or_die(host, port);
600
601	setsockopt(G.netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
602
603	signal(SIGINT, fgotsig);
604
605#ifdef USE_POLL
606	ufds[0].fd = 0; ufds[1].fd = G.netfd;
607	ufds[0].events = ufds[1].events = POLLIN;
608#else
609	FD_ZERO(&readfds);
610	FD_SET(0, &readfds);
611	FD_SET(G.netfd, &readfds);
612	maxfd = G.netfd + 1;
613#endif
614
615	while (1) {
616#ifndef USE_POLL
617		fd_set rfds = readfds;
618
619		switch (select(maxfd, &rfds, NULL, NULL, NULL))
620#else
621		switch (poll(ufds, 2, -1))
622#endif
623		{
624		case 0:
625			/* timeout */
626		case -1:
627			/* error, ignore and/or log something, bay go to loop */
628			if (G.gotsig)
629				conescape();
630			else
631				sleep(1);
632			break;
633		default:
634
635#ifdef USE_POLL
636			if (ufds[0].revents) /* well, should check POLLIN, but ... */
637#else
638			if (FD_ISSET(0, &rfds))
639#endif
640			{
641				len = read(0, G.buf, DATABUFSIZE);
642				if (len <= 0)
643					doexit(0);
644				TRACE(0, ("Read con: %d\n", len));
645				handlenetoutput(len);
646			}
647
648#ifdef USE_POLL
649			if (ufds[1].revents) /* well, should check POLLIN, but ... */
650#else
651			if (FD_ISSET(G.netfd, &rfds))
652#endif
653			{
654				len = read(G.netfd, G.buf, DATABUFSIZE);
655				if (len <= 0) {
656					write_str(1, "Connection closed by foreign host\r\n");
657					doexit(1);
658				}
659				TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len));
660				handlenetinput(len);
661			}
662		}
663	}
664}
665