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 the GPL v2 or later, see the file LICENSE in this tarball. 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#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; /* could even use byte, but it's a loss on x86 */ 58 byte telstate; /* telnet negotiation state from network input */ 59 byte telwish; /* DO, DONT, WILL, WONT */ 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 /* same buffer used both for network and console read/write */ 73 char buf[DATABUFSIZE]; 74 /* buffer to handle telnet negotiations */ 75 char iacbuf[IACBUFSIZE]; 76 struct termios termios_def; 77 struct termios termios_raw; 78} FIX_ALIASING; 79#define G (*(struct globals*)&bb_common_bufsiz1) 80#define INIT_G() do { \ 81 struct G_sizecheck { \ 82 char G_sizecheck[sizeof(G) > COMMON_BUFSIZE ? -1 : 1]; \ 83 }; \ 84} while (0) 85 86/* Function prototypes */ 87static void rawmode(void); 88static void cookmode(void); 89static void do_linemode(void); 90static void will_charmode(void); 91static void telopt(byte c); 92static int subneg(byte c); 93 94static void iac_flush(void) 95{ 96 write(netfd, G.iacbuf, G.iaclen); 97 G.iaclen = 0; 98} 99 100#define write_str(fd, str) write(fd, str, sizeof(str) - 1) 101 102static void doexit(int ev) NORETURN; 103static void doexit(int ev) 104{ 105 cookmode(); 106 exit(ev); 107} 108 109static void con_escape(void) 110{ 111 char b; 112 113 if (bb_got_signal) /* came from line mode... go raw */ 114 rawmode(); 115 116 write_str(1, "\r\nConsole escape. Commands are:\r\n\n" 117 " l go to line mode\r\n" 118 " c go to character mode\r\n" 119 " z suspend telnet\r\n" 120 " e exit telnet\r\n"); 121 122 if (read(STDIN_FILENO, &b, 1) <= 0) 123 doexit(EXIT_FAILURE); 124 125 switch (b) { 126 case 'l': 127 if (!bb_got_signal) { 128 do_linemode(); 129 goto ret; 130 } 131 break; 132 case 'c': 133 if (bb_got_signal) { 134 will_charmode(); 135 goto ret; 136 } 137 break; 138 case 'z': 139 cookmode(); 140 kill(0, SIGTSTP); 141 rawmode(); 142 break; 143 case 'e': 144 doexit(EXIT_SUCCESS); 145 } 146 147 write_str(1, "continuing...\r\n"); 148 149 if (bb_got_signal) 150 cookmode(); 151 ret: 152 bb_got_signal = 0; 153 154} 155 156static void handle_net_output(int len) 157{ 158 /* here we could do smart tricks how to handle 0xFF:s in output 159 * stream like writing twice every sequence of FF:s (thus doing 160 * many write()s. But I think interactive telnet application does 161 * not need to be 100% 8-bit clean, so changing every 0xff:s to 162 * 0x7f:s 163 * 164 * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com) 165 * I don't agree. 166 * first - I cannot use programs like sz/rz 167 * second - the 0x0D is sent as one character and if the next 168 * char is 0x0A then it's eaten by a server side. 169 * third - why do you have to make 'many write()s'? 170 * I don't understand. 171 * So I implemented it. It's really useful for me. I hope that 172 * other people will find it interesting too. 173 */ 174 175 int i, j; 176 byte *p = (byte*)G.buf; 177 byte outbuf[4*DATABUFSIZE]; 178 179 for (i = len, j = 0; i > 0; i--, p++) { 180 if (*p == 0x1d) { 181 con_escape(); 182 return; 183 } 184 outbuf[j++] = *p; 185 if (*p == 0xff) 186 outbuf[j++] = 0xff; 187 else if (*p == 0x0d) 188 outbuf[j++] = 0x00; 189 } 190 if (j > 0) 191 write(netfd, outbuf, j); 192} 193 194static void handle_net_input(int len) 195{ 196 int i; 197 int cstart = 0; 198 199 for (i = 0; i < len; i++) { 200 byte c = G.buf[i]; 201 202 if (G.telstate == 0) { /* most of the time state == 0 */ 203 if (c == IAC) { 204 cstart = i; 205 G.telstate = TS_IAC; 206 } 207 continue; 208 } 209 switch (G.telstate) { 210 case TS_0: 211 if (c == IAC) 212 G.telstate = TS_IAC; 213 else 214 G.buf[cstart++] = c; 215 break; 216 217 case TS_IAC: 218 if (c == IAC) { /* IAC IAC -> 0xFF */ 219 G.buf[cstart++] = c; 220 G.telstate = TS_0; 221 break; 222 } 223 /* else */ 224 switch (c) { 225 case SB: 226 G.telstate = TS_SUB1; 227 break; 228 case DO: 229 case DONT: 230 case WILL: 231 case WONT: 232 G.telwish = c; 233 G.telstate = TS_OPT; 234 break; 235 default: 236 G.telstate = TS_0; /* DATA MARK must be added later */ 237 } 238 break; 239 case TS_OPT: /* WILL, WONT, DO, DONT */ 240 telopt(c); 241 G.telstate = TS_0; 242 break; 243 case TS_SUB1: /* Subnegotiation */ 244 case TS_SUB2: /* Subnegotiation */ 245 if (subneg(c)) 246 G.telstate = TS_0; 247 break; 248 } 249 } 250 if (G.telstate) { 251 if (G.iaclen) 252 iac_flush(); 253 if (G.telstate == TS_0) 254 G.telstate = 0; 255 len = cstart; 256 } 257 258 if (len) 259 write(STDOUT_FILENO, G.buf, len); 260} 261 262static void put_iac(int c) 263{ 264 G.iacbuf[G.iaclen++] = c; 265} 266 267static void put_iac2(byte wwdd, byte c) 268{ 269 if (G.iaclen + 3 > IACBUFSIZE) 270 iac_flush(); 271 272 put_iac(IAC); 273 put_iac(wwdd); 274 put_iac(c); 275} 276 277#if ENABLE_FEATURE_TELNET_TTYPE 278static void put_iac_subopt(byte c, char *str) 279{ 280 int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) 281 282 if (G.iaclen + len > IACBUFSIZE) 283 iac_flush(); 284 285 put_iac(IAC); 286 put_iac(SB); 287 put_iac(c); 288 put_iac(0); 289 290 while (*str) 291 put_iac(*str++); 292 293 put_iac(IAC); 294 put_iac(SE); 295} 296#endif 297 298#if ENABLE_FEATURE_TELNET_AUTOLOGIN 299static void put_iac_subopt_autologin(void) 300{ 301 int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2) 302 const char *p = "USER"; 303 304 if (G.iaclen + len > IACBUFSIZE) 305 iac_flush(); 306 307 put_iac(IAC); 308 put_iac(SB); 309 put_iac(TELOPT_NEW_ENVIRON); 310 put_iac(TELQUAL_IS); 311 put_iac(NEW_ENV_VAR); 312 313 while (*p) 314 put_iac(*p++); 315 316 put_iac(NEW_ENV_VALUE); 317 318 p = G.autologin; 319 while (*p) 320 put_iac(*p++); 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 /* if server requests ECHO, don't agree */ 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 full_write1_str("\r\n"); /* sudden modec */ 422} 423 424static void to_sga(void) 425{ 426 /* daemon always sends will/wont, client do/dont */ 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; /* toggle */ 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 /* Tell server we will (or won't) do TTYPE */ 445 if (G.ttype) 446 put_iac2(WILL, TELOPT_TTYPE); 447 else 448 put_iac2(WONT, TELOPT_TTYPE); 449} 450#endif 451 452#if ENABLE_FEATURE_TELNET_AUTOLOGIN 453static void to_new_environ(void) 454{ 455 /* Tell server we will (or will not) do AUTOLOGIN */ 456 if (G.autologin) 457 put_iac2(WILL, TELOPT_NEW_ENVIRON); 458 else 459 put_iac2(WONT, TELOPT_NEW_ENVIRON); 460} 461#endif 462 463#if ENABLE_FEATURE_AUTOWIDTH 464static void to_naws(void) 465{ 466 /* Tell server we will do NAWS */ 467 put_iac2(WILL, TELOPT_NAWS); 468} 469#endif 470 471static void telopt(byte c) 472{ 473 switch (c) { 474 case TELOPT_ECHO: 475 to_echo(); break; 476 case TELOPT_SGA: 477 to_sga(); break; 478#if ENABLE_FEATURE_TELNET_TTYPE 479 case TELOPT_TTYPE: 480 to_ttype(); break; 481#endif 482#if ENABLE_FEATURE_TELNET_AUTOLOGIN 483 case TELOPT_NEW_ENVIRON: 484 to_new_environ(); break; 485#endif 486#if ENABLE_FEATURE_AUTOWIDTH 487 case TELOPT_NAWS: 488 to_naws(); 489 put_iac_naws(c, G.win_width, G.win_height); 490 break; 491#endif 492 default: 493 to_notsup(c); 494 break; 495 } 496} 497 498/* subnegotiation -- ignore all (except TTYPE,NAWS) */ 499static int subneg(byte c) 500{ 501 switch (G.telstate) { 502 case TS_SUB1: 503 if (c == IAC) 504 G.telstate = TS_SUB2; 505#if ENABLE_FEATURE_TELNET_TTYPE 506 else 507 if (c == TELOPT_TTYPE && G.ttype) 508 put_iac_subopt(TELOPT_TTYPE, G.ttype); 509#endif 510#if ENABLE_FEATURE_TELNET_AUTOLOGIN 511 else 512 if (c == TELOPT_NEW_ENVIRON && G.autologin) 513 put_iac_subopt_autologin(); 514#endif 515 break; 516 case TS_SUB2: 517 if (c == SE) 518 return TRUE; 519 G.telstate = TS_SUB1; 520 /* break; */ 521 } 522 return FALSE; 523} 524 525static void rawmode(void) 526{ 527 if (G.do_termios) 528 tcsetattr(0, TCSADRAIN, &G.termios_raw); 529} 530 531static void cookmode(void) 532{ 533 if (G.do_termios) 534 tcsetattr(0, TCSADRAIN, &G.termios_def); 535} 536 537/* poll gives smaller (-70 bytes) code */ 538#define USE_POLL 1 539 540int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 541int telnet_main(int argc UNUSED_PARAM, char **argv) 542{ 543 char *host; 544 int port; 545 int len; 546#ifdef USE_POLL 547 struct pollfd ufds[2]; 548#else 549 fd_set readfds; 550 int maxfd; 551#endif 552 553 INIT_G(); 554 555#if ENABLE_FEATURE_AUTOWIDTH 556 get_terminal_width_height(0, &G.win_width, &G.win_height); 557#endif 558 559#if ENABLE_FEATURE_TELNET_TTYPE 560 G.ttype = getenv("TERM"); 561#endif 562 563 if (tcgetattr(0, &G.termios_def) >= 0) { 564 G.do_termios = 1; 565 G.termios_raw = G.termios_def; 566 cfmakeraw(&G.termios_raw); 567 } 568 569#if ENABLE_FEATURE_TELNET_AUTOLOGIN 570 if (1 & getopt32(argv, "al:", &G.autologin)) 571 G.autologin = getenv("USER"); 572 argv += optind; 573#else 574 argv++; 575#endif 576 if (!*argv) 577 bb_show_usage(); 578 host = *argv++; 579 port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); 580 if (*argv) /* extra params?? */ 581 bb_show_usage(); 582 583 xmove_fd(create_and_connect_stream_or_die(host, port), netfd); 584 585 setsockopt(netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); 586 587 signal(SIGINT, record_signo); 588 589#ifdef USE_POLL 590 ufds[0].fd = 0; ufds[1].fd = netfd; 591 ufds[0].events = ufds[1].events = POLLIN; 592#else 593 FD_ZERO(&readfds); 594 FD_SET(STDIN_FILENO, &readfds); 595 FD_SET(netfd, &readfds); 596 maxfd = netfd + 1; 597#endif 598 599 while (1) { 600#ifndef USE_POLL 601 fd_set rfds = readfds; 602 603 switch (select(maxfd, &rfds, NULL, NULL, NULL)) 604#else 605 switch (poll(ufds, 2, -1)) 606#endif 607 { 608 case 0: 609 /* timeout */ 610 case -1: 611 /* error, ignore and/or log something, bay go to loop */ 612 if (bb_got_signal) 613 con_escape(); 614 else 615 sleep(1); 616 break; 617 default: 618 619#ifdef USE_POLL 620 if (ufds[0].revents) 621#else 622 if (FD_ISSET(STDIN_FILENO, &rfds)) 623#endif 624 { 625 len = safe_read(STDIN_FILENO, G.buf, DATABUFSIZE); 626 if (len <= 0) 627 doexit(EXIT_SUCCESS); 628 TRACE(0, ("Read con: %d\n", len)); 629 handle_net_output(len); 630 } 631 632#ifdef USE_POLL 633 if (ufds[1].revents) 634#else 635 if (FD_ISSET(netfd, &rfds)) 636#endif 637 { 638 len = safe_read(netfd, G.buf, DATABUFSIZE); 639 if (len <= 0) { 640 full_write1_str("Connection closed by foreign host\r\n"); 641 doexit(EXIT_FAILURE); 642 } 643 TRACE(0, ("Read netfd (%d): %d\n", netfd, len)); 644 handle_net_input(len); 645 } 646 } 647 } /* while (1) */ 648} 649