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 full_write(netfd, G.iacbuf, G.iaclen);
156 G.iaclen = 0;
157}
158
159static void doexit(int ev) NORETURN;
160static void doexit(int ev)
161{
162 cookmode();
163 exit(ev);
164}
165
166static void con_escape(void)
167{
168 char b;
169
170 if (bb_got_signal)
171 rawmode();
172
173 full_write1_str("\r\nConsole escape. Commands are:\r\n\n"
174 " l go to line mode\r\n"
175 " c go to character mode\r\n"
176 " z suspend telnet\r\n"
177 " e exit telnet\r\n");
178
179 if (read(STDIN_FILENO, &b, 1) <= 0)
180 doexit(EXIT_FAILURE);
181
182 switch (b) {
183 case 'l':
184 if (!bb_got_signal) {
185 do_linemode();
186 goto ret;
187 }
188 break;
189 case 'c':
190 if (bb_got_signal) {
191 will_charmode();
192 goto ret;
193 }
194 break;
195 case 'z':
196 cookmode();
197 kill(0, SIGTSTP);
198 rawmode();
199 break;
200 case 'e':
201 doexit(EXIT_SUCCESS);
202 }
203
204 full_write1_str("continuing...\r\n");
205
206 if (bb_got_signal)
207 cookmode();
208 ret:
209 bb_got_signal = 0;
210}
211
212static void handle_net_output(int len)
213{
214 byte outbuf[2 * DATABUFSIZE];
215 byte *dst = outbuf;
216 byte *src = (byte*)G.buf;
217 byte *end = src + len;
218
219 while (src < end) {
220 byte c = *src++;
221 if (c == 0x1d) {
222 con_escape();
223 return;
224 }
225 *dst = c;
226 if (c == IAC)
227 *++dst = c;
228 else
229 if (c == '\r' || c == '\n') {
230
231
232
233
234
235
236 *dst = '\r';
237 *++dst = '\n';
238 }
239 dst++;
240 }
241 if (dst - outbuf != 0)
242 full_write(netfd, outbuf, dst - outbuf);
243}
244
245static void handle_net_input(int len)
246{
247 int i;
248 int cstart = 0;
249
250 for (i = 0; i < len; i++) {
251 byte c = G.buf[i];
252
253 if (G.telstate == TS_NORMAL) {
254 if (c == IAC) {
255 cstart = i;
256 G.telstate = TS_IAC;
257 }
258 else if (c == '\r') {
259 cstart = i + 1;
260 G.telstate = TS_CR;
261 }
262
263
264 continue;
265 }
266
267 switch (G.telstate) {
268 case TS_CR:
269
270
271
272 G.telstate = TS_COPY;
273 if (c == '\0')
274 break;
275
276
277 case TS_COPY:
278
279 if (c == IAC)
280 G.telstate = TS_IAC;
281 else
282 G.buf[cstart++] = c;
283 if (c == '\r')
284 G.telstate = TS_CR;
285 break;
286
287 case TS_IAC:
288 if (c == IAC) {
289 G.buf[cstart++] = c;
290 G.telstate = TS_COPY;
291 break;
292 }
293
294 switch (c) {
295 case SB:
296 G.telstate = TS_SUB1;
297 break;
298 case DO:
299 case DONT:
300 case WILL:
301 case WONT:
302 G.telwish = c;
303 G.telstate = TS_OPT;
304 break;
305
306 default:
307 G.telstate = TS_COPY;
308 }
309 break;
310
311 case TS_OPT:
312 telopt(c);
313 G.telstate = TS_COPY;
314 break;
315
316 case TS_SUB1:
317 case TS_SUB2:
318 subneg(c);
319 break;
320 }
321 }
322
323 if (G.telstate != TS_NORMAL) {
324
325 if (G.iaclen)
326 iac_flush();
327 if (G.telstate == TS_COPY)
328 G.telstate = TS_NORMAL;
329 len = cstart;
330 }
331
332 if (len)
333 full_write(STDOUT_FILENO, G.buf, len);
334}
335
336static void put_iac(int c)
337{
338 G.iacbuf[G.iaclen++] = c;
339}
340
341static void put_iac2_merged(unsigned wwdd_and_c)
342{
343 if (G.iaclen + 3 > IACBUFSIZE)
344 iac_flush();
345
346 put_iac(IAC);
347 put_iac(wwdd_and_c >> 8);
348 put_iac(wwdd_and_c & 0xff);
349}
350#define put_iac2(wwdd,c) put_iac2_merged(((wwdd)<<8) + (c))
351
352#if ENABLE_FEATURE_TELNET_TTYPE
353static void put_iac_subopt(byte c, char *str)
354{
355 int len = strlen(str) + 6;
356
357 if (G.iaclen + len > IACBUFSIZE)
358 iac_flush();
359
360 put_iac(IAC);
361 put_iac(SB);
362 put_iac(c);
363 put_iac(0);
364
365 while (*str)
366 put_iac(*str++);
367
368 put_iac(IAC);
369 put_iac(SE);
370}
371#endif
372
373#if ENABLE_FEATURE_TELNET_AUTOLOGIN
374static void put_iac_subopt_autologin(void)
375{
376 int len = strlen(G.autologin) + 6;
377 const char *p = "USER";
378
379 if (G.iaclen + len > IACBUFSIZE)
380 iac_flush();
381
382 put_iac(IAC);
383 put_iac(SB);
384 put_iac(TELOPT_NEW_ENVIRON);
385 put_iac(TELQUAL_IS);
386 put_iac(NEW_ENV_VAR);
387
388 while (*p)
389 put_iac(*p++);
390
391 put_iac(NEW_ENV_VALUE);
392
393 p = G.autologin;
394 while (*p)
395 put_iac(*p++);
396
397 put_iac(IAC);
398 put_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 if (G.iaclen + 9 > IACBUFSIZE)
406 iac_flush();
407
408 put_iac(IAC);
409 put_iac(SB);
410 put_iac(c);
411
412
413 put_iac(x >> 8);
414 put_iac(x);
415 put_iac(y >> 8);
416 put_iac(y);
417
418 put_iac(IAC);
419 put_iac(SE);
420}
421#endif
422
423static void setConMode(void)
424{
425 if (G.telflags & UF_ECHO) {
426 if (G.charmode == CHM_TRY) {
427 G.charmode = CHM_ON;
428 printf("\r\nEntering %s mode"
429 "\r\nEscape character is '^%c'.\r\n", "character", ']');
430 rawmode();
431 }
432 } else {
433 if (G.charmode != CHM_OFF) {
434 G.charmode = CHM_OFF;
435 printf("\r\nEntering %s mode"
436 "\r\nEscape character is '^%c'.\r\n", "line", 'C');
437 cookmode();
438 }
439 }
440}
441
442static void will_charmode(void)
443{
444 G.charmode = CHM_TRY;
445 G.telflags |= (UF_ECHO | UF_SGA);
446 setConMode();
447
448 put_iac2(DO, TELOPT_ECHO);
449 put_iac2(DO, TELOPT_SGA);
450 iac_flush();
451}
452
453static void do_linemode(void)
454{
455 G.charmode = CHM_TRY;
456 G.telflags &= ~(UF_ECHO | UF_SGA);
457 setConMode();
458
459 put_iac2(DONT, TELOPT_ECHO);
460 put_iac2(DONT, TELOPT_SGA);
461 iac_flush();
462}
463
464static void to_notsup(char c)
465{
466 if (G.telwish == WILL)
467 put_iac2(DONT, c);
468 else if (G.telwish == DO)
469 put_iac2(WONT, c);
470}
471
472static void to_echo(void)
473{
474
475 if (G.telwish == DO) {
476 put_iac2(WONT, TELOPT_ECHO);
477 return;
478 }
479 if (G.telwish == DONT)
480 return;
481
482 if (G.telflags & UF_ECHO) {
483 if (G.telwish == WILL)
484 return;
485 } else if (G.telwish == WONT)
486 return;
487
488 if (G.charmode != CHM_OFF)
489 G.telflags ^= UF_ECHO;
490
491 if (G.telflags & UF_ECHO)
492 put_iac2(DO, TELOPT_ECHO);
493 else
494 put_iac2(DONT, TELOPT_ECHO);
495
496 setConMode();
497 full_write1_str("\r\n");
498}
499
500static void to_sga(void)
501{
502
503
504 if (G.telflags & UF_SGA) {
505 if (G.telwish == WILL)
506 return;
507 } else if (G.telwish == WONT)
508 return;
509
510 G.telflags ^= UF_SGA;
511 if (G.telflags & UF_SGA)
512 put_iac2(DO, TELOPT_SGA);
513 else
514 put_iac2(DONT, TELOPT_SGA);
515}
516
517#if ENABLE_FEATURE_TELNET_TTYPE
518static void to_ttype(void)
519{
520
521 if (G.ttype)
522 put_iac2(WILL, TELOPT_TTYPE);
523 else
524 put_iac2(WONT, TELOPT_TTYPE);
525}
526#endif
527
528#if ENABLE_FEATURE_TELNET_AUTOLOGIN
529static void to_new_environ(void)
530{
531
532 if (G.autologin)
533 put_iac2(WILL, TELOPT_NEW_ENVIRON);
534 else
535 put_iac2(WONT, TELOPT_NEW_ENVIRON);
536}
537#endif
538
539#if ENABLE_FEATURE_TELNET_WIDTH
540static void to_naws(void)
541{
542
543 put_iac2(WILL, TELOPT_NAWS);
544}
545#endif
546
547static void telopt(byte c)
548{
549 switch (c) {
550 case TELOPT_ECHO:
551 to_echo(); break;
552 case TELOPT_SGA:
553 to_sga(); break;
554#if ENABLE_FEATURE_TELNET_TTYPE
555 case TELOPT_TTYPE:
556 to_ttype(); break;
557#endif
558#if ENABLE_FEATURE_TELNET_AUTOLOGIN
559 case TELOPT_NEW_ENVIRON:
560 to_new_environ(); break;
561#endif
562#if ENABLE_FEATURE_TELNET_WIDTH
563 case TELOPT_NAWS:
564 to_naws();
565 put_iac_naws(c, G.win_width, G.win_height);
566 break;
567#endif
568 default:
569 to_notsup(c);
570 break;
571 }
572}
573
574
575static void subneg(byte c)
576{
577 switch (G.telstate) {
578 case TS_SUB1:
579 if (c == IAC)
580 G.telstate = TS_SUB2;
581#if ENABLE_FEATURE_TELNET_TTYPE
582 else
583 if (c == TELOPT_TTYPE && G.ttype)
584 put_iac_subopt(TELOPT_TTYPE, G.ttype);
585#endif
586#if ENABLE_FEATURE_TELNET_AUTOLOGIN
587 else
588 if (c == TELOPT_NEW_ENVIRON && G.autologin)
589 put_iac_subopt_autologin();
590#endif
591 break;
592 case TS_SUB2:
593 if (c == SE) {
594 G.telstate = TS_COPY;
595 return;
596 }
597 G.telstate = TS_SUB1;
598 break;
599 }
600}
601
602static void rawmode(void)
603{
604 if (G.do_termios)
605 tcsetattr(0, TCSADRAIN, &G.termios_raw);
606}
607
608static void cookmode(void)
609{
610 if (G.do_termios)
611 tcsetattr(0, TCSADRAIN, &G.termios_def);
612}
613
614int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
615int telnet_main(int argc UNUSED_PARAM, char **argv)
616{
617 char *host;
618 int port;
619 int len;
620 struct pollfd ufds[2];
621
622 INIT_G();
623
624#if ENABLE_FEATURE_TELNET_TTYPE
625 G.ttype = getenv("TERM");
626#endif
627
628 if (tcgetattr(0, &G.termios_def) >= 0) {
629 G.do_termios = 1;
630 G.termios_raw = G.termios_def;
631 cfmakeraw(&G.termios_raw);
632 }
633
634#if ENABLE_FEATURE_TELNET_AUTOLOGIN
635 if (1 == getopt32(argv, "al:", &G.autologin)) {
636
637 G.autologin = getenv("USER");
638 }
639 argv += optind;
640#else
641 argv++;
642#endif
643 if (!*argv)
644 bb_show_usage();
645 host = *argv++;
646 port = *argv ? bb_lookup_port(*argv++, "tcp", 23)
647 : bb_lookup_std_port("telnet", "tcp", 23);
648 if (*argv)
649 bb_show_usage();
650
651 xmove_fd(create_and_connect_stream_or_die(host, port), netfd);
652
653 setsockopt_keepalive(netfd);
654
655#if ENABLE_FEATURE_TELNET_WIDTH
656 get_terminal_width_height(0, &G.win_width, &G.win_height);
657
658#endif
659
660 signal(SIGINT, record_signo);
661
662 ufds[0].fd = STDIN_FILENO;
663 ufds[0].events = POLLIN;
664 ufds[1].fd = netfd;
665 ufds[1].events = POLLIN;
666
667 while (1) {
668 if (poll(ufds, 2, -1) < 0) {
669
670 if (bb_got_signal)
671 con_escape();
672 else
673 sleep(1);
674 continue;
675 }
676
677
678
679 if (ufds[0].revents) {
680 len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE);
681 if (len <= 0)
682 doexit(EXIT_SUCCESS);
683 handle_net_output(len);
684 }
685
686 if (ufds[1].revents) {
687 len = safe_read(netfd, G.buf, DATABUFSIZE);
688 if (len <= 0) {
689 full_write1_str("Connection closed by foreign host\r\n");
690 doexit(EXIT_FAILURE);
691 }
692 handle_net_input(len);
693 }
694 }
695}
696