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