#if HAVE_AFINET6 -> #ifdef HAVE_AFINET6
[oweals/busybox.git] / networking / telnetd.c
index edc018a2a03a37c889c3a695620fd04087c7f262..da7911fcc88156807f4ff5a5c0e61d02006b70e2 100644 (file)
@@ -1,10 +1,9 @@
-/* $Id: telnetd.c,v 1.1 2002/09/30 20:52:04 andersen Exp $
- *
+/* vi: set sw=4 ts=4: */
+/*
  * Simple telnet server
  * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
  *
- * This file is distributed under the Gnu Public License (GPL),
- * please see the file LICENSE for further information.
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
  *
  * ---------------------------------------------------------------------------
  * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
  */
 
 /*#define DEBUG 1 */
+#undef DEBUG
+
+#include "busybox.h"
 
-#include <sys/time.h>
-#include <sys/socket.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <netinet/in.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <signal.h>
-#include <termios.h>
 #ifdef DEBUG
 #define TELCMDS
 #define TELOPTS
 #endif
 #include <arpa/telnet.h>
-#include <ctype.h>
 #include <sys/syslog.h>
 
-#include "busybox.h"
 
 #define BUFSIZE 4000
 
-static const char *loginpath = "/bin/sh";
+#ifdef CONFIG_FEATURE_IPV6
+#define SOCKET_TYPE    AF_INET6
+typedef struct sockaddr_in6 sockaddr_type;
+#else
+#define SOCKET_TYPE    AF_INET
+typedef struct sockaddr_in sockaddr_type;
+#endif
+
+
+#ifdef CONFIG_LOGIN
+static const char *loginpath = "/bin/login";
+#else
+static const char *loginpath;
+#endif
+static const char *issuefile = "/etc/issue.net";
 
 /* shell name and arguments */
 
