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