ifupdown: improve compatibility with Debian
[oweals/busybox.git] / networking / telnetd.c
index 163efaa4213f5f6406838319ca280f8930ce1fdb..33020f1b416436d6ad1315459704637401ca4a40 100644 (file)
@@ -3,7 +3,7 @@
  * Simple telnet server
  * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
  *
  * Simple telnet server
  * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
  *
- * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  *
  * ---------------------------------------------------------------------------
  * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
  *
  * ---------------------------------------------------------------------------
  * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
  * Set process group corrections, initial busybox port
  */
 
  * Set process group corrections, initial busybox port
  */
 
+//usage:#define telnetd_trivial_usage
+//usage:       "[OPTIONS]"
+//usage:#define telnetd_full_usage "\n\n"
+//usage:       "Handle incoming telnet connections"
+//usage:       IF_NOT_FEATURE_TELNETD_STANDALONE(" via inetd") "\n"
+//usage:     "\n       -l LOGIN        Exec LOGIN on connect"
+//usage:     "\n       -f ISSUE_FILE   Display ISSUE_FILE instead of /etc/issue"
+//usage:     "\n       -K              Close connection as soon as login exits"
+//usage:     "\n                       (normally wait until all programs close slave pty)"
+//usage:       IF_FEATURE_TELNETD_STANDALONE(
+//usage:     "\n       -p PORT         Port to listen on"
+//usage:     "\n       -b ADDR[:PORT]  Address to bind to"
+//usage:     "\n       -F              Run in foreground"
+//usage:     "\n       -i              Inetd mode"
+//usage:       IF_FEATURE_TELNETD_INETD_WAIT(
+//usage:     "\n       -w SEC          Inetd 'wait' mode, linger time SEC"
+//usage:     "\n       -S              Log to syslog (implied by -i or without -F and -w)"
+//usage:       )
+//usage:       )
+
 #define DEBUG 0
 
 #include "libbb.h"
 #include <syslog.h>
 
 #if DEBUG
 #define DEBUG 0
 
 #include "libbb.h"
 #include <syslog.h>
 
 #if DEBUG
-#define TELCMDS
-#define TELOPTS
+# define TELCMDS
+# define TELOPTS
 #endif
 #include <arpa/telnet.h>
 
 #endif
 #include <arpa/telnet.h>
 
