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