@@ -57,8 +59,12 @@ static const char *argv_init[] = {NULL, NULL};
 /* structure that describes a session */
 
 struct tsession {
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+       int sockfd_read, sockfd_write, ptyfd;
+#else /* CONFIG_FEATURE_TELNETD_INETD */
        struct tsession *next;
        int sockfd, ptyfd;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
        int shell_pid;
        /* two circular buffers */
        char *buf1, *buf2;
@@ -90,7 +96,7 @@ static struct tsession *sessions;
 
 /*
 
-   Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored
+   Remove all IAC's from the buffer pointed to by bf (received IACs are ignored
    and must be removed so as to not be interpreted by the terminal).  Make an
    uninterrupted string of characters fit for the terminal.  Do this by packing
    all characters meant for the terminal sequentially towards the end of bf.
@@ -106,10 +112,12 @@ static struct tsession *sessions;
    FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
    what is the escape character?  We aren't handling that situation here.
 
+   CR-LF ->'s CR mapping is also done here, for convenience
+
   */
 static char *
 remove_iacs(struct tsession *ts, int *pnum_totty) {
-       unsigned char *ptr0 = ts->buf1 + ts->wridx1;
+       unsigned char *ptr0 = (unsigned char *)ts->buf1 + ts->wridx1;
        unsigned char *ptr = ptr0;
        unsigned char *totty = ptr;
        unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
@@ -118,23 +126,46 @@ remove_iacs(struct tsession *ts, int *pnum_totty) {
 
        while (ptr < end) {
                if (*ptr != IAC) {
+                       int c = *ptr;
                        *totty++ = *ptr++;
+                       /* We now map \r\n ==> \r for pragmatic reasons.
+                        * Many client implementations send \r\n when
+                        * the user hits the CarriageReturn key.
+                        */
+                       if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end)
+                               ptr++;
                }
                else {
-                       if ((ptr+2) < end) {
-                       /* the entire IAC is contained in the buffer
-                       we were asked to process. */
-#ifdef DEBUG
-                               fprintf(stderr, "Ignoring IAC %s,%s\n",
-                                   *ptr, TELCMD(*(ptr+1)), TELOPT(*(ptr+2)));
-#endif
-                               ptr += 3;
-                       } else {
+                       /*
+                        * TELOPT_NAWS support!
+                        */
+                       if ((ptr+2) >= end) {
                                /* only the beginning of the IAC is in the
                                buffer we were asked to process, we can't
                                process this char. */
                                break;
                        }
+
+                       /*
+                        * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
+                        */
+                       else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
+                               struct winsize ws;
+                               if ((ptr+8) >= end)
+                                       break;  /* incomplete, can't process */
+                               ws.ws_col = (ptr[3] << 8) | ptr[4];
+                               ws.ws_row = (ptr[5] << 8) | ptr[6];
+                               (void) ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
+                               ptr += 9;
+                       }
+                       else {
+                               /* skip 3-byte IAC non-SB cmd */
+#ifdef DEBUG
+                               fprintf(stderr, "Ignoring IAC %s,%s\n",
+                                       TELCMD(*(ptr+1)), TELOPT(*(ptr+2)));
+#endif
+                               ptr += 3;
+                       }
                }
        }
 
@@ -156,13 +187,19 @@ static int
 getpty(char *line)
 {
        int p;
-#ifdef HAVE_DEVPTS_FS
-       p = open("/dev/ptmx", 2);
+#ifdef CONFIG_FEATURE_DEVPTS
+       p = open("/dev/ptmx", O_RDWR);
        if (p > 0) {
+               const char *name;
                grantpt(p);
                unlockpt(p);
-               strcpy(line, ptsname(p));
-               return(p);
+               name = ptsname(p);
+               if (!name) {
+                       bb_perror_msg("ptsname error (is /dev/pts mounted?)");
+                       return -1;
+               }
+               strcpy(line, name);
+               return p;
        }
 #else
        struct stat stb;
@@ -179,13 +216,17 @@ getpty(char *line)
                }
                for (j = 0; j < 16; j++) {
                        line[9] = j < 10 ? j + '0' : j - 10 + 'a';
-                       if ((p = open(line, O_RDWR | O_NOCTTY)) >= 0) {
+#ifdef DEBUG
+                       fprintf(stderr, "Trying to open device: %s\n", line);
+#endif
+                       p = open(line, O_RDWR | O_NOCTTY);
+                       if (p >= 0) {
                                line[5] = 't';
                                return p;
                        }
                }
        }
-#endif /* HAVE_DEVPTS_FS */
+#endif /* CONFIG_FEATURE_DEVPTS */
        return -1;
 }
 
@@ -204,27 +245,32 @@ send_iac(struct tsession *ts, unsigned char command, int option)
 
 
 static struct tsession *
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+make_new_session(void)
+#else /* CONFIG_FEATURE_TELNETD_INETD */
 make_new_session(int sockfd)
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 {
        struct termios termbuf;
        int pty, pid;
        char tty_name[32];
-       struct tsession *ts = malloc(sizeof(struct tsession) + BUFSIZE * 2);
+       struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
 
        ts->buf1 = (char *)(&ts[1]);
        ts->buf2 = ts->buf1 + BUFSIZE;
 
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+       ts->sockfd_write = 1;
+#else /* CONFIG_FEATURE_TELNETD_INETD */
        ts->sockfd = sockfd;
-
-       ts->rdidx1 = ts->wridx1 = ts->size1 = 0;
-       ts->rdidx2 = ts->wridx2 = ts->size2 = 0;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
        /* Got a new connection, set up a tty and spawn a shell.  */
 
        pty = getpty(tty_name);
 
        if (pty < 0) {
-               syslog_msg(LOG_USER, LOG_ERR, "All network ports in use!");
+               bb_error_msg("all terminals in use");
                return 0;
        }
 
@@ -240,13 +286,13 @@ make_new_session(int sockfd)
         */
 
        send_iac(ts, DO, TELOPT_ECHO);
+       send_iac(ts, DO, TELOPT_NAWS);
        send_iac(ts, DO, TELOPT_LFLOW);
        send_iac(ts, WILL, TELOPT_ECHO);
        send_iac(ts, WILL, TELOPT_SGA);
 
-
        if ((pid = fork()) < 0) {
-               syslog_msg(LOG_USER, LOG_ERR, "Can`t forking");
+               bb_perror_msg("fork");
        }
        if (pid == 0) {
                /* In child, open the child's side of the tty.  */
@@ -257,10 +303,7 @@ make_new_session(int sockfd)
                /* make new process group */
                setsid();
 
-               if (open(tty_name, O_RDWR /*| O_NOCTTY*/) < 0) {
-                       syslog_msg(LOG_USER, LOG_ERR, "Could not open tty");
-                       exit(1);
-                       }
+               xopen(tty_name, O_RDWR /*| O_NOCTTY*/);
                dup(0);
                dup(0);
 
@@ -278,12 +321,13 @@ make_new_session(int sockfd)
                /*termbuf.c_lflag &= ~ICANON;*/
                tcsetattr(0, TCSANOW, &termbuf);
 
+               print_login_issue(issuefile, NULL);
+
                /* exec shell, with correct argv and env */
                execv(loginpath, (char *const *)argv_init);
 
                /* NOT REACHED */
-               syslog_msg(LOG_USER, LOG_ERR, "execv error");
-               exit(1);
+               bb_perror_msg_and_die("execv");
        }
 
        ts->shell_pid = pid;
@@ -291,13 +335,14 @@ make_new_session(int sockfd)
        return ts;
 }
 
+#ifndef CONFIG_FEATURE_TELNETD_INETD
 static void
 free_session(struct tsession *ts)
 {
        struct tsession *t = sessions;
 
        /* Unlink this telnet session from the session list.  */
-       if(t == ts)
+       if (t == ts)
                sessions = ts->next;
        else {
                while(t->next != ts)
@@ -312,78 +357,92 @@ free_session(struct tsession *ts)
        close(ts->ptyfd);
        close(ts->sockfd);
 
-       if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
+       if (ts->ptyfd == maxfd || ts->sockfd == maxfd)
                maxfd--;
-       if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
+       if (ts->ptyfd == maxfd || ts->sockfd == maxfd)
                maxfd--;
 
        free(ts);
 }
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
 int
 telnetd_main(int argc, char **argv)
 {
-       struct sockaddr_in sa;
-       int master_fd;
+       unsigned opt;
        fd_set rdfdset, wrfdset;
        int selret;
+#ifndef CONFIG_FEATURE_TELNETD_INETD
+       sockaddr_type sa;
+       int master_fd;
        int on = 1;
-       int portnbr = 23;
-       int c;
-
-       /* check if user supplied a port number */
+       unsigned portnbr = 23;
+       struct in_addr bind_addr = { .s_addr = 0x0 };
+       char *opt_portnbr, *opt_bindaddr;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
+       int maxlen, w, r;
+
+#ifndef CONFIG_LOGIN
+       loginpath = DEFAULT_SHELL;
+#endif
 
-       for (;;) {
-               c = getopt( argc, argv, "p:l:");
-               if (c == EOF) break;
-               switch (c) {
-                       case 'p':
-                               portnbr = atoi(optarg);
-                               break;
-                       case 'l':
-                               loginpath = strdup (optarg);
-                               break;
-                       default:
-                               show_usage();
-               }
-       }
+       /* We use inetd-style operation unconditionally
+        * (no --foreground option), user most likely will
+        * look into syslog for all errors, even early ones.
+        * Direct all output to syslog at once.
+        */
+       openlog(applet_name, 0, LOG_USER);
+       logmode = LOGMODE_SYSLOG;
+
+       opt = getopt32(argc, argv, "f:l:" SKIP_FEATURE_TELNETD_INETD("p:b:"),
+                       &issuefile, &loginpath
+                       SKIP_FEATURE_TELNETD_INETD(, &opt_portnbr, &opt_bindaddr));
+       //if (opt & 1) // -f
+       //if (opt & 2) // -l
+#ifndef CONFIG_FEATURE_TELNETD_INETD
+       if (opt & 4) portnbr = xatou16(opt_portnbr); // -p
+       if (opt & 8) // -b
+               if (inet_aton(opt_bindaddr, &bind_addr) == 0) bb_show_usage();
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
        if (access(loginpath, X_OK) < 0) {
-               error_msg_and_die ("'%s' unavailable.", loginpath);
+               bb_error_msg_and_die("'%s' unavailable", loginpath);
        }
 
        argv_init[0] = loginpath;
+
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+       maxfd = 1;
+       sessions = make_new_session();
+#else /* CONFIG_EATURE_TELNETD_INETD */
        sessions = 0;
 
        /* Grab a TCP socket.  */
 
-       master_fd = socket(AF_INET, SOCK_STREAM, 0);
-       if (master_fd < 0) {
-               perror_msg_and_die("socket");
-       }
+       master_fd = xsocket(SOCKET_TYPE, SOCK_STREAM, 0);
        (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
 
        /* Set it to listen to specified port.  */
 
        memset((void *)&sa, 0, sizeof(sa));
+#ifdef CONFIG_FEATURE_IPV6
+       sa.sin6_family = AF_INET6;
+       sa.sin6_port = htons(portnbr);
+       /* sa.sin6_addr = bind_addr6; */
+#else
        sa.sin_family = AF_INET;
        sa.sin_port = htons(portnbr);
+       sa.sin_addr = bind_addr;
+#endif
 
-       if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
-               perror_msg_and_die("bind");
-       }
-
-       if (listen(master_fd, 1) < 0) {
-               perror_msg_and_die("listen");
-       }
-
-       if (daemon(0, 0) < 0)
-               perror_msg_and_die("daemon");
-
+       xbind(master_fd, (struct sockaddr *) &sa, sizeof(sa));
+       xlisten(master_fd, 1);
+       xdaemon(0, 0);
 
        maxfd = master_fd;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
-       do {
+       while(1) {
                struct tsession *ts;
 
                FD_ZERO(&rdfdset);
@@ -393,10 +452,14 @@ telnetd_main(int argc, char **argv)
                 * ptys if there is room in their respective session buffers.
                 */
 
+#ifndef CONFIG_FEATURE_TELNETD_INETD
                FD_SET(master_fd, &rdfdset);
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
                ts = sessions;
+#ifndef CONFIG_FEATURE_TELNETD_INETD
                while (ts) {
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                        /* buf1 is used from socket to pty
                         * buf2 is used from pty to socket
                         */
@@ -404,29 +467,41 @@ telnetd_main(int argc, char **argv)
                                FD_SET(ts->ptyfd, &wrfdset);  /* can write to pty */
                        }
                        if (ts->size1 < BUFSIZE) {
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+                               FD_SET(ts->sockfd_read, &rdfdset); /* can read from socket */
+#else /* CONFIG_FEATURE_TELNETD_INETD */
                                FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                        }
                        if (ts->size2 > 0) {
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+                               FD_SET(ts->sockfd_write, &wrfdset); /* can write to socket */
+#else /* CONFIG_FEATURE_TELNETD_INETD */
                                FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                        }
                        if (ts->size2 < BUFSIZE) {
                                FD_SET(ts->ptyfd, &rdfdset);  /* can read from pty */
                        }
+#ifndef CONFIG_FEATURE_TELNETD_INETD
                        ts = ts->next;
                }
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
                selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
 
                if (!selret)
                        break;
 
+#ifndef CONFIG_FEATURE_TELNETD_INETD
                /* First check for and accept new sessions.  */
                if (FD_ISSET(master_fd, &rdfdset)) {
-                       int fd, salen;
+                       int fd;
+                       socklen_t salen;
 
                        salen = sizeof(sa);
-                       if ((fd = accept(master_fd, (struct sockaddr *)&sa,
-                                               &salen)) < 0) {
+                       fd = accept(master_fd, (struct sockaddr *)&sa, &salen);
+                       if (fd < 0) {
                                continue;
                        } else {
                                /* Create a new session and link it into
@@ -447,8 +522,10 @@ telnetd_main(int argc, char **argv)
 
                ts = sessions;
                while (ts) { /* For all sessions...  */
-                       int maxlen, w, r;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
+#ifndef CONFIG_FEATURE_TELNETD_INETD
                        struct tsession *next = ts->next; /* in case we free ts. */
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
                        if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
                                int num_totty;
@@ -459,9 +536,13 @@ telnetd_main(int argc, char **argv)
 
                                w = write(ts->ptyfd, ptr, num_totty);
                                if (w < 0) {
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+                                       exit(0);
+#else /* CONFIG_FEATURE_TELNETD_INETD */
                                        free_session(ts);
                                        ts = next;
                                        continue;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                                }
                                ts->wridx1 += w;
                                ts->size1 -= w;
@@ -469,34 +550,54 @@ telnetd_main(int argc, char **argv)
                                        ts->wridx1 = 0;
                        }
 
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+                       if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) {
+#else /* CONFIG_FEATURE_TELNETD_INETD */
                        if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                                /* Write to socket from buffer 2.  */
                                maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+                               w = write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen);
+                               if (w < 0)
+                                       exit(0);
+#else /* CONFIG_FEATURE_TELNETD_INETD */
                                w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
                                if (w < 0) {
                                        free_session(ts);
                                        ts = next;
                                        continue;
                                }
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                                ts->wridx2 += w;
                                ts->size2 -= w;
                                if (ts->wridx2 == BUFSIZE)
                                        ts->wridx2 = 0;
                        }
 
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+                       if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) {
+#else /* CONFIG_FEATURE_TELNETD_INETD */
                        if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                                /* Read from socket to buffer 1. */
                                maxlen = MIN(BUFSIZE - ts->rdidx1,
                                                BUFSIZE - ts->size1);
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+                               r = read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen);
+                               if (!r || (r < 0 && errno != EINTR))
+                                       exit(0);
+#else /* CONFIG_FEATURE_TELNETD_INETD */
                                r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
                                if (!r || (r < 0 && errno != EINTR)) {
                                        free_session(ts);
                                        ts = next;
                                        continue;
                                }
-                               if(!*(ts->buf1 + ts->rdidx1 + r - 1)) {
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
+                               if (!*(ts->buf1 + ts->rdidx1 + r - 1)) {
                                        r--;
-                                       if(!r)
+                                       if (!r)
                                                continue;
                                }
                                ts->rdidx1 += r;
@@ -511,9 +612,13 @@ telnetd_main(int argc, char **argv)
                                                BUFSIZE - ts->size2);
                                r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
                                if (!r || (r < 0 && errno != EINTR)) {
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+                                       exit(0);
+#else /* CONFIG_FEATURE_TELNETD_INETD */
                                        free_session(ts);
                                        ts = next;
                                        continue;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                                }
                                ts->rdidx2 += r;
                                ts->size2 += r;
@@ -529,10 +634,12 @@ telnetd_main(int argc, char **argv)
                                ts->rdidx2 = 0;
                                ts->wridx2 = 0;
                        }
+#ifndef CONFIG_FEATURE_TELNETD_INETD
                        ts = next;
                }
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
-       } while (1);
+       } /* while(1) */
 
        return 0;
 }