OSDN Git Service

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