+
 struct tsession {
        struct tsession *next;
        pid_t shell_pid;
 struct tsession {
        struct tsession *next;
        pid_t shell_pid;
@@ -60,7 +81,7 @@ struct globals {
        const char *loginpath;
        const char *issuefile;
        int maxfd;
        const char *loginpath;
        const char *issuefile;
        int maxfd;
-};
+} FIX_ALIASING;
 #define G (*(struct globals*)&bb_common_bufsiz1)
 #define INIT_G() do { \
        G.loginpath = "/bin/login"; \
 #define G (*(struct globals*)&bb_common_bufsiz1)
 #define INIT_G() do { \
        G.loginpath = "/bin/login"; \
@@ -74,11 +95,11 @@ struct globals {
    string of characters fit for the terminal.  Do this by packing
    all characters meant for the terminal sequentially towards the end of buf.
 
    string of characters fit for the terminal.  Do this by packing
    all characters meant for the terminal sequentially towards the end of buf.
 
-   Return a pointer to the beginning of the characters meant for the terminal.
+   Return a pointer to the beginning of the characters meant for the terminal
    and make *num_totty the number of characters that should be sent to
    the terminal.
 
    and make *num_totty the number of characters that should be sent to
    the terminal.
 
-   Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
+   Note - if an IAC (3 byte quantity) starts before (bf + len) but extends
    past (bf + len) then that IAC will be left unprocessed and *processed
    will be less than len.
 
    past (bf + len) then that IAC will be left unprocessed and *processed
    will be less than len.
 
@@ -137,7 +158,7 @@ remove_iacs(struct tsession *ts, int *pnum_totty)
                if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
                        struct winsize ws;
                        if ((ptr+8) >= end)
                if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
                        struct winsize ws;
                        if ((ptr+8) >= end)
-                               break;  /* incomplete, can't process */
+                               break;  /* incomplete, can't process */
                        ws.ws_col = (ptr[3] << 8) | ptr[4];
                        ws.ws_row = (ptr[5] << 8) | ptr[6];
                        ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
                        ws.ws_col = (ptr[3] << 8) | ptr[4];
                        ws.ws_row = (ptr[5] << 8) | ptr[6];
                        ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
@@ -222,6 +243,9 @@ make_new_session(
                IF_FEATURE_TELNETD_STANDALONE(int sock)
                IF_NOT_FEATURE_TELNETD_STANDALONE(void)
 ) {
                IF_FEATURE_TELNETD_STANDALONE(int sock)
                IF_NOT_FEATURE_TELNETD_STANDALONE(void)
 ) {
+#if !ENABLE_FEATURE_TELNETD_STANDALONE
+       enum { sock = 0 };
+#endif
        const char *login_argv[2];
        struct termios termbuf;
        int fd, pid;
        const char *login_argv[2];
        struct termios termbuf;
        int fd, pid;
@@ -239,9 +263,9 @@ make_new_session(
        ndelay_on(fd);
        close_on_exec_on(fd);
 
        ndelay_on(fd);
        close_on_exec_on(fd);
 
-#if ENABLE_FEATURE_TELNETD_STANDALONE
        /* SO_KEEPALIVE by popular demand */
        setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
        /* SO_KEEPALIVE by popular demand */
        setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+#if ENABLE_FEATURE_TELNETD_STANDALONE
        ts->sockfd_read = sock;
        ndelay_on(sock);
        if (sock == 0) { /* We are called with fd 0 - we are in inetd mode */
        ts->sockfd_read = sock;
        ndelay_on(sock);
        if (sock == 0) { /* We are called with fd 0 - we are in inetd mode */
@@ -252,8 +276,6 @@ make_new_session(
        if (sock > G.maxfd)
                G.maxfd = sock;
 #else
        if (sock > G.maxfd)
                G.maxfd = sock;
 #else
-       /* SO_KEEPALIVE by popular demand */
-       setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
        /* ts->sockfd_read = 0; - done by xzalloc */
        ts->sockfd_write = 1;
        ndelay_on(0);
        /* ts->sockfd_read = 0; - done by xzalloc */
        ts->sockfd_write = 1;
        ndelay_on(0);
@@ -268,8 +290,8 @@ make_new_session(
                static const char iacs_to_send[] ALIGN1 = {
                        IAC, DO, TELOPT_ECHO,
                        IAC, DO, TELOPT_NAWS,
                static const char iacs_to_send[] ALIGN1 = {
                        IAC, DO, TELOPT_ECHO,
                        IAC, DO, TELOPT_NAWS,
-               /* This requires telnetd.ctrlSQ.patch (incomplete) */
-               /*      IAC, DO, TELOPT_LFLOW, */
+                       /* This requires telnetd.ctrlSQ.patch (incomplete) */
+                       /*IAC, DO, TELOPT_LFLOW,*/
                        IAC, WILL, TELOPT_ECHO,
                        IAC, WILL, TELOPT_SGA
                };
                        IAC, WILL, TELOPT_ECHO,
                        IAC, WILL, TELOPT_SGA
                };
@@ -288,7 +310,7 @@ make_new_session(
                /*ts->size2 = 0;*/
        }
 
                /*ts->size2 = 0;*/
        }
 
-       fflush(NULL); /* flush all streams */
+       fflush_all();
        pid = vfork(); /* NOMMU-friendly */
        if (pid < 0) {
                free(ts);
        pid = vfork(); /* NOMMU-friendly */
        if (pid < 0) {
                free(ts);
@@ -309,6 +331,19 @@ make_new_session(
        /* Restore default signal handling ASAP */
        bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
 
        /* Restore default signal handling ASAP */
        bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
 
+       pid = getpid();
+
+       if (ENABLE_FEATURE_UTMP) {
+               len_and_sockaddr *lsa = get_peer_lsa(sock);
+               char *hostname = NULL;
+               if (lsa) {
+                       hostname = xmalloc_sockaddr2dotted(&lsa->u.sa);
+                       free(lsa);
+               }
+               write_new_utmp(pid, LOGIN_PROCESS, tty_name, /*username:*/ "LOGIN", hostname);
+               free(hostname);
+       }
+
        /* Make new session and process group */
        setsid();
 
        /* Make new session and process group */
        setsid();
 
@@ -319,7 +354,7 @@ make_new_session(
        xopen(tty_name, O_RDWR); /* becomes our ctty */
        xdup2(0, 1);
        xdup2(0, 2);
        xopen(tty_name, O_RDWR); /* becomes our ctty */
        xdup2(0, 1);
        xdup2(0, 2);
-       tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
+       tcsetpgrp(0, pid); /* switch this tty's process group to us */
 
        /* The pseudo-terminal allocated to the client is configured to operate
         * in cooked mode, and with XTABS CRMOD enabled (see tty(4)) */
 
        /* The pseudo-terminal allocated to the client is configured to operate
         * in cooked mode, and with XTABS CRMOD enabled (see tty(4)) */
@@ -331,7 +366,7 @@ make_new_session(
        /*termbuf.c_lflag &= ~ICANON;*/
        tcsetattr_stdin_TCSANOW(&termbuf);
 
        /*termbuf.c_lflag &= ~ICANON;*/
        tcsetattr_stdin_TCSANOW(&termbuf);
 
-       /* Uses FILE-based I/O to stdout, but does fflush(stdout),
+       /* Uses FILE-based I/O to stdout, but does fflush_all(),
         * so should be safe with vfork.
         * I fear, though, that some users will have ridiculously big
         * issue files, and they may block writing to fd 1,
         * so should be safe with vfork.
         * I fear, though, that some users will have ridiculously big
         * issue files, and they may block writing to fd 1,
@@ -358,12 +393,13 @@ make_new_session(
 static void
 free_session(struct tsession *ts)
 {
 static void
 free_session(struct tsession *ts)
 {
-       struct tsession *t = G.sessions;
+       struct tsession *t;
 
        if (option_mask32 & OPT_INETD)
                exit(EXIT_SUCCESS);
 
        /* Unlink this telnet session from the session list */
 
        if (option_mask32 & OPT_INETD)
                exit(EXIT_SUCCESS);
 
        /* Unlink this telnet session from the session list */
+       t = G.sessions;
        if (t == ts)
                G.sessions = ts->next;
        else {
        if (t == ts)
                G.sessions = ts->next;
        else {
@@ -414,6 +450,7 @@ static void handle_sigchld(int sig UNUSED_PARAM)
 {
        pid_t pid;
        struct tsession *ts;
 {
        pid_t pid;
        struct tsession *ts;
+       int save_errno = errno;
 
        /* Looping: more than one child may have exited */
        while (1) {
 
        /* Looping: more than one child may have exited */
        while (1) {
@@ -424,11 +461,22 @@ static void handle_sigchld(int sig UNUSED_PARAM)
                while (ts) {
                        if (ts->shell_pid == pid) {
                                ts->shell_pid = -1;
                while (ts) {
                        if (ts->shell_pid == pid) {
                                ts->shell_pid = -1;
+// man utmp:
+// When init(8) finds that a process has exited, it locates its utmp entry
+// by ut_pid, sets ut_type to DEAD_PROCESS, and clears ut_user, ut_host
+// and ut_time with null bytes.
+// [same applies to other processes which maintain utmp entries, like telnetd]
+//
+// We do not bother actually clearing fields:
+// it might be interesting to know who was logged in and from where
+                               update_utmp(pid, DEAD_PROCESS, /*tty_name:*/ NULL, /*username:*/ NULL, /*hostname:*/ NULL);
                                break;
                        }
                        ts = ts->next;
                }
        }
                                break;
                        }
                        ts = ts->next;
                }
        }
+
+       errno = save_errno;
 }
 
 int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 }
 
 int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
@@ -689,6 +737,8 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv)
                ts = next;
                continue;
  kill_session:
                ts = next;
                continue;
  kill_session:
+               if (ts->shell_pid > 0)
+                       update_utmp(ts->shell_pid, DEAD_PROCESS, /*tty_name:*/ NULL, /*username:*/ NULL, /*hostname:*/ NULL);
                free_session(ts);
                ts = next;
        }
                free_session(ts);
                ts = next;
        }