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 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 * 25 * HISTORY 26 * Revision 3.1 1994/04/17 11:31:54 too 27 * initial revision 28 * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen 29 * <andersen@lineo.com> 30 * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan 31 * <jam@ltsp.org> 32 * 33 */ 34 35#include <termios.h> 36#include <unistd.h> 37#include <errno.h> 38#include <stdlib.h> 39#include <stdarg.h> 40#include <string.h> 41#include <signal.h> 42#include <arpa/telnet.h> 43#include <sys/types.h> 44#include <sys/socket.h> 45#include <netinet/in.h> 46#include <netdb.h> 47#include "busybox.h" 48 49 50#ifdef DOTRACE 51#include <arpa/inet.h> /* for inet_ntoa()... */ 52#define TRACE(x, y) do { if (x) printf y; } while (0) 53#else 54#define TRACE(x, y) 55#endif 56 57#include <sys/time.h> 58 59#define DATABUFSIZE 128 60#define IACBUFSIZE 128 61 62static const int CHM_TRY = 0; 63static const int CHM_ON = 1; 64static const int CHM_OFF = 2; 65 66static const int UF_ECHO = 0x01; 67static const int UF_SGA = 0x02; 68 69enum { 70 TS_0 = 1, 71 TS_IAC = 2, 72 TS_OPT = 3, 73 TS_SUB1 = 4, 74 TS_SUB2 = 5, 75}; 76 77#define WriteCS(fd, str) write(fd, str, sizeof str -1) 78 79typedef unsigned char byte; 80 81/* use globals to reduce size ??? */ /* test this hypothesis later */ 82static struct Globalvars { 83 int netfd; /* console fd:s are 0 and 1 (and 2) */ 84 /* same buffer used both for network and console read/write */ 85 char buf[DATABUFSIZE]; /* allocating so static size is smaller */ 86 byte telstate; /* telnet negotiation state from network input */ 87 byte telwish; /* DO, DONT, WILL, WONT */ 88 byte charmode; 89 byte telflags; 90 byte gotsig; 91 /* buffer to handle telnet negotiations */ 92 char iacbuf[IACBUFSIZE]; 93 short iaclen; /* could even use byte */ 94 struct termios termios_def; 95 struct termios termios_raw; 96} G; 97 98#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */ 99 100#ifdef USE_GLOBALVAR_PTR 101struct Globalvars * Gptr; 102#define G (*Gptr) 103#else 104static struct Globalvars G; 105#endif 106 107static inline void iacflush() 108{ 109 write(G.netfd, G.iacbuf, G.iaclen); 110 G.iaclen = 0; 111} 112 113/* Function prototypes */ 114static int getport(char * p); 115static struct in_addr getserver(char * p); 116static int create_socket(); 117static void setup_sockaddr_in(struct sockaddr_in * addr, int port); 118static int remote_connect(struct in_addr addr, int port); 119static void rawmode(); 120static void cookmode(); 121static void do_linemode(); 122static void will_charmode(); 123static void telopt(byte c); 124static int subneg(byte c); 125 126/* Some globals */ 127static int one = 1; 128 129#ifdef BB_FEATURE_TELNET_TTYPE 130static char *ttype; 131#endif 132 133static void doexit(int ev) 134{ 135 cookmode(); 136 exit(ev); 137} 138 139static void conescape() 140{ 141 char b; 142 143 if (G.gotsig) /* came from line mode... go raw */ 144 rawmode(); 145 146 WriteCS(1, "\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(0, &b, 1) <= 0) 153 doexit(1); 154 155 switch (b) 156 { 157 case 'l': 158 if (!G.gotsig) 159 { 160 do_linemode(); 161 goto rrturn; 162 } 163 break; 164 case 'c': 165 if (G.gotsig) 166 { 167 will_charmode(); 168 goto rrturn; 169 } 170 break; 171 case 'z': 172 cookmode(); 173 kill(0, SIGTSTP); 174 rawmode(); 175 break; 176 case 'e': 177 doexit(0); 178 } 179 180 WriteCS(1, "continuing...\r\n"); 181 182 if (G.gotsig) 183 cookmode(); 184 185 rrturn: 186 G.gotsig = 0; 187 188} 189static void handlenetoutput(int len) 190{ 191 /* here we could do smart tricks how to handle 0xFF:s in output 192 * stream like writing twice every sequence of FF:s (thus doing 193 * many write()s. But I think interactive telnet application does 194 * not need to be 100% 8-bit clean, so changing every 0xff:s to 195 * 0x7f:s */ 196 197 int i; 198 byte * p = G.buf; 199 200 for (i = len; i > 0; i--, p++) 201 { 202 if (*p == 0x1d) 203 { 204 conescape(); 205 return; 206 } 207 if (*p == 0xff) 208 *p = 0x7f; 209 } 210 write(G.netfd, G.buf, len); 211} 212 213 214static void handlenetinput(int len) 215{ 216 int i; 217 int cstart = 0; 218 219 for (i = 0; i < len; i++) 220 { 221 byte c = G.buf[i]; 222 223 if (G.telstate == 0) /* most of the time state == 0 */ 224 { 225 if (c == IAC) 226 { 227 cstart = i; 228 G.telstate = TS_IAC; 229 } 230 } 231 else 232 switch (G.telstate) 233 { 234 case TS_0: 235 if (c == IAC) 236 G.telstate = TS_IAC; 237 else 238 G.buf[cstart++] = c; 239 break; 240 241 case TS_IAC: 242 if (c == IAC) /* IAC IAC -> 0xFF */ 243 { 244 G.buf[cstart++] = c; 245 G.telstate = TS_0; 246 break; 247 } 248 /* else */ 249 switch (c) 250 { 251 case SB: 252 G.telstate = TS_SUB1; 253 break; 254 case DO: 255 case DONT: 256 case WILL: 257 case WONT: 258 G.telwish = c; 259 G.telstate = TS_OPT; 260 break; 261 default: 262 G.telstate = TS_0; /* DATA MARK must be added later */ 263 } 264 break; 265 case TS_OPT: /* WILL, WONT, DO, DONT */ 266 telopt(c); 267 G.telstate = TS_0; 268 break; 269 case TS_SUB1: /* Subnegotiation */ 270 case TS_SUB2: /* Subnegotiation */ 271 if (subneg(c) == TRUE) 272 G.telstate = TS_0; 273 break; 274 } 275 } 276 if (G.telstate) 277 { 278 if (G.iaclen) iacflush(); 279 if (G.telstate == TS_0) G.telstate = 0; 280 281 len = cstart; 282 } 283 284 if (len) 285 write(1, G.buf, len); 286} 287 288 289/* ******************************* */ 290 291static inline void putiac(int c) 292{ 293 G.iacbuf[G.iaclen++] = c; 294} 295 296 297static void putiac2(byte wwdd, byte c) 298{ 299 if (G.iaclen + 3 > IACBUFSIZE) 300 iacflush(); 301 302 putiac(IAC); 303 putiac(wwdd); 304 putiac(c); 305} 306 307 308#ifdef BB_FEATURE_TELNET_TTYPE 309static void putiac_subopt(byte c, char *str) 310{ 311 int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) 312 313 if (G.iaclen + len > IACBUFSIZE) 314 iacflush(); 315 316 putiac(IAC); 317 putiac(SB); 318 putiac(c); 319 putiac(0); 320 321 while(*str) 322 putiac(*str++); 323 324 putiac(IAC); 325 putiac(SE); 326} 327#endif 328 329/* void putiacstring (subneg strings) */ 330 331/* ******************************* */ 332 333static char const escapecharis[] = "\r\nEscape character is "; 334 335static void setConMode() 336{ 337 if (G.telflags & UF_ECHO) 338 { 339 if (G.charmode == CHM_TRY) { 340 G.charmode = CHM_ON; 341 printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); 342 rawmode(); 343 } 344 } 345 else 346 { 347 if (G.charmode != CHM_OFF) { 348 G.charmode = CHM_OFF; 349 printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); 350 cookmode(); 351 } 352 } 353} 354 355/* ******************************* */ 356 357static void will_charmode() 358{ 359 G.charmode = CHM_TRY; 360 G.telflags |= (UF_ECHO | UF_SGA); 361 setConMode(); 362 363 putiac2(DO, TELOPT_ECHO); 364 putiac2(DO, TELOPT_SGA); 365 iacflush(); 366} 367 368static void do_linemode() 369{ 370 G.charmode = CHM_TRY; 371 G.telflags &= ~(UF_ECHO | UF_SGA); 372 setConMode(); 373 374 putiac2(DONT, TELOPT_ECHO); 375 putiac2(DONT, TELOPT_SGA); 376 iacflush(); 377} 378 379/* ******************************* */ 380 381static inline void to_notsup(char c) 382{ 383 if (G.telwish == WILL) putiac2(DONT, c); 384 else if (G.telwish == DO) putiac2(WONT, c); 385} 386 387static inline void to_echo() 388{ 389 /* if server requests ECHO, don't agree */ 390 if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; } 391 else if (G.telwish == DONT) return; 392 393 if (G.telflags & UF_ECHO) 394 { 395 if (G.telwish == WILL) 396 return; 397 } 398 else 399 if (G.telwish == WONT) 400 return; 401 402 if (G.charmode != CHM_OFF) 403 G.telflags ^= UF_ECHO; 404 405 if (G.telflags & UF_ECHO) 406 putiac2(DO, TELOPT_ECHO); 407 else 408 putiac2(DONT, TELOPT_ECHO); 409 410 setConMode(); 411 WriteCS(1, "\r\n"); /* sudden modec */ 412} 413 414static inline void to_sga() 415{ 416 /* daemon always sends will/wont, client do/dont */ 417 418 if (G.telflags & UF_SGA) 419 { 420 if (G.telwish == WILL) 421 return; 422 } 423 else 424 if (G.telwish == WONT) 425 return; 426 427 if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ 428 putiac2(DO, TELOPT_SGA); 429 else 430 putiac2(DONT, TELOPT_SGA); 431 432 return; 433} 434 435#ifdef BB_FEATURE_TELNET_TTYPE 436static inline void to_ttype() 437{ 438 /* Tell server we will (or won't) do TTYPE */ 439 440 if(ttype) 441 putiac2(WILL, TELOPT_TTYPE); 442 else 443 putiac2(WONT, TELOPT_TTYPE); 444 445 return; 446} 447#endif 448 449static void telopt(byte c) 450{ 451 switch (c) 452 { 453 case TELOPT_ECHO: to_echo(c); break; 454 case TELOPT_SGA: to_sga(c); break; 455#ifdef BB_FEATURE_TELNET_TTYPE 456 case TELOPT_TTYPE: to_ttype(c); break; 457#endif 458 default: to_notsup(c); break; 459 } 460} 461 462 463/* ******************************* */ 464 465/* subnegotiation -- ignore all (except TTYPE) */ 466 467static int subneg(byte c) 468{ 469 switch (G.telstate) 470 { 471 case TS_SUB1: 472 if (c == IAC) 473 G.telstate = TS_SUB2; 474#ifdef BB_FEATURE_TELNET_TTYPE 475 else 476 if (c == TELOPT_TTYPE) 477 putiac_subopt(TELOPT_TTYPE,ttype); 478#endif 479 break; 480 case TS_SUB2: 481 if (c == SE) 482 return TRUE; 483 G.telstate = TS_SUB1; 484 /* break; */ 485 } 486 return FALSE; 487} 488 489/* ******************************* */ 490 491static void fgotsig(int sig) 492{ 493 G.gotsig = sig; 494} 495 496 497static void rawmode() 498{ 499 tcsetattr(0, TCSADRAIN, &G.termios_raw); 500} 501 502static void cookmode() 503{ 504 tcsetattr(0, TCSADRAIN, &G.termios_def); 505} 506 507extern int telnet_main(int argc, char** argv) 508{ 509 struct in_addr host; 510 int port; 511 int len; 512#ifdef USE_POLL 513 struct pollfd ufds[2]; 514#else 515 fd_set readfds; 516 int maxfd; 517#endif 518 519#ifdef BB_FEATURE_TELNET_TTYPE 520 ttype = getenv("TERM"); 521#endif 522 523 memset(&G, 0, sizeof G); 524 525 if (tcgetattr(0, &G.termios_def) < 0) 526 exit(1); 527 528 G.termios_raw = G.termios_def; 529 cfmakeraw(&G.termios_raw); 530 531 if (argc < 2) show_usage(); 532 port = (argc > 2)? getport(argv[2]): 23; 533 534 host = getserver(argv[1]); 535 536 G.netfd = remote_connect(host, port); 537 538 signal(SIGINT, fgotsig); 539 540#ifdef USE_POLL 541 ufds[0].fd = 0; ufds[1].fd = G.netfd; 542 ufds[0].events = ufds[1].events = POLLIN; 543#else 544 FD_ZERO(&readfds); 545 FD_SET(0, &readfds); 546 FD_SET(G.netfd, &readfds); 547 maxfd = G.netfd + 1; 548#endif 549 550 while (1) 551 { 552#ifndef USE_POLL 553 fd_set rfds = readfds; 554 555 switch (select(maxfd, &rfds, NULL, NULL, NULL)) 556#else 557 switch (poll(ufds, 2, -1)) 558#endif 559 { 560 case 0: 561 /* timeout */ 562 case -1: 563 /* error, ignore and/or log something, bay go to loop */ 564 if (G.gotsig) 565 conescape(); 566 else 567 sleep(1); 568 break; 569 default: 570 571#ifdef USE_POLL 572 if (ufds[0].revents) /* well, should check POLLIN, but ... */ 573#else 574 if (FD_ISSET(0, &rfds)) 575#endif 576 { 577 len = read(0, G.buf, DATABUFSIZE); 578 579 if (len <= 0) 580 doexit(0); 581 582 TRACE(0, ("Read con: %d\n", len)); 583 584 handlenetoutput(len); 585 } 586 587#ifdef USE_POLL 588 if (ufds[1].revents) /* well, should check POLLIN, but ... */ 589#else 590 if (FD_ISSET(G.netfd, &rfds)) 591#endif 592 { 593 len = read(G.netfd, G.buf, DATABUFSIZE); 594 595 if (len <= 0) 596 { 597 WriteCS(1, "Connection closed by foreign host.\r\n"); 598 doexit(1); 599 } 600 TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len)); 601 602 handlenetinput(len); 603 } 604 } 605 } 606} 607 608static int getport(char * p) 609{ 610 unsigned int port = atoi(p); 611 612 if ((unsigned)(port - 1 ) > 65534) 613 { 614 error_msg_and_die("%s: bad port number", p); 615 } 616 return port; 617} 618 619static struct in_addr getserver(char * host) 620{ 621 struct in_addr addr; 622 623 struct hostent * he; 624 he = xgethostbyname(host); 625 memcpy(&addr, he->h_addr, sizeof addr); 626 627 TRACE(1, ("addr: %s\n", inet_ntoa(addr))); 628 629 return addr; 630} 631 632static int create_socket() 633{ 634 return socket(AF_INET, SOCK_STREAM, 0); 635} 636 637static void setup_sockaddr_in(struct sockaddr_in * addr, int port) 638{ 639 memset(addr, 0, sizeof(struct sockaddr_in)); 640 addr->sin_family = AF_INET; 641 addr->sin_port = htons(port); 642} 643 644 645static int remote_connect(struct in_addr addr, int port) 646{ 647 struct sockaddr_in s_addr; 648 int s = create_socket(); 649 650 setup_sockaddr_in(&s_addr, port); 651 s_addr.sin_addr = addr; 652 653 setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); 654 655 if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0) 656 { 657 perror_msg_and_die("Unable to connect to remote host"); 658 } 659 return s; 660} 661 662/* 663Local Variables: 664c-file-style: "linux" 665c-basic-offset: 4 666tab-width: 4 667End: 668*/ 669 670