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