source: MondoRescue/branches/3.2/mindi-busybox/networking/telnet.c@ 3232

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