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