busybox/networking/telnetd.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Simple telnet server
   4 * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
   5 *
   6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   7 *
   8 * ---------------------------------------------------------------------------
   9 * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
  10 ****************************************************************************
  11 *
  12 * The telnetd manpage says it all:
  13 *
  14 * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for
  15 * a client, then creating a login process which has the slave side of the
  16 * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
  17 * master side of the pseudo-terminal, implementing the telnet protocol and
  18 * passing characters between the remote client and the login process.
  19 *
  20 * Vladimir Oleynik <dzo@simtreas.ru> 2001
  21 * Set process group corrections, initial busybox port
  22 */
  23
  24//usage:#define telnetd_trivial_usage
  25//usage:       "[OPTIONS]"
  26//usage:#define telnetd_full_usage "\n\n"
  27//usage:       "Handle incoming telnet connections"
  28//usage:        IF_NOT_FEATURE_TELNETD_STANDALONE(" via inetd") "\n"
  29//usage:     "\n        -l LOGIN        Exec LOGIN on connect"
  30//usage:     "\n        -f ISSUE_FILE   Display ISSUE_FILE instead of /etc/issue"
  31//usage:     "\n        -K              Close connection as soon as login exits"
  32//usage:     "\n                        (normally wait until all programs close slave pty)"
  33//usage:        IF_FEATURE_TELNETD_STANDALONE(
  34//usage:     "\n        -p PORT         Port to listen on"
  35//usage:     "\n        -b ADDR[:PORT]  Address to bind to"
  36//usage:     "\n        -F              Run in foreground"
  37//usage:     "\n        -i              Inetd mode"
  38//usage:        IF_FEATURE_TELNETD_INETD_WAIT(
  39//usage:     "\n        -w SEC          Inetd 'wait' mode, linger time SEC"
  40//usage:     "\n        -S              Log to syslog (implied by -i or without -F and -w)"
  41//usage:        )
  42//usage:        )
  43
  44#define DEBUG 0
  45
  46#include "libbb.h"
  47#include "common_bufsiz.h"
  48#include <syslog.h>
  49
  50#if DEBUG
  51# define TELCMDS
  52# define TELOPTS
  53#endif
  54#include <arpa/telnet.h>
  55
  56
  57struct tsession {
  58        struct tsession *next;
  59        pid_t shell_pid;
  60        int sockfd_read;
  61        int sockfd_write;
  62        int ptyfd;
  63
  64        /* two circular buffers */
  65        /*char *buf1, *buf2;*/
  66/*#define TS_BUF1(ts) ts->buf1*/
  67/*#define TS_BUF2(ts) TS_BUF2(ts)*/
  68#define TS_BUF1(ts) ((unsigned char*)(ts + 1))
  69#define TS_BUF2(ts) (((unsigned char*)(ts + 1)) + BUFSIZE)
  70        int rdidx1, wridx1, size1;
  71        int rdidx2, wridx2, size2;
  72};
  73
  74/* Two buffers are directly after tsession in malloced memory.
  75 * Make whole thing fit in 4k */
  76enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
  77
  78
  79/* Globals */
  80struct globals {
  81        struct tsession *sessions;
  82        const char *loginpath;
  83        const char *issuefile;
  84        int maxfd;
  85} FIX_ALIASING;
  86#define G (*(struct globals*)bb_common_bufsiz1)
  87#define INIT_G() do { \
  88        setup_common_bufsiz(); \
  89        G.loginpath = "/bin/login"; \
  90        G.issuefile = "/etc/issue.net"; \
  91} while (0)
  92
  93
  94/*
  95   Remove all IAC's from buf1 (received IACs are ignored and must be removed
  96   so as to not be interpreted by the terminal).  Make an uninterrupted
  97   string of characters fit for the terminal.  Do this by packing
  98   all characters meant for the terminal sequentially towards the end of buf.
  99
 100   Return a pointer to the beginning of the characters meant for the terminal
 101   and make *num_totty the number of characters that should be sent to
 102   the terminal.
 103
 104   Note - if an IAC (3 byte quantity) starts before (bf + len) but extends
 105   past (bf + len) then that IAC will be left unprocessed and *processed
 106   will be less than len.
 107
 108   CR-LF ->'s CR mapping is also done here, for convenience.
 109
 110   NB: may fail to remove iacs which wrap around buffer!
 111 */
 112static unsigned char *
 113remove_iacs(struct tsession *ts, int *pnum_totty)
 114{
 115        unsigned char *ptr0 = TS_BUF1(ts) + ts->wridx1;
 116        unsigned char *ptr = ptr0;
 117        unsigned char *totty = ptr;
 118        unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
 119        int num_totty;
 120
 121        while (ptr < end) {
 122                if (*ptr != IAC) {
 123                        char c = *ptr;
 124
 125                        *totty++ = c;
 126                        ptr++;
 127                        /* We map \r\n ==> \r for pragmatic reasons.
 128                         * Many client implementations send \r\n when
 129                         * the user hits the CarriageReturn key.
 130                         * See RFC 1123 3.3.1 Telnet End-of-Line Convention.
 131                         */
 132                        if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
 133                                ptr++;
 134                        continue;
 135                }
 136
 137                if ((ptr+1) >= end)
 138                        break;
 139                if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */
 140                        ptr += 2;
 141                        continue;
 142                }
 143                if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */
 144                        *totty++ = ptr[1];
 145                        ptr += 2;
 146                        continue;
 147                }
 148
 149                /*
 150                 * TELOPT_NAWS support!
 151                 */
 152                if ((ptr+2) >= end) {
 153                        /* Only the beginning of the IAC is in the
 154                        buffer we were asked to process, we can't
 155                        process this char */
 156                        break;
 157                }
 158                /*
 159                 * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
 160                 */
 161                if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
 162                        struct winsize ws;
 163                        if ((ptr+8) >= end)
 164                                break;  /* incomplete, can't process */
 165                        ws.ws_col = (ptr[3] << 8) | ptr[4];
 166                        ws.ws_row = (ptr[5] << 8) | ptr[6];
 167                        ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
 168                        ptr += 9;
 169                        continue;
 170                }
 171                /* skip 3-byte IAC non-SB cmd */
 172#if DEBUG
 173                fprintf(stderr, "Ignoring IAC %s,%s\n",
 174                                TELCMD(ptr[1]), TELOPT(ptr[2]));
 175#endif
 176                ptr += 3;
 177        }
 178
 179        num_totty = totty - ptr0;
 180        *pnum_totty = num_totty;
 181        /* The difference between ptr and totty is number of iacs
 182           we removed from the stream. Adjust buf1 accordingly */
 183        if ((ptr - totty) == 0) /* 99.999% of cases */
 184                return ptr0;
 185        ts->wridx1 += ptr - totty;
 186        ts->size1 -= ptr - totty;
 187        /* Move chars meant for the terminal towards the end of the buffer */
 188        return memmove(ptr - num_totty, ptr0, num_totty);
 189}
 190
 191/*
 192 * Converting single IAC into double on output
 193 */
 194static size_t iac_safe_write(int fd, const char *buf, size_t count)
 195{
 196        const char *IACptr;
 197        size_t wr, rc, total;
 198
 199        total = 0;
 200        while (1) {
 201                if (count == 0)
 202                        return total;
 203                if (*buf == (char)IAC) {
 204                        static const char IACIAC[] ALIGN1 = { IAC, IAC };
 205                        rc = safe_write(fd, IACIAC, 2);
 206                        if (rc != 2)
 207                                break;
 208                        buf++;
 209                        total++;
 210                        count--;
 211                        continue;
 212                }
 213                /* count != 0, *buf != IAC */
 214                IACptr = memchr(buf, IAC, count);
 215                wr = count;
 216                if (IACptr)
 217                        wr = IACptr - buf;
 218                rc = safe_write(fd, buf, wr);
 219                if (rc != wr)
 220                        break;
 221                buf += rc;
 222                total += rc;
 223                count -= rc;
 224        }
 225        /* here: rc - result of last short write */
 226        if ((ssize_t)rc < 0) { /* error? */
 227                if (total == 0)
 228                        return rc;
 229                rc = 0;
 230        }
 231        return total + rc;
 232}
 233
 234/* Must match getopt32 string */
 235enum {
 236        OPT_WATCHCHILD = (1 << 2), /* -K */
 237        OPT_INETD      = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
 238        OPT_PORT       = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p PORT */
 239        OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
 240        OPT_SYSLOG     = (1 << 7) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -S */
 241        OPT_WAIT       = (1 << 8) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -w SEC */
 242};
 243
 244static struct tsession *
 245make_new_session(
 246                IF_FEATURE_TELNETD_STANDALONE(int sock)
 247                IF_NOT_FEATURE_TELNETD_STANDALONE(void)
 248) {
 249#if !ENABLE_FEATURE_TELNETD_STANDALONE
 250        enum { sock = 0 };
 251#endif
 252        const char *login_argv[2];
 253        struct termios termbuf;
 254        int fd, pid;
 255        char tty_name[GETPTY_BUFSIZE];
 256        struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
 257
 258        /*ts->buf1 = (char *)(ts + 1);*/
 259        /*ts->buf2 = ts->buf1 + BUFSIZE;*/
 260
 261        /* Got a new connection, set up a tty */
 262        fd = xgetpty(tty_name);
 263        if (fd > G.maxfd)
 264                G.maxfd = fd;
 265        ts->ptyfd = fd;
 266        ndelay_on(fd);
 267        close_on_exec_on(fd);
 268
 269        /* SO_KEEPALIVE by popular demand */
 270        setsockopt_keepalive(sock);
 271#if ENABLE_FEATURE_TELNETD_STANDALONE
 272        ts->sockfd_read = sock;
 273        ndelay_on(sock);
 274        if (sock == 0) { /* We are called with fd 0 - we are in inetd mode */
 275                sock++; /* so use fd 1 for output */
 276                ndelay_on(sock);
 277        }
 278        ts->sockfd_write = sock;
 279        if (sock > G.maxfd)
 280                G.maxfd = sock;
 281#else
 282        /* ts->sockfd_read = 0; - done by xzalloc */
 283        ts->sockfd_write = 1;
 284        ndelay_on(0);
 285        ndelay_on(1);
 286#endif
 287
 288        /* Make the telnet client understand we will echo characters so it
 289         * should not do it locally. We don't tell the client to run linemode,
 290         * because we want to handle line editing and tab completion and other
 291         * stuff that requires char-by-char support. */
 292        {
 293                static const char iacs_to_send[] ALIGN1 = {
 294                        IAC, DO, TELOPT_ECHO,
 295                        IAC, DO, TELOPT_NAWS,
 296                        /* This requires telnetd.ctrlSQ.patch (incomplete) */
 297                        /*IAC, DO, TELOPT_LFLOW,*/
 298                        IAC, WILL, TELOPT_ECHO,
 299                        IAC, WILL, TELOPT_SGA
 300                };
 301                /* This confuses iac_safe_write(), it will try to duplicate
 302                 * each IAC... */
 303                //memcpy(TS_BUF2(ts), iacs_to_send, sizeof(iacs_to_send));
 304                //ts->rdidx2 = sizeof(iacs_to_send);
 305                //ts->size2 = sizeof(iacs_to_send);
 306                /* So just stuff it into TCP stream! (no error check...) */
 307#if ENABLE_FEATURE_TELNETD_STANDALONE
 308                safe_write(sock, iacs_to_send, sizeof(iacs_to_send));
 309#else
 310                safe_write(1, iacs_to_send, sizeof(iacs_to_send));
 311#endif
 312                /*ts->rdidx2 = 0; - xzalloc did it */
 313                /*ts->size2 = 0;*/
 314        }
 315
 316        fflush_all();
 317        pid = vfork(); /* NOMMU-friendly */
 318        if (pid < 0) {
 319                free(ts);
 320                close(fd);
 321                /* sock will be closed by caller */
 322                bb_perror_msg("vfork");
 323                return NULL;
 324        }
 325        if (pid > 0) {
 326                /* Parent */
 327                ts->shell_pid = pid;
 328                return ts;
 329        }
 330
 331        /* Child */
 332        /* Careful - we are after vfork! */
 333
 334        /* Restore default signal handling ASAP */
 335        bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
 336
 337        pid = getpid();
 338
 339        if (ENABLE_FEATURE_UTMP) {
 340                len_and_sockaddr *lsa = get_peer_lsa(sock);
 341                char *hostname = NULL;
 342                if (lsa) {
 343                        hostname = xmalloc_sockaddr2dotted(&lsa->u.sa);
 344                        free(lsa);
 345                }
 346                write_new_utmp(pid, LOGIN_PROCESS, tty_name, /*username:*/ "LOGIN", hostname);
 347                free(hostname);
 348        }
 349
 350        /* Make new session and process group */
 351        setsid();
 352
 353        /* Open the child's side of the tty */
 354        /* NB: setsid() disconnects from any previous ctty's. Therefore
 355         * we must open child's side of the tty AFTER setsid! */
 356        close(0);
 357        xopen(tty_name, O_RDWR); /* becomes our ctty */
 358        xdup2(0, 1);
 359        xdup2(0, 2);
 360        tcsetpgrp(0, pid); /* switch this tty's process group to us */
 361
 362        /* The pseudo-terminal allocated to the client is configured to operate
 363         * in cooked mode, and with XTABS CRMOD enabled (see tty(4)) */
 364        tcgetattr(0, &termbuf);
 365        termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
 366        termbuf.c_oflag |= ONLCR | XTABS;
 367        termbuf.c_iflag |= ICRNL;
 368        termbuf.c_iflag &= ~IXOFF;
 369        /*termbuf.c_lflag &= ~ICANON;*/
 370        tcsetattr_stdin_TCSANOW(&termbuf);
 371
 372        /* Uses FILE-based I/O to stdout, but does fflush_all(),
 373         * so should be safe with vfork.
 374         * I fear, though, that some users will have ridiculously big
 375         * issue files, and they may block writing to fd 1,
 376         * (parent is supposed to read it, but parent waits
 377         * for vforked child to exec!) */
 378        print_login_issue(G.issuefile, tty_name);
 379
 380        /* Exec shell / login / whatever */
 381        login_argv[0] = G.loginpath;
 382        login_argv[1] = NULL;
 383        /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
 384         * exec external program.
 385         * NB: sock is either 0 or has CLOEXEC set on it.
 386         * fd has CLOEXEC set on it too. These two fds will be closed here.
 387         */
 388        BB_EXECVP(G.loginpath, (char **)login_argv);
 389        /* _exit is safer with vfork, and we shouldn't send message
 390         * to remote clients anyway */
 391        _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/
 392}
 393
 394#if ENABLE_FEATURE_TELNETD_STANDALONE
 395
 396static void
 397free_session(struct tsession *ts)
 398{
 399        struct tsession *t;
 400
 401        if (option_mask32 & OPT_INETD)
 402                exit(EXIT_SUCCESS);
 403
 404        /* Unlink this telnet session from the session list */
 405        t = G.sessions;
 406        if (t == ts)
 407                G.sessions = ts->next;
 408        else {
 409                while (t->next != ts)
 410                        t = t->next;
 411                t->next = ts->next;
 412        }
 413
 414#if 0
 415        /* It was said that "normal" telnetd just closes ptyfd,
 416         * doesn't send SIGKILL. When we close ptyfd,
 417         * kernel sends SIGHUP to processes having slave side opened. */
 418        kill(ts->shell_pid, SIGKILL);
 419        waitpid(ts->shell_pid, NULL, 0);
 420#endif
 421        close(ts->ptyfd);
 422        close(ts->sockfd_read);
 423        /* We do not need to close(ts->sockfd_write), it's the same
 424         * as sockfd_read unless we are in inetd mode. But in inetd mode
 425         * we do not reach this */
 426        free(ts);
 427
 428        /* Scan all sessions and find new maxfd */
 429        G.maxfd = 0;
 430        ts = G.sessions;
 431        while (ts) {
 432                if (G.maxfd < ts->ptyfd)
 433                        G.maxfd = ts->ptyfd;
 434                if (G.maxfd < ts->sockfd_read)
 435                        G.maxfd = ts->sockfd_read;
 436#if 0
 437                /* Again, sockfd_write == sockfd_read here */
 438                if (G.maxfd < ts->sockfd_write)
 439                        G.maxfd = ts->sockfd_write;
 440#endif
 441                ts = ts->next;
 442        }
 443}
 444
 445#else /* !FEATURE_TELNETD_STANDALONE */
 446
 447/* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
 448#define free_session(ts) return 0
 449
 450#endif
 451
 452static void handle_sigchld(int sig UNUSED_PARAM)
 453{
 454        pid_t pid;
 455        struct tsession *ts;
 456        int save_errno = errno;
 457
 458        /* Looping: more than one child may have exited */
 459        while (1) {
 460                pid = wait_any_nohang(NULL);
 461                if (pid <= 0)
 462                        break;
 463                ts = G.sessions;
 464                while (ts) {
 465                        if (ts->shell_pid == pid) {
 466                                ts->shell_pid = -1;
 467                                update_utmp_DEAD_PROCESS(pid);
 468                                break;
 469                        }
 470                        ts = ts->next;
 471                }
 472        }
 473
 474        errno = save_errno;
 475}
 476
 477int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 478int telnetd_main(int argc UNUSED_PARAM, char **argv)
 479{
 480        fd_set rdfdset, wrfdset;
 481        unsigned opt;
 482        int count;
 483        struct tsession *ts;
 484#if ENABLE_FEATURE_TELNETD_STANDALONE
 485#define IS_INETD (opt & OPT_INETD)
 486        int master_fd = master_fd; /* for compiler */
 487        int sec_linger = sec_linger;
 488        char *opt_bindaddr = NULL;
 489        char *opt_portnbr;
 490#else
 491        enum {
 492                IS_INETD = 1,
 493                master_fd = -1,
 494        };
 495#endif
 496        INIT_G();
 497
 498        /* -w NUM, and implies -F. -w and -i don't mix */
 499        IF_FEATURE_TELNETD_INETD_WAIT(opt_complementary = "wF:w+:i--w:w--i";)
 500        /* Even if !STANDALONE, we accept (and ignore) -i, thus people
 501         * don't need to guess whether it's ok to pass -i to us */
 502        opt = getopt32(argv, "f:l:Ki"
 503                        IF_FEATURE_TELNETD_STANDALONE("p:b:F")
 504                        IF_FEATURE_TELNETD_INETD_WAIT("Sw:"),
 505                        &G.issuefile, &G.loginpath
 506                        IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)
 507                        IF_FEATURE_TELNETD_INETD_WAIT(, &sec_linger)
 508        );
 509        if (!IS_INETD /*&& !re_execed*/) {
 510                /* inform that we start in standalone mode?
 511                 * May be useful when people forget to give -i */
 512                /*bb_error_msg("listening for connections");*/
 513                if (!(opt & OPT_FOREGROUND)) {
 514                        /* DAEMON_CHDIR_ROOT was giving inconsistent
 515                         * behavior with/without -F, -i */
 516                        bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
 517                }
 518        }
 519        /* Redirect log to syslog early, if needed */
 520        if (IS_INETD || (opt & OPT_SYSLOG) || !(opt & OPT_FOREGROUND)) {
 521                openlog(applet_name, LOG_PID, LOG_DAEMON);
 522                logmode = LOGMODE_SYSLOG;
 523        }
 524#if ENABLE_FEATURE_TELNETD_STANDALONE
 525        if (IS_INETD) {
 526                G.sessions = make_new_session(0);
 527                if (!G.sessions) /* pty opening or vfork problem, exit */
 528                        return 1; /* make_new_session printed error message */
 529        } else {
 530                master_fd = 0;
 531                if (!(opt & OPT_WAIT)) {
 532                        unsigned portnbr = 23;
 533                        if (opt & OPT_PORT)
 534                                portnbr = xatou16(opt_portnbr);
 535                        master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
 536                        xlisten(master_fd, 1);
 537                }
 538                close_on_exec_on(master_fd);
 539        }
 540#else
 541        G.sessions = make_new_session();
 542        if (!G.sessions) /* pty opening or vfork problem, exit */
 543                return 1; /* make_new_session printed error message */
 544#endif
 545
 546        /* We don't want to die if just one session is broken */
 547        signal(SIGPIPE, SIG_IGN);
 548
 549        if (opt & OPT_WATCHCHILD)
 550                signal(SIGCHLD, handle_sigchld);
 551        else /* prevent dead children from becoming zombies */
 552                signal(SIGCHLD, SIG_IGN);
 553
 554/*
 555   This is how the buffers are used. The arrows indicate data flow.
 556
 557   +-------+     wridx1++     +------+     rdidx1++     +----------+
 558   |       | <--------------  | buf1 | <--------------  |          |
 559   |       |     size1--      +------+     size1++      |          |
 560   |  pty  |                                            |  socket  |
 561   |       |     rdidx2++     +------+     wridx2++     |          |
 562   |       |  --------------> | buf2 |  --------------> |          |
 563   +-------+     size2++      +------+     size2--      +----------+
 564
 565   size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
 566   size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
 567
 568   Each session has got two buffers. Buffers are circular. If sizeN == 0,
 569   buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
 570   rdidxN == wridxN.
 571*/
 572 again:
 573        FD_ZERO(&rdfdset);
 574        FD_ZERO(&wrfdset);
 575
 576        /* Select on the master socket, all telnet sockets and their
 577         * ptys if there is room in their session buffers.
 578         * NB: scalability problem: we recalculate entire bitmap
 579         * before each select. Can be a problem with 500+ connections. */
 580        ts = G.sessions;
 581        while (ts) {
 582                struct tsession *next = ts->next; /* in case we free ts */
 583                if (ts->shell_pid == -1) {
 584                        /* Child died and we detected that */
 585                        free_session(ts);
 586                } else {
 587                        if (ts->size1 > 0)       /* can write to pty */
 588                                FD_SET(ts->ptyfd, &wrfdset);
 589                        if (ts->size1 < BUFSIZE) /* can read from socket */
 590                                FD_SET(ts->sockfd_read, &rdfdset);
 591                        if (ts->size2 > 0)       /* can write to socket */
 592                                FD_SET(ts->sockfd_write, &wrfdset);
 593                        if (ts->size2 < BUFSIZE) /* can read from pty */
 594                                FD_SET(ts->ptyfd, &rdfdset);
 595                }
 596                ts = next;
 597        }
 598        if (!IS_INETD) {
 599                FD_SET(master_fd, &rdfdset);
 600                /* This is needed because free_session() does not
 601                 * take master_fd into account when it finds new
 602                 * maxfd among remaining fd's */
 603                if (master_fd > G.maxfd)
 604                        G.maxfd = master_fd;
 605        }
 606
 607        {
 608                struct timeval *tv_ptr = NULL;
 609#if ENABLE_FEATURE_TELNETD_INETD_WAIT
 610                struct timeval tv;
 611                if ((opt & OPT_WAIT) && !G.sessions) {
 612                        tv.tv_sec = sec_linger;
 613                        tv.tv_usec = 0;
 614                        tv_ptr = &tv;
 615                }
 616#endif
 617                count = select(G.maxfd + 1, &rdfdset, &wrfdset, NULL, tv_ptr);
 618        }
 619        if (count == 0) /* "telnetd -w SEC" timed out */
 620                return 0;
 621        if (count < 0)
 622                goto again; /* EINTR or ENOMEM */
 623
 624#if ENABLE_FEATURE_TELNETD_STANDALONE
 625        /* Check for and accept new sessions */
 626        if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
 627                int fd;
 628                struct tsession *new_ts;
 629
 630                fd = accept(master_fd, NULL, NULL);
 631                if (fd < 0)
 632                        goto again;
 633                close_on_exec_on(fd);
 634
 635                /* Create a new session and link it into active list */
 636                new_ts = make_new_session(fd);
 637                if (new_ts) {
 638                        new_ts->next = G.sessions;
 639                        G.sessions = new_ts;
 640                } else {
 641                        close(fd);
 642                }
 643        }
 644#endif
 645
 646        /* Then check for data tunneling */
 647        ts = G.sessions;
 648        while (ts) { /* For all sessions... */
 649                struct tsession *next = ts->next; /* in case we free ts */
 650
 651                if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
 652                        int num_totty;
 653                        unsigned char *ptr;
 654                        /* Write to pty from buffer 1 */
 655                        ptr = remove_iacs(ts, &num_totty);
 656                        count = safe_write(ts->ptyfd, ptr, num_totty);
 657                        if (count < 0) {
 658                                if (errno == EAGAIN)
 659                                        goto skip1;
 660                                goto kill_session;
 661                        }
 662                        ts->size1 -= count;
 663                        ts->wridx1 += count;
 664                        if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
 665                                ts->wridx1 = 0;
 666                }
 667 skip1:
 668                if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
 669                        /* Write to socket from buffer 2 */
 670                        count = MIN(BUFSIZE - ts->wridx2, ts->size2);
 671                        count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count);
 672                        if (count < 0) {
 673                                if (errno == EAGAIN)
 674                                        goto skip2;
 675                                goto kill_session;
 676                        }
 677                        ts->size2 -= count;
 678                        ts->wridx2 += count;
 679                        if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
 680                                ts->wridx2 = 0;
 681                }
 682 skip2:
 683                /* Should not be needed, but... remove_iacs is actually buggy
 684                 * (it cannot process iacs which wrap around buffer's end)!
 685                 * Since properly fixing it requires writing bigger code,
 686                 * we rely instead on this code making it virtually impossible
 687                 * to have wrapped iac (people don't type at 2k/second).
 688                 * It also allows for bigger reads in common case. */
 689                if (ts->size1 == 0) {
 690                        ts->rdidx1 = 0;
 691                        ts->wridx1 = 0;
 692                }
 693                if (ts->size2 == 0) {
 694                        ts->rdidx2 = 0;
 695                        ts->wridx2 = 0;
 696                }
 697
 698                if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
 699                        /* Read from socket to buffer 1 */
 700                        count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
 701                        count = safe_read(ts->sockfd_read, TS_BUF1(ts) + ts->rdidx1, count);
 702                        if (count <= 0) {
 703                                if (count < 0 && errno == EAGAIN)
 704                                        goto skip3;
 705                                goto kill_session;
 706                        }
 707                        /* Ignore trailing NUL if it is there */
 708                        if (!TS_BUF1(ts)[ts->rdidx1 + count - 1]) {
 709                                --count;
 710                        }
 711                        ts->size1 += count;
 712                        ts->rdidx1 += count;
 713                        if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
 714                                ts->rdidx1 = 0;
 715                }
 716 skip3:
 717                if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
 718                        /* Read from pty to buffer 2 */
 719                        count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
 720                        count = safe_read(ts->ptyfd, TS_BUF2(ts) + ts->rdidx2, count);
 721                        if (count <= 0) {
 722                                if (count < 0 && errno == EAGAIN)
 723                                        goto skip4;
 724                                goto kill_session;
 725                        }
 726                        ts->size2 += count;
 727                        ts->rdidx2 += count;
 728                        if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
 729                                ts->rdidx2 = 0;
 730                }
 731 skip4:
 732                ts = next;
 733                continue;
 734 kill_session:
 735                if (ts->shell_pid > 0)
 736                        update_utmp_DEAD_PROCESS(ts->shell_pid);
 737                free_session(ts);
 738                ts = next;
 739        }
 740
 741        goto again;
 742}
 743