- patch from Denis Vlasenko to add and use bb_xchdir()
[oweals/busybox.git] / networking / telnetd.c
index d208319a385fecec26c01c2ed091760f3936a498..1a53c0c0ccf3ed830fb86f6988ced32f8debee36 100644 (file)
@@ -1,10 +1,9 @@
-/* $Id: telnetd.c,v 1.2 2002/11/10 22:26:19 bug1 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 GPL, see file LICENSE in this tarball for details.
  *
  * ---------------------------------------------------------------------------
  * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
 #include <sys/time.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
+#include <sys/ioctl.h>
 #include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
 #include <netinet/in.h>
+#include <arpa/inet.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <signal.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 +72,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;
@@ -74,7 +93,7 @@ struct tsession {
    +-------+     wridx1++     +------+     rdidx1++     +----------+
    |       | <--------------  | buf1 | <--------------  |          |
    |       |     size1--      +------+     size1++      |          |
-   |  pty  |                                            |  socket  |
+   |  pty  |                                       |  socket  |
    |       |     rdidx2++     +------+     wridx2++     |          |
    |       |  --------------> | buf2 |  --------------> |          |
    +-------+     size2++      +------+     size2--      +----------+
@@ -106,10 +125,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 +139,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;
+                       }
                }
        }
 
@@ -204,7 +248,11 @@ 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;
@@ -214,7 +262,12 @@ make_new_session(int sockfd)
        ts->buf1 = (char *)(&ts[1]);
        ts->buf2 = ts->buf1 + BUFSIZE;
 
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+       ts->sockfd_read = 0;
+       ts->sockfd_write = 1;
+#else /* CONFIG_FEATURE_TELNETD_INETD */
        ts->sockfd = sockfd;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
        ts->rdidx1 = ts->wridx1 = ts->size1 = 0;
        ts->rdidx2 = ts->wridx2 = ts->size2 = 0;
@@ -224,7 +277,7 @@ make_new_session(int sockfd)
        pty = getpty(tty_name);
 
        if (pty < 0) {
-               syslog_msg(LOG_USER, LOG_ERR, "All network ports in use!");
+               syslog(LOG_ERR, "All network ports in use!");
                return 0;
        }
 
@@ -240,13 +293,14 @@ 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");
+               syslog(LOG_ERR, "Can`t forking");
        }
        if (pid == 0) {
                /* In child, open the child's side of the tty.  */
@@ -258,7 +312,7 @@ make_new_session(int sockfd)
                setsid();
 
                if (open(tty_name, O_RDWR /*| O_NOCTTY*/) < 0) {
-                       syslog_msg(LOG_USER, LOG_ERR, "Could not open tty");
+                       syslog(LOG_ERR, "Could not open tty");
                        exit(1);
                        }
                dup(0);
@@ -278,11 +332,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");
+               syslog(LOG_ERR, "execv error");
                exit(1);
        }
 
@@ -291,6 +347,7 @@ make_new_session(int sockfd)
        return ts;
 }
 
+#ifndef CONFIG_FEATURE_TELNETD_INETD
 static void
 free_session(struct tsession *ts)
 {
@@ -319,69 +376,97 @@ free_session(struct tsession *ts)
 
        free(ts);
 }
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
 int
 telnetd_main(int argc, char **argv)
 {
-       struct sockaddr_in sa;
+#ifndef CONFIG_FEATURE_TELNETD_INETD
+       sockaddr_type sa;
        int master_fd;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
        fd_set rdfdset, wrfdset;
        int selret;
+#ifndef CONFIG_FEATURE_TELNETD_INETD
        int on = 1;
        int portnbr = 23;
+       struct in_addr bind_addr = { .s_addr = 0x0 };
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
        int c;
-
-       /* check if user supplied a port number */
+       static const char options[] =
+#ifdef CONFIG_FEATURE_TELNETD_INETD
+               "f:l:";
+#else /* CONFIG_EATURE_TELNETD_INETD */
+               "f:l:p:b:";
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
+       int maxlen, w, r;
+
+#ifndef CONFIG_LOGIN
+       loginpath = DEFAULT_SHELL;
+#endif
 
        for (;;) {
-               c = getopt( argc, argv, "p:l:");
+               c = getopt( argc, argv, options);
                if (c == EOF) break;
                switch (c) {
+                       case 'f':
+                               issuefile = optarg;
+                               break;
+                       case 'l':
+                               loginpath = optarg;
+                               break;
+#ifndef CONFIG_FEATURE_TELNETD_INETD
                        case 'p':
                                portnbr = atoi(optarg);
                                break;
-                       case 'l':
-                               loginpath = strdup (optarg);
+                       case 'b':
+                               if (inet_aton(optarg, &bind_addr) == 0)
+                                       bb_show_usage();
                                break;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                        default:
-                               show_usage();
+                               bb_show_usage();
                }
        }
 
        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;
+
+       openlog(bb_applet_name, 0, LOG_USER);
+
+#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 = bb_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");
-
+       bb_xbind(master_fd, (struct sockaddr *) &sa, sizeof(sa));
+       bb_xlisten(master_fd, 1);
+       bb_xdaemon(0, 0);
 
        maxfd = master_fd;
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
 
        do {
                struct tsession *ts;
@@ -393,10 +478,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,22 +493,33 @@ 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;
@@ -447,8 +547,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 +561,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,31 +575,51 @@ 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;
                                }
+#endif /* CONFIG_FEATURE_TELNETD_INETD */
                                if(!*(ts->buf1 + ts->rdidx1 + r - 1)) {
                                        r--;
                                        if(!r)
@@ -511,9 +637,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,8 +659,10 @@ 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);