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