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