OSDN Git Service

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