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