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