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