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