source: MondoRescue/branches/3.3/mindi-busybox/networking/telnet.c@ 3734

Last change on this file since 3734 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

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