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