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