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 <termios.h> 25#include <arpa/telnet.h> 26#include <netinet/in.h> 27#include "libbb.h" 28 29#ifdef DOTRACE 30#define TRACE(x, y) do { if (x) printf y; } while (0) 31#else 32#define TRACE(x, y) 33#endif 34 35enum { 36 DATABUFSIZE = 128, 37 IACBUFSIZE = 128, 38 39 CHM_TRY = 0, 40 CHM_ON = 1, 41 CHM_OFF = 2, 42 43 UF_ECHO = 0x01, 44 UF_SGA = 0x02, 45 46 TS_0 = 1, 47 TS_IAC = 2, 48 TS_OPT = 3, 49 TS_SUB1 = 4, 50 TS_SUB2 = 5, 51}; 52 53typedef unsigned char byte; 54 55struct globals { 56 int netfd; /* console fd:s are 0 and 1 (and 2) */ 57 short iaclen; /* could even use byte */ 58 byte telstate; /* telnet negotiation state from network input */ 59 byte telwish; /* DO, DONT, WILL, WONT */ 60 byte charmode; 61 byte telflags; 62 byte gotsig; 63 byte do_termios; 64#if ENABLE_FEATURE_TELNET_TTYPE 65 char *ttype; 66#endif 67#if ENABLE_FEATURE_TELNET_AUTOLOGIN 68 const char *autologin; 69#endif 70#if ENABLE_FEATURE_AUTOWIDTH 71 int win_width, win_height; 72#endif 73 /* same buffer used both for network and console read/write */ 74 char buf[DATABUFSIZE]; 75 /* buffer to handle telnet negotiations */ 76 char iacbuf[IACBUFSIZE]; 77 struct termios termios_def; 78 struct termios termios_raw; 79}; 80#define G (*(struct globals*)&bb_common_bufsiz1) 81void BUG_telnet_globals_too_big(void); 82#define INIT_G() do { \ 83 if (sizeof(G) > COMMON_BUFSIZE) \ 84 BUG_telnet_globals_too_big(); \ 85 /* memset(&G, 0, sizeof G); - already is */ \ 86} while (0) 87 88/* Function prototypes */ 89static void rawmode(void); 90static void cookmode(void); 91static void do_linemode(void); 92static void will_charmode(void); 93static void telopt(byte c); 94static int subneg(byte c); 95 96static void iacflush(void) 97{ 98 write(G.netfd, G.iacbuf, G.iaclen); 99 G.iaclen = 0; 100} 101 102#define write_str(fd, str) write(fd, str, sizeof(str) - 1) 103 104static void doexit(int ev) 105{ 106 cookmode(); 107 exit(ev); 108} 109 110static void conescape(void) 111{ 112 char b; 113 114 if (G.gotsig) /* came from line mode... go raw */ 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(0, &b, 1) <= 0) 124 doexit(1); 125 126 switch (b) { 127 case 'l': 128 if (!G.gotsig) { 129 do_linemode(); 130 goto rrturn; 131 } 132 break; 133 case 'c': 134 if (G.gotsig) { 135 will_charmode(); 136 goto rrturn; 137 } 138 break; 139 case 'z': 140 cookmode(); 141 kill(0, SIGTSTP); 142 rawmode(); 143 break; 144 case 'e': 145 doexit(0); 146 } 147 148 write_str(1, "continuing...\r\n"); 149 150 if (G.gotsig) 151 cookmode(); 152 153 rrturn: 154 G.gotsig = 0; 155 156} 157 158static void handlenetoutput(int len) 159{ 160 /* here we could do smart tricks how to handle 0xFF:s in output 161 * stream like writing twice every sequence of FF:s (thus doing 162 * many write()s. But I think interactive telnet application does 163 * not need to be 100% 8-bit clean, so changing every 0xff:s to 164 * 0x7f:s 165 * 166 * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com) 167 * I don't agree. 168 * first - I cannot use programs like sz/rz 169 * second - the 0x0D is sent as one character and if the next 170 * char is 0x0A then it's eaten by a server side. 171 * third - whay doy you have to make 'many write()s'? 172 * I don't understand. 173 * So I implemented it. It's realy useful for me. I hope that 174 * others people will find it interesting to. 175 */ 176 177 int i, j; 178 byte * p = (byte*)G.buf; 179 byte outbuf[4*DATABUFSIZE]; 180 181 for (i = len, j = 0; i > 0; i--, p++) 182 { 183 if (*p == 0x1d) 184 { 185 conescape(); 186 return; 187 } 188 outbuf[j++] = *p; 189 if (*p == 0xff) 190 outbuf[j++] = 0xff; 191 else if (*p == 0x0d) 192 outbuf[j++] = 0x00; 193 } 194 if (j > 0) 195 write(G.netfd, outbuf, j); 196} 197 198static void handlenetinput(int len) 199{ 200 int i; 201 int cstart = 0; 202 203 for (i = 0; i < len; i++) 204 { 205 byte c = G.buf[i]; 206 207 if (G.telstate == 0) /* most of the time state == 0 */ 208 { 209 if (c == IAC) 210 { 211 cstart = i; 212 G.telstate = TS_IAC; 213 } 214 } 215 else 216 switch (G.telstate) 217 { 218 case TS_0: 219 if (c == IAC) 220 G.telstate = TS_IAC; 221 else 222 G.buf[cstart++] = c; 223 break; 224 225 case TS_IAC: 226 if (c == IAC) /* IAC IAC -> 0xFF */ 227 { 228 G.buf[cstart++] = c; 229 G.telstate = TS_0; 230 break; 231 } 232 /* else */ 233 switch (c) 234 { 235 case SB: 236 G.telstate = TS_SUB1; 237 break; 238 case DO: 239 case DONT: 240 case WILL: 241 case WONT: 242 G.telwish = c; 243 G.telstate = TS_OPT; 244 break; 245 default: 246 G.telstate = TS_0; /* DATA MARK must be added later */ 247 } 248 break; 249 case TS_OPT: /* WILL, WONT, DO, DONT */ 250 telopt(c); 251 G.telstate = TS_0; 252 break; 253 case TS_SUB1: /* Subnegotiation */ 254 case TS_SUB2: /* Subnegotiation */ 255 if (subneg(c)) 256 G.telstate = TS_0; 257 break; 258 } 259 } 260 if (G.telstate) 261 { 262 if (G.iaclen) iacflush(); 263 if (G.telstate == TS_0) G.telstate = 0; 264 265 len = cstart; 266 } 267 268 if (len) 269 write(1, G.buf, len); 270} 271 272static void putiac(int c) 273{ 274 G.iacbuf[G.iaclen++] = c; 275} 276 277static void putiac2(byte wwdd, byte c) 278{ 279 if (G.iaclen + 3 > IACBUFSIZE) 280 iacflush(); 281 282 putiac(IAC); 283 putiac(wwdd); 284 putiac(c); 285} 286 287#if ENABLE_FEATURE_TELNET_TTYPE 288static void putiac_subopt(byte c, char *str) 289{ 290 int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) 291 292 if (G.iaclen + len > IACBUFSIZE) 293 iacflush(); 294 295 putiac(IAC); 296 putiac(SB); 297 putiac(c); 298 putiac(0); 299 300 while (*str) 301 putiac(*str++); 302 303 putiac(IAC); 304 putiac(SE); 305} 306#endif 307 308#if ENABLE_FEATURE_TELNET_AUTOLOGIN 309static void putiac_subopt_autologin(void) 310{ 311 int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2) 312 const char *user = "USER"; 313 314 if (G.iaclen + len > IACBUFSIZE) 315 iacflush(); 316 317 putiac(IAC); 318 putiac(SB); 319 putiac(TELOPT_NEW_ENVIRON); 320 putiac(TELQUAL_IS); 321 putiac(NEW_ENV_VAR); 322 323 while (*user) 324 putiac(*user++); 325 326 putiac(NEW_ENV_VALUE); 327 328 while (*G.autologin) 329 putiac(*G.autologin++); 330 331 putiac(IAC); 332 putiac(SE); 333} 334#endif 335 336#if ENABLE_FEATURE_AUTOWIDTH 337static void putiac_naws(byte c, int x, int y) 338{ 339 if (G.iaclen + 9 > IACBUFSIZE) 340 iacflush(); 341 342 putiac(IAC); 343 putiac(SB); 344 putiac(c); 345 346 putiac((x >> 8) & 0xff); 347 putiac(x & 0xff); 348 putiac((y >> 8) & 0xff); 349 putiac(y & 0xff); 350 351 putiac(IAC); 352 putiac(SE); 353} 354#endif 355 356static char const escapecharis[] ALIGN1 = "\r\nEscape character is "; 357 358static void setConMode(void) 359{ 360 if (G.telflags & UF_ECHO) { 361 if (G.charmode == CHM_TRY) { 362 G.charmode = CHM_ON; 363 printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); 364 rawmode(); 365 } 366 } else { 367 if (G.charmode != CHM_OFF) { 368 G.charmode = CHM_OFF; 369 printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); 370 cookmode(); 371 } 372 } 373} 374 375static void will_charmode(void) 376{ 377 G.charmode = CHM_TRY; 378 G.telflags |= (UF_ECHO | UF_SGA); 379 setConMode(); 380 381 putiac2(DO, TELOPT_ECHO); 382 putiac2(DO, TELOPT_SGA); 383 iacflush(); 384} 385 386static void do_linemode(void) 387{ 388 G.charmode = CHM_TRY; 389 G.telflags &= ~(UF_ECHO | UF_SGA); 390 setConMode(); 391 392 putiac2(DONT, TELOPT_ECHO); 393 putiac2(DONT, TELOPT_SGA); 394 iacflush(); 395} 396 397static void to_notsup(char c) 398{ 399 if (G.telwish == WILL) 400 putiac2(DONT, c); 401 else if (G.telwish == DO) 402 putiac2(WONT, c); 403} 404 405static void to_echo(void) 406{ 407 /* if server requests ECHO, don't agree */ 408 if (G.telwish == DO) { 409 putiac2(WONT, TELOPT_ECHO); 410 return; 411 } 412 if (G.telwish == DONT) 413 return; 414 415 if (G.telflags & UF_ECHO) { 416 if (G.telwish == WILL) 417 return; 418 } else if (G.telwish == WONT) 419 return; 420 421 if (G.charmode != CHM_OFF) 422 G.telflags ^= UF_ECHO; 423 424 if (G.telflags & UF_ECHO) 425 putiac2(DO, TELOPT_ECHO); 426 else 427 putiac2(DONT, TELOPT_ECHO); 428 429 setConMode(); 430 write_str(1, "\r\n"); /* sudden modec */ 431} 432 433static void to_sga(void) 434{ 435 /* daemon always sends will/wont, client do/dont */ 436 437 if (G.telflags & UF_SGA) { 438 if (G.telwish == WILL) 439 return; 440 } else if (G.telwish == WONT) 441 return; 442 443 if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ 444 putiac2(DO, TELOPT_SGA); 445 else 446 putiac2(DONT, TELOPT_SGA); 447} 448 449#if ENABLE_FEATURE_TELNET_TTYPE 450static void to_ttype(void) 451{ 452 /* Tell server we will (or won't) do TTYPE */ 453 454 if (G.ttype) 455 putiac2(WILL, TELOPT_TTYPE); 456 else 457 putiac2(WONT, TELOPT_TTYPE); 458} 459#endif 460 461#if ENABLE_FEATURE_TELNET_AUTOLOGIN 462static void to_new_environ(void) 463{ 464 /* Tell server we will (or will not) do AUTOLOGIN */ 465 466 if (G.autologin) 467 putiac2(WILL, TELOPT_NEW_ENVIRON); 468 else 469 putiac2(WONT, TELOPT_NEW_ENVIRON); 470} 471#endif 472 473#if ENABLE_FEATURE_AUTOWIDTH 474static void to_naws(void) 475{ 476 /* Tell server we will do NAWS */ 477 putiac2(WILL, TELOPT_NAWS); 478} 479#endif 480 481static void telopt(byte c) 482{ 483 switch (c) { 484 case TELOPT_ECHO: 485 to_echo(); break; 486 case TELOPT_SGA: 487 to_sga(); break; 488#if ENABLE_FEATURE_TELNET_TTYPE 489 case TELOPT_TTYPE: 490 to_ttype(); break; 491#endif 492#if ENABLE_FEATURE_TELNET_AUTOLOGIN 493 case TELOPT_NEW_ENVIRON: 494 to_new_environ(); break; 495#endif 496#if ENABLE_FEATURE_AUTOWIDTH 497 case TELOPT_NAWS: 498 to_naws(); 499 putiac_naws(c, G.win_width, G.win_height); 500 break; 501#endif 502 default: 503 to_notsup(c); 504 break; 505 } 506} 507 508/* subnegotiation -- ignore all (except TTYPE,NAWS) */ 509static int subneg(byte c) 510{ 511 switch (G.telstate) { 512 case TS_SUB1: 513 if (c == IAC) 514 G.telstate = TS_SUB2; 515#if ENABLE_FEATURE_TELNET_TTYPE 516 else 517 if (c == TELOPT_TTYPE) 518 putiac_subopt(TELOPT_TTYPE, G.ttype); 519#endif 520#if ENABLE_FEATURE_TELNET_AUTOLOGIN 521 else 522 if (c == TELOPT_NEW_ENVIRON) 523 putiac_subopt_autologin(); 524#endif 525 break; 526 case TS_SUB2: 527 if (c == SE) 528 return TRUE; 529 G.telstate = TS_SUB1; 530 /* break; */ 531 } 532 return FALSE; 533} 534 535static void fgotsig(int sig) 536{ 537 G.gotsig = sig; 538} 539 540 541static void rawmode(void) 542{ 543 if (G.do_termios) 544 tcsetattr(0, TCSADRAIN, &G.termios_raw); 545} 546 547static void cookmode(void) 548{ 549 if (G.do_termios) 550 tcsetattr(0, TCSADRAIN, &G.termios_def); 551} 552 553int telnet_main(int argc, char** argv); 554int telnet_main(int argc, char** argv) 555{ 556 char *host; 557 int port; 558 int len; 559#ifdef USE_POLL 560 struct pollfd ufds[2]; 561#else 562 fd_set readfds; 563 int maxfd; 564#endif 565 566 INIT_G(); 567 568#if ENABLE_FEATURE_AUTOWIDTH 569 get_terminal_width_height(0, &G.win_width, &G.win_height); 570#endif 571 572#if ENABLE_FEATURE_TELNET_TTYPE 573 G.ttype = getenv("TERM"); 574#endif 575 576 if (tcgetattr(0, &G.termios_def) >= 0) { 577 G.do_termios = 1; 578 G.termios_raw = G.termios_def; 579 cfmakeraw(&G.termios_raw); 580 } 581 582 if (argc < 2) 583 bb_show_usage(); 584 585#if ENABLE_FEATURE_TELNET_AUTOLOGIN 586 if (1 & getopt32(argv, "al:", &G.autologin)) 587 G.autologin = getenv("USER"); 588 argv += optind; 589#else 590 argv++; 591#endif 592 if (!*argv) 593 bb_show_usage(); 594 host = *argv++; 595 port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); 596 if (*argv) /* extra params?? */ 597 bb_show_usage(); 598 599 G.netfd = create_and_connect_stream_or_die(host, port); 600 601 setsockopt(G.netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); 602 603 signal(SIGINT, fgotsig); 604 605#ifdef USE_POLL 606 ufds[0].fd = 0; ufds[1].fd = G.netfd; 607 ufds[0].events = ufds[1].events = POLLIN; 608#else 609 FD_ZERO(&readfds); 610 FD_SET(0, &readfds); 611 FD_SET(G.netfd, &readfds); 612 maxfd = G.netfd + 1; 613#endif 614 615 while (1) { 616#ifndef USE_POLL 617 fd_set rfds = readfds; 618 619 switch (select(maxfd, &rfds, NULL, NULL, NULL)) 620#else 621 switch (poll(ufds, 2, -1)) 622#endif 623 { 624 case 0: 625 /* timeout */ 626 case -1: 627 /* error, ignore and/or log something, bay go to loop */ 628 if (G.gotsig) 629 conescape(); 630 else 631 sleep(1); 632 break; 633 default: 634 635#ifdef USE_POLL 636 if (ufds[0].revents) /* well, should check POLLIN, but ... */ 637#else 638 if (FD_ISSET(0, &rfds)) 639#endif 640 { 641 len = read(0, G.buf, DATABUFSIZE); 642 if (len <= 0) 643 doexit(0); 644 TRACE(0, ("Read con: %d\n", len)); 645 handlenetoutput(len); 646 } 647 648#ifdef USE_POLL 649 if (ufds[1].revents) /* well, should check POLLIN, but ... */ 650#else 651 if (FD_ISSET(G.netfd, &rfds)) 652#endif 653 { 654 len = read(G.netfd, G.buf, DATABUFSIZE); 655 if (len <= 0) { 656 write_str(1, "Connection closed by foreign host\r\n"); 657 doexit(1); 658 } 659 TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len)); 660 handlenetinput(len); 661 } 662 } 663 } 664} 665