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