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