1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72#include <arpa/telnet.h>
73#include <netinet/in.h>
74#include "libbb.h"
75#include "common_bufsiz.h"
76
77#ifdef __BIONIC__
78
79# define IAC 255
80# define DONT 254
81# define DO 253
82# define WONT 252
83# define WILL 251
84# define SB 250
85# define SE 240
86# define TELOPT_ECHO 1
87# define TELOPT_SGA 3
88# define TELOPT_TTYPE 24
89# define TELOPT_NAWS 31
90#endif
91
92enum {
93 DATABUFSIZE = 128,
94 IACBUFSIZE = 128,
95
96 CHM_TRY = 0,
97 CHM_ON = 1,
98 CHM_OFF = 2,
99
100 UF_ECHO = 0x01,
101 UF_SGA = 0x02,
102
103 TS_NORMAL = 0,
104 TS_COPY = 1,
105 TS_IAC = 2,
106 TS_OPT = 3,
107 TS_SUB1 = 4,
108 TS_SUB2 = 5,
109 TS_CR = 6,
110};
111
112typedef unsigned char byte;
113
114enum { netfd = 3 };
115
116struct globals {
117 int iaclen;
118 byte telstate;
119 byte telwish;
120 byte charmode;
121 byte telflags;
122 byte do_termios;
123#if ENABLE_FEATURE_TELNET_TTYPE
124 char *ttype;
125#endif
126#if ENABLE_FEATURE_TELNET_AUTOLOGIN
127 const char *autologin;
128#endif
129#if ENABLE_FEATURE_TELNET_WIDTH
130 unsigned win_width, win_height;
131#endif
132
133 char buf[DATABUFSIZE];
134
135 char iacbuf[IACBUFSIZE];
136 struct termios termios_def;
137 struct termios termios_raw;
138} FIX_ALIASING;
139#define G (*(struct globals*)bb_common_bufsiz1)
140#define INIT_G() do { \
141 setup_common_bufsiz(); \
142 BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \
143} while (0)
144
145
146static void rawmode(void);
147static void cookmode(void);
148static void do_linemode(void);
149static void will_charmode(void);
150static void telopt(byte c);
151static void subneg(byte c);
152
153static void iac_flush(void)
154{
155 if (G.iaclen != 0) {
156 full_write(netfd, G.iacbuf, G.iaclen);
157 G.iaclen = 0;
158 }
159}
160
161static void doexit(int ev) NORETURN;
162static void doexit(int ev)
163{
164 cookmode();
165 exit(ev);
166}
167
168static void con_escape(void)
169{
170 char b;
171
172 if (bb_got_signal)
173 rawmode();
174
175 full_write1_str("\r\nConsole escape. Commands are:\r\n\n"
176 " l go to line mode\r\n"
177 " c go to character mode\r\n"
178 " z suspend telnet\r\n"
179 " e exit telnet\r\n");
180
181 if (read(STDIN_FILENO, &b, 1) <= 0)
182 doexit(EXIT_FAILURE);
183
184 switch (b) {
185 case 'l':
186 if (!bb_got_signal) {
187 do_linemode();
188 goto ret;
189 }
190 break;
191 case 'c':
192 if (bb_got_signal) {
193 will_charmode();
194 goto ret;
195 }
196 break;
197 case 'z':
198 cookmode();
199 kill(0, SIGTSTP);
200 rawmode();
201 break;
202 case 'e':
203 doexit(EXIT_SUCCESS);
204 }
205
206 full_write1_str("continuing...\r\n");
207
208 if (bb_got_signal)
209 cookmode();
210 ret:
211 bb_got_signal = 0;
212}
213
214static void handle_net_output(int len)
215{
216 byte outbuf[2 * DATABUFSIZE];
217 byte *dst = outbuf;
218 byte *src = (byte*)G.buf;
219 byte *end = src + len;
220
221 while (src < end) {
222 byte c = *src++;
223 if (c == 0x1d) {
224 con_escape();
225 return;
226 }
227 *dst = c;
228 if (c == IAC)
229 *++dst = c;
230 else
231 if (c == '\r' || c == '\n') {
232
233
234
235
236
237
238 *dst = '\r';
239 *++dst = '\n';
240 }
241 dst++;
242 }
243 if (dst - outbuf != 0)
244 full_write(netfd, outbuf, dst - outbuf);
245}
246
247static void handle_net_input(int len)
248{
249 byte c;
250 int i;
251 int cstart = cstart;
252
253 i = 0;
254
255 if (G.telstate == TS_NORMAL) {
256 while (i < len) {
257 c = G.buf[i];
258 i++;
259 if (c == IAC)
260 goto got_IAC;
261 if (c != '\r')
262 continue;
263 G.telstate = TS_CR;
264 cstart = i;
265 goto got_special;
266 }
267 full_write(STDOUT_FILENO, G.buf, len);
268 return;
269 got_IAC:
270 G.telstate = TS_IAC;
271 cstart = i - 1;
272 got_special: ;
273 }
274
275 for (; i < len; i++) {
276 c = G.buf[i];
277
278 switch (G.telstate) {
279 case TS_CR:
280
281
282
283 G.telstate = TS_COPY;
284 if (c == '\0')
285 break;
286
287
288 case TS_COPY:
289
290 if (c == IAC)
291 G.telstate = TS_IAC;
292 else {
293 G.buf[cstart++] = c;
294 if (c == '\r')
295 G.telstate = TS_CR;
296 }
297 break;
298
299 case TS_IAC:
300 switch (c) {
301 case IAC:
302 G.buf[cstart++] = c;
303 G.telstate = TS_COPY;
304 break;
305 case SB:
306 G.telstate = TS_SUB1;
307 break;
308 case DO:
309 case DONT:
310 case WILL:
311 case WONT:
312 G.telwish = c;
313 G.telstate = TS_OPT;
314 break;
315
316 default:
317 G.telstate = TS_COPY;
318 }
319 break;
320
321 case TS_OPT:
322 telopt(c);
323 G.telstate = TS_COPY;
324 break;
325
326 case TS_SUB1:
327 case TS_SUB2:
328 subneg(c);
329 break;
330 }
331 }
332
333
334 iac_flush();
335 if (G.telstate == TS_COPY)
336 G.telstate = TS_NORMAL;
337 if (cstart != 0)
338 full_write(STDOUT_FILENO, G.buf, cstart);
339}
340
341static void put_iac(int c)
342{
343 int iaclen = G.iaclen;
344 if (iaclen >= IACBUFSIZE) {
345 iac_flush();
346 iaclen = 0;
347 }
348 G.iacbuf[iaclen] = c;
349 G.iaclen = iaclen + 1;
350}
351
352static void put_iac2_msb_lsb(unsigned x_y)
353{
354 put_iac(x_y >> 8);
355 put_iac(x_y);
356}
357#define put_iac2_x_y(x,y) put_iac2_msb_lsb(((x)<<8) + (y))
358
359static void put_iac4_msb_lsb(unsigned x_y_z_t)
360{
361 put_iac2_msb_lsb(x_y_z_t >> 16);
362 put_iac2_msb_lsb(x_y_z_t);
363}
364#define put_iac4_x_y_z_t(x,y,z,t) put_iac4_msb_lsb(((x)<<24) + ((y)<<16) + ((z)<<8) + (t))
365
366static void put_iac3_IAC_x_y_merged(unsigned wwdd_and_c)
367{
368 put_iac(IAC);
369 put_iac2_msb_lsb(wwdd_and_c);
370}
371#define put_iac3_IAC_x_y(wwdd,c) put_iac3_IAC_x_y_merged(((wwdd)<<8) + (c))
372
373#if ENABLE_FEATURE_TELNET_TTYPE
374static void put_iac_subopt(byte c, char *str)
375{
376 put_iac4_x_y_z_t(IAC, SB, c, 0);
377
378 while (*str)
379 put_iac(*str++);
380
381 put_iac2_x_y(IAC, SE);
382}
383#endif
384
385#if ENABLE_FEATURE_TELNET_AUTOLOGIN
386static void put_iac_subopt_autologin(void)
387{
388 const char *p;
389
390 put_iac4_x_y_z_t(IAC, SB, TELOPT_NEW_ENVIRON, TELQUAL_IS);
391 put_iac4_x_y_z_t(NEW_ENV_VAR, 'U', 'S', 'E');
392 put_iac2_x_y('R', NEW_ENV_VALUE);
393
394 p = G.autologin;
395 while (*p)
396 put_iac(*p++);
397
398 put_iac2_x_y(IAC, SE);
399}
400#endif
401
402#if ENABLE_FEATURE_TELNET_WIDTH
403static void put_iac_naws(byte c, int x, int y)
404{
405 put_iac3_IAC_x_y(SB, c);
406
407 put_iac4_msb_lsb((x << 16) + y);
408
409 put_iac2_x_y(IAC, SE);
410}
411#endif
412
413static void setConMode(void)
414{
415 if (G.telflags & UF_ECHO) {
416 if (G.charmode == CHM_TRY) {
417 G.charmode = CHM_ON;
418 printf("\r\nEntering %s mode"
419 "\r\nEscape character is '^%c'.\r\n", "character", ']');
420 rawmode();
421 }
422 } else {
423 if (G.charmode != CHM_OFF) {
424 G.charmode = CHM_OFF;
425 printf("\r\nEntering %s mode"
426 "\r\nEscape character is '^%c'.\r\n", "line", 'C');
427 cookmode();
428 }
429 }
430}
431
432static void will_charmode(void)
433{
434 G.charmode = CHM_TRY;
435 G.telflags |= (UF_ECHO | UF_SGA);
436 setConMode();
437
438 put_iac3_IAC_x_y(DO, TELOPT_ECHO);
439 put_iac3_IAC_x_y(DO, TELOPT_SGA);
440 iac_flush();
441}
442
443static void do_linemode(void)
444{
445 G.charmode = CHM_TRY;
446 G.telflags &= ~(UF_ECHO | UF_SGA);
447 setConMode();
448
449 put_iac3_IAC_x_y(DONT, TELOPT_ECHO);
450 put_iac3_IAC_x_y(DONT, TELOPT_SGA);
451 iac_flush();
452}
453
454static void to_notsup(char c)
455{
456 if (G.telwish == WILL)
457 put_iac3_IAC_x_y(DONT, c);
458 else if (G.telwish == DO)
459 put_iac3_IAC_x_y(WONT, c);
460}
461
462static void to_echo(void)
463{
464
465 if (G.telwish == DO) {
466 put_iac3_IAC_x_y(WONT, TELOPT_ECHO);
467 return;
468 }
469 if (G.telwish == DONT)
470 return;
471
472 if (G.telflags & UF_ECHO) {
473 if (G.telwish == WILL)
474 return;
475 } else if (G.telwish == WONT)
476 return;
477
478 if (G.charmode != CHM_OFF)
479 G.telflags ^= UF_ECHO;
480
481 if (G.telflags & UF_ECHO)
482 put_iac3_IAC_x_y(DO, TELOPT_ECHO);
483 else
484 put_iac3_IAC_x_y(DONT, TELOPT_ECHO);
485
486 setConMode();
487 full_write1_str("\r\n");
488}
489
490static void to_sga(void)
491{
492
493
494 if (G.telflags & UF_SGA) {
495 if (G.telwish == WILL)
496 return;
497 } else if (G.telwish == WONT)
498 return;
499
500 G.telflags ^= UF_SGA;
501 if (G.telflags & UF_SGA)
502 put_iac3_IAC_x_y(DO, TELOPT_SGA);
503 else
504 put_iac3_IAC_x_y(DONT, TELOPT_SGA);
505}
506
507#if ENABLE_FEATURE_TELNET_TTYPE
508static void to_ttype(void)
509{
510
511 if (G.ttype)
512 put_iac3_IAC_x_y(WILL, TELOPT_TTYPE);
513 else
514 put_iac3_IAC_x_y(WONT, TELOPT_TTYPE);
515}
516#endif
517
518#if ENABLE_FEATURE_TELNET_AUTOLOGIN
519static void to_new_environ(void)
520{
521
522 if (G.autologin)
523 put_iac3_IAC_x_y(WILL, TELOPT_NEW_ENVIRON);
524 else
525 put_iac3_IAC_x_y(WONT, TELOPT_NEW_ENVIRON);
526}
527#endif
528
529#if ENABLE_FEATURE_TELNET_WIDTH
530static void to_naws(void)
531{
532
533 put_iac3_IAC_x_y(WILL, TELOPT_NAWS);
534}
535#endif
536
537static void telopt(byte c)
538{
539 switch (c) {
540 case TELOPT_ECHO:
541 to_echo(); break;
542 case TELOPT_SGA:
543 to_sga(); break;
544#if ENABLE_FEATURE_TELNET_TTYPE
545 case TELOPT_TTYPE:
546 to_ttype(); break;
547#endif
548#if ENABLE_FEATURE_TELNET_AUTOLOGIN
549 case TELOPT_NEW_ENVIRON:
550 to_new_environ(); break;
551#endif
552#if ENABLE_FEATURE_TELNET_WIDTH
553 case TELOPT_NAWS:
554 to_naws();
555 put_iac_naws(c, G.win_width, G.win_height);
556 break;
557#endif
558 default:
559 to_notsup(c);
560 break;
561 }
562}
563
564
565static void subneg(byte c)
566{
567 switch (G.telstate) {
568 case TS_SUB1:
569 if (c == IAC)
570 G.telstate = TS_SUB2;
571#if ENABLE_FEATURE_TELNET_TTYPE
572 else
573 if (c == TELOPT_TTYPE && G.ttype)
574 put_iac_subopt(TELOPT_TTYPE, G.ttype);
575#endif
576#if ENABLE_FEATURE_TELNET_AUTOLOGIN
577 else
578 if (c == TELOPT_NEW_ENVIRON && G.autologin)
579 put_iac_subopt_autologin();
580#endif
581 break;
582 case TS_SUB2:
583 if (c == SE) {
584 G.telstate = TS_COPY;
585 return;
586 }
587 G.telstate = TS_SUB1;
588 break;
589 }
590}
591
592static void rawmode(void)
593{
594 if (G.do_termios)
595 tcsetattr(0, TCSADRAIN, &G.termios_raw);
596}
597
598static void cookmode(void)
599{
600 if (G.do_termios)
601 tcsetattr(0, TCSADRAIN, &G.termios_def);
602}
603
604int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
605int telnet_main(int argc UNUSED_PARAM, char **argv)
606{
607 char *host;
608 int port;
609 int len;
610 struct pollfd ufds[2];
611
612 INIT_G();
613
614#if ENABLE_FEATURE_TELNET_TTYPE
615 G.ttype = getenv("TERM");
616#endif
617
618 if (tcgetattr(0, &G.termios_def) >= 0) {
619 G.do_termios = 1;
620 G.termios_raw = G.termios_def;
621 cfmakeraw(&G.termios_raw);
622 }
623
624#if ENABLE_FEATURE_TELNET_AUTOLOGIN
625 if (1 == getopt32(argv, "al:", &G.autologin)) {
626
627 G.autologin = getenv("USER");
628 }
629 argv += optind;
630#else
631 argv++;
632#endif
633 if (!*argv)
634 bb_show_usage();
635 host = *argv++;
636 port = *argv ? bb_lookup_port(*argv++, "tcp", 23)
637 : bb_lookup_std_port("telnet", "tcp", 23);
638 if (*argv)
639 bb_show_usage();
640
641 xmove_fd(create_and_connect_stream_or_die(host, port), netfd);
642 printf("Connected to %s\n", host);
643
644 setsockopt_keepalive(netfd);
645
646#if ENABLE_FEATURE_TELNET_WIDTH
647 get_terminal_width_height(0, &G.win_width, &G.win_height);
648
649#endif
650
651 signal(SIGINT, record_signo);
652
653 ufds[0].fd = STDIN_FILENO;
654 ufds[0].events = POLLIN;
655 ufds[1].fd = netfd;
656 ufds[1].events = POLLIN;
657
658 while (1) {
659 if (poll(ufds, 2, -1) < 0) {
660
661 if (bb_got_signal)
662 con_escape();
663 else
664 sleep(1);
665 continue;
666 }
667
668
669
670 if (ufds[0].revents) {
671 len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE);
672 if (len <= 0)
673 doexit(EXIT_SUCCESS);
674 handle_net_output(len);
675 }
676
677 if (ufds[1].revents) {
678 len = safe_read(netfd, G.buf, DATABUFSIZE);
679 if (len <= 0) {
680 full_write1_str("Connection closed by foreign host\r\n");
681 doexit(EXIT_FAILURE);
682 }
683 handle_net_input(len);
684 }
685 }
686}
687