blob: a255797737ef7aaafb041525c5a141d73764bbb6
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 | |
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 | |
58 | #ifdef DOTRACE |
59 | # define TRACE(x, y) do { if (x) printf y; } while (0) |
60 | #else |
61 | # define TRACE(x, y) |
62 | #endif |
63 | |
64 | enum { |
65 | DATABUFSIZE = 128, |
66 | IACBUFSIZE = 128, |
67 | |
68 | CHM_TRY = 0, |
69 | CHM_ON = 1, |
70 | CHM_OFF = 2, |
71 | |
72 | UF_ECHO = 0x01, |
73 | UF_SGA = 0x02, |
74 | |
75 | TS_NORMAL = 0, |
76 | TS_COPY = 1, |
77 | TS_IAC = 2, |
78 | TS_OPT = 3, |
79 | TS_SUB1 = 4, |
80 | TS_SUB2 = 5, |
81 | TS_CR = 6, |
82 | }; |
83 | |
84 | typedef unsigned char byte; |
85 | |
86 | enum { netfd = 3 }; |
87 | |
88 | struct globals { |
89 | int iaclen; /* could even use byte, but it's a loss on x86 */ |
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; |
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 |
102 | unsigned win_width, win_height; |
103 | #endif |
104 | /* same buffer used both for network and console read/write */ |
105 | char buf[DATABUFSIZE]; |
106 | /* buffer to handle telnet negotiations */ |
107 | char iacbuf[IACBUFSIZE]; |
108 | struct termios termios_def; |
109 | struct termios termios_raw; |
110 | } FIX_ALIASING; |
111 | #define G (*(struct globals*)&bb_common_bufsiz1) |
112 | #define INIT_G() do { \ |
113 | struct G_sizecheck { \ |
114 | char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \ |
115 | }; \ |
116 | } while (0) |
117 | |
118 | |
119 | static void rawmode(void); |
120 | static void cookmode(void); |
121 | static void do_linemode(void); |
122 | static void will_charmode(void); |
123 | static void telopt(byte c); |
124 | static void subneg(byte c); |
125 | |
126 | static void iac_flush(void) |
127 | { |
128 | full_write(netfd, G.iacbuf, G.iaclen); |
129 | G.iaclen = 0; |
130 | } |
131 | |
132 | static void doexit(int ev) NORETURN; |
133 | static void doexit(int ev) |
134 | { |
135 | cookmode(); |
136 | exit(ev); |
137 | } |
138 | |
139 | static 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 | |
185 | static 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 | |
218 | static 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 | |
309 | static void put_iac(int c) |
310 | { |
311 | G.iacbuf[G.iaclen++] = c; |
312 | } |
313 | |
314 | static 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 |
325 | static 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 |
346 | static 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 |
375 | static 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 | |
395 | static 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 | |
414 | static 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 | |
425 | static 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 | |
436 | static 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 | |
444 | static 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 | |
472 | static 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 |
490 | static 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 |
501 | static 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 |
512 | static void to_naws(void) |
513 | { |
514 | /* Tell server we will do NAWS */ |
515 | put_iac2(WILL, TELOPT_NAWS); |
516 | } |
517 | #endif |
518 | |
519 | static 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) */ |
547 | static 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 | |
574 | static void rawmode(void) |
575 | { |
576 | if (G.do_termios) |
577 | tcsetattr(0, TCSADRAIN, &G.termios_raw); |
578 | } |
579 | |
580 | static void cookmode(void) |
581 | { |
582 | if (G.do_termios) |
583 | tcsetattr(0, TCSADRAIN, &G.termios_def); |
584 | } |
585 | |
586 | int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
587 | int 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(netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); |
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 | } |
666 |