Ignore:
Timestamp:
Feb 25, 2011, 9:26:54 PM (13 years ago)
Author:
Bruno Cornec
Message:
  • Update mindi-busybox to 1.18.3 to avoid problems with the tar command which is now failing on recent versions with busybox 1.7.3
File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/2.2.9/mindi-busybox/networking/telnetd.c

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