OSDN Git Service

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