busybox/networking/telnet.c
<<
>>
Prefs
   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};
  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        /* memset(&G, 0, sizeof G); - already is */ \
  85} while (0)
  86
  87/* Function prototypes */
  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) /* 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(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        /* here we could do smart tricks how to handle 0xFF:s in output
 160         * stream like writing twice every sequence of FF:s (thus doing
 161         * many write()s. But I think interactive telnet application does
 162         * not need to be 100% 8-bit clean, so changing every 0xff:s to
 163         * 0x7f:s
 164         *
 165         * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com)
 166         * I don't agree.
 167         * first - I cannot use programs like sz/rz
 168         * second - the 0x0D is sent as one character and if the next
 169         *      char is 0x0A then it's eaten by a server side.
 170         * third - why do you have to make 'many write()s'?
 171         *      I don't understand.
 172         * So I implemented it. It's really useful for me. I hope that
 173         * other people will find it interesting too.
 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) { /* most of the time state == 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) { /* IAC IAC -> 0xFF */
 220                                G.buf[cstart++] = c;
 221                                G.telstate = TS_0;
 222                                break;
 223                        }
 224                        /* else */
 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;      /* DATA MARK must be added later */
 238                        }
 239                        break;
 240                case TS_OPT: /* WILL, WONT, DO, DONT */
 241                        telopt(c);
 242                        G.telstate = TS_0;
 243                        break;
 244                case TS_SUB1: /* Subnegotiation */
 245                case TS_SUB2: /* Subnegotiation */
 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;   // ( 2 + 1 + 1 + strlen + 2 )
 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;      // (2 + 1 + 1 + strlen + 2)
 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        /* 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        write_str(1, "\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
 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        /* Tell server we will (or will not) do AUTOLOGIN */
 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        /* Tell server we will do NAWS */
 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/* subnegotiation -- ignore all (except TTYPE,NAWS) */
 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                /* break; */
 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/* poll gives smaller (-70 bytes) code */
 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) /* extra params?? */
 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                        /* timeout */
 612                case -1:
 613                        /* error, ignore and/or log something, bay go to loop */
 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) /* well, should check POLLIN, but ... */
 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) /* well, should check POLLIN, but ... */
 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        } /* while (1) */
 650}
 651