httpd: trivial simplification
[oweals/busybox.git] / networking / telnetd.c
index edc018a2a03a37c889c3a695620fd04087c7f262..b21991212cacd05fa56ffc15758b45f33c4964b1 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
  *     Set process group corrections, initial busybox port
  */
 
-/*#define DEBUG 1 */
-
-#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 DEBUG 0
+
+#include "libbb.h"
+
+#if 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";
-
-/* shell name and arguments */
+#if ENABLE_LOGIN
+static const char *loginpath = "/bin/login";
+#else
+static const char *loginpath = DEFAULT_SHELL;
+#endif
 
-static const char *argv_init[] = {NULL, NULL};
+static const char *issuefile = "/etc/issue.net";
 
 /* structure that describes a session */
 
 struct tsession {
        struct tsession *next;
-       int sockfd, ptyfd;
+       int sockfd_read, sockfd_write, ptyfd;
        int shell_pid;
        /* two circular buffers */
        char *buf1, *buf2;
@@ -66,8 +53,11 @@ struct tsession {
        int rdidx2, wridx2, size2;
 };
 
-/*
+/* Two buffers are directly after tsession in malloced memory.
+ * Make whole thing fit in 4k */
+enum { BUFSIZE = (4*1024 - sizeof(struct tsession)) / 2 };
 
+/*
    This is how the buffers are used. The arrows indicate the movement
    of data.
 
@@ -80,7 +70,6 @@ struct tsession {
    +-------+     size2++      +------+     size2--      +----------+
 
    Each session has got two buffers.
-
 */
 
 static int maxfd;
@@ -89,8 +78,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 +94,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;
+remove_iacs(struct tsession *ts, int *pnum_totty)
+{
+       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 +108,44 @@ remove_iacs(struct tsession *ts, int *pnum_totty) {
 
        while (ptr < end) {
                if (*ptr != IAC) {
+                       int c = *ptr;
                        *totty++ = *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 {
+                       /* 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 {
+                       /*
+                        * 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];
+                               ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
+                               ptr += 9;
+                       } else {
+                               /* skip 3-byte IAC non-SB cmd */
+#if DEBUG
+                               fprintf(stderr, "Ignoring IAC %s,%s\n",
+                                       TELCMD(ptr[1]), TELOPT(ptr[2]));
+#endif
+                               ptr += 3;
+                       }
                }
        }
 
@@ -153,16 +164,22 @@ remove_iacs(struct tsession *ts, int *pnum_totty) {
 
 
 static int
-getpty(char *line)
+getpty(char *line, int size)
 {
        int p;
-#ifdef HAVE_DEVPTS_FS
-       p = open("/dev/ptmx", 2);
+#if ENABLE_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;
+               }
+               safe_strncpy(line, name, size);
+               return p;
        }
 #else
        struct stat stb;
@@ -179,13 +196,16 @@ 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) {
+                       if (DEBUG)
+                               fprintf(stderr, "Trying to open device: %s\n", line);
+                       p = open(line, O_RDWR | O_NOCTTY);
+                       if (p >= 0) {
                                line[5] = 't';
                                return p;
                        }
                }
        }
-#endif /* HAVE_DEVPTS_FS */
+#endif /* FEATURE_DEVPTS */
        return -1;
 }
 
@@ -193,7 +213,7 @@ getpty(char *line)
 static void
 send_iac(struct tsession *ts, unsigned char command, int option)
 {
-       /* We rely on that there is space in the buffer for now.  */
+       /* We rely on that there is space in the buffer for now. */
        char *b = ts->buf2 + ts->rdidx2;
        *b++ = IAC;
        *b++ = command;
@@ -204,335 +224,349 @@ send_iac(struct tsession *ts, unsigned char command, int option)
 
 
 static struct tsession *
-make_new_session(int sockfd)
-{
+make_new_session(
+               USE_FEATURE_TELNETD_STANDALONE(int sock_r, int sock_w)
+               SKIP_FEATURE_TELNETD_STANDALONE(void)
+) {
+       const char *login_argv[2];
        struct termios termbuf;
-       int pty, pid;
+       int fd, 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;
 
-       ts->sockfd = sockfd;
-
-       ts->rdidx1 = ts->wridx1 = ts->size1 = 0;
-       ts->rdidx2 = ts->wridx2 = ts->size2 = 0;
-
-       /* 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!");
-               return 0;
+       /* Got a new connection, set up a tty. */
+       fd = getpty(tty_name, 32);
+       if (fd < 0) {
+               bb_error_msg("all terminals in use");
+               return NULL;
        }
-
-       if (pty > maxfd)
-               maxfd = pty;
-
-       ts->ptyfd = pty;
-
+       if (fd > maxfd) maxfd = fd;
+       ndelay_on(ts->ptyfd = fd);
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       if (sock_w > maxfd) maxfd = sock_w;
+       if (sock_r > maxfd) maxfd = sock_r;
+       ndelay_on(ts->sockfd_write = sock_w);
+       ndelay_on(ts->sockfd_read = sock_r);
+#else
+       ts->sockfd_write = 1;
+       /* xzalloc: ts->sockfd_read = 0; */
+       ndelay_on(0);
+       ndelay_on(1);
+#endif
        /* Make the telnet client understand we will echo characters so it
         * should not do it locally. We don't tell the client to run linemode,
         * because we want to handle line editing and tab completion and other
-        * stuff that requires char-by-char support.
-        */
-
+        * stuff that requires char-by-char support. */
        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");
+       pid = fork();
+       if (pid < 0) {
+               free(ts);
+               close(fd);
+               bb_perror_msg("fork");
+               return NULL;
        }
-       if (pid == 0) {
-               /* In child, open the child's side of the tty.  */
-               int i;
-
-               for(i = 0; i <= maxfd; i++)
-                       close(i);
-               /* 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);
-                       }
-               dup(0);
-               dup(0);
-
-               tcsetpgrp(0, getpid());
-
-               /* The pseudo-terminal allocated to the client is configured to operate in
-                * cooked mode, and with XTABS CRMOD enabled (see tty(4)).
-                */
-
-               tcgetattr(0, &termbuf);
-               termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
-               termbuf.c_oflag |= ONLCR|XTABS;
-               termbuf.c_iflag |= ICRNL;
-               termbuf.c_iflag &= ~IXOFF;
-               /*termbuf.c_lflag &= ~ICANON;*/
-               tcsetattr(0, TCSANOW, &termbuf);
-
-               /* 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);
+       if (pid > 0) {
+               /* parent */
+               ts->shell_pid = pid;
+               return ts;
        }
 
-       ts->shell_pid = pid;
-
-       return ts;
+       /* child */
+
+       /* make new session and process group */
+       setsid();
+
+       /* open the child's side of the tty. */
+       /* NB: setsid() disconnects from any previous ctty's. Therefore
+        * we must open child's side of the tty AFTER setsid! */
+       fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
+       dup2(fd, 0);
+       dup2(fd, 1);
+       dup2(fd, 2);
+       while (fd > 2) close(fd--);
+       tcsetpgrp(0, getpid()); /* 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)). */
+       tcgetattr(0, &termbuf);
+       termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
+       termbuf.c_oflag |= ONLCR|XTABS;
+       termbuf.c_iflag |= ICRNL;
+       termbuf.c_iflag &= ~IXOFF;
+       /*termbuf.c_lflag &= ~ICANON;*/
+       tcsetattr(0, TCSANOW, &termbuf);
+
+       print_login_issue(issuefile, NULL);
+
+       /* exec shell / login /whatever */
+       login_argv[0] = loginpath;
+       login_argv[1] = NULL;
+       execv(loginpath, (char **)login_argv);
+       /* Hmmm... this gets sent to the client thru fd#2! Is it ok?? */
+       bb_perror_msg_and_die("execv %s", loginpath);
 }
 
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+
 static void
 free_session(struct tsession *ts)
 {
        struct tsession *t = sessions;
 
-       /* Unlink this telnet session from the session list.  */
-       if(t == ts)
+       /* unlink this telnet session from the session list */
+       if (t == ts)
                sessions = ts->next;
        else {
-               while(t->next != ts)
+               while (t->next != ts)
                        t = t->next;
                t->next = ts->next;
        }
 
        kill(ts->shell_pid, SIGKILL);
-
        wait4(ts->shell_pid, NULL, 0, NULL);
-
        close(ts->ptyfd);
-       close(ts->sockfd);
-
-       if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
-               maxfd--;
-       if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
-               maxfd--;
-
+       close(ts->sockfd_read);
+       /* error if ts->sockfd_read == ts->sockfd_write. So what? ;) */
+       close(ts->sockfd_write);
        free(ts);
-}
 
-int
-telnetd_main(int argc, char **argv)
-{
-       struct sockaddr_in sa;
-       int master_fd;
-       fd_set rdfdset, wrfdset;
-       int selret;
-       int on = 1;
-       int portnbr = 23;
-       int c;
-
-       /* check if user supplied a port number */
-
-       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();
-               }
+       /* scan all sessions and find new maxfd */
+       ts = sessions;
+       maxfd = 0;
+       while (ts) {
+               if (maxfd < ts->ptyfd)
+                       maxfd = ts->ptyfd;
+               if (maxfd < ts->sockfd_read)
+                       maxfd = ts->sockfd_read;
+               if (maxfd < ts->sockfd_write)
+                       maxfd = ts->sockfd_write;
+               ts = ts->next;
        }
+}
 
-       if (access(loginpath, X_OK) < 0) {
-               error_msg_and_die ("'%s' unavailable.", loginpath);
-       }
-
-       argv_init[0] = loginpath;
-       sessions = 0;
-
-       /* Grab a TCP socket.  */
+#else /* !FEATURE_TELNETD_STANDALONE */
 
-       master_fd = socket(AF_INET, SOCK_STREAM, 0);
-       if (master_fd < 0) {
-               perror_msg_and_die("socket");
-       }
-       (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+/* Never actually called */
+void free_session(struct tsession *ts);
 
-       /* Set it to listen to specified port.  */
+#endif
 
-       memset((void *)&sa, 0, sizeof(sa));
-       sa.sin_family = AF_INET;
-       sa.sin_port = htons(portnbr);
 
-       if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
-               perror_msg_and_die("bind");
+int telnetd_main(int argc, char **argv);
+int telnetd_main(int argc, char **argv)
+{
+       fd_set rdfdset, wrfdset;
+       unsigned opt;
+       int selret, maxlen, w, r;
+       struct tsession *ts;
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+#define IS_INETD (opt & OPT_INETD)
+       int master_fd = -1; /* be happy, gcc */
+       unsigned portnbr = 23;
+       char *opt_bindaddr = NULL;
+       char *opt_portnbr;
+#else
+       enum {
+               IS_INETD = 1,
+               master_fd = -1,
+               portnbr = 23,
+       };
+#endif
+       enum {
+               OPT_PORT = 4 * ENABLE_FEATURE_TELNETD_STANDALONE,
+               OPT_FOREGROUND = 0x10 * ENABLE_FEATURE_TELNETD_STANDALONE,
+               OPT_INETD = 0x20 * ENABLE_FEATURE_TELNETD_STANDALONE,
+       };
+
+       opt = getopt32(argv, "f:l:" USE_FEATURE_TELNETD_STANDALONE("p:b:Fi"),
+                       &issuefile, &loginpath
+                       USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
+       /* Redirect log to syslog early, if needed */
+       if (IS_INETD || !(opt & OPT_FOREGROUND)) {
+               openlog(applet_name, 0, LOG_USER);
+               logmode = LOGMODE_SYSLOG;
        }
-
-       if (listen(master_fd, 1) < 0) {
-               perror_msg_and_die("listen");
+       //if (opt & 1) // -f
+       //if (opt & 2) // -l
+       USE_FEATURE_TELNETD_STANDALONE(
+               if (opt & OPT_PORT) // -p
+                       portnbr = xatou16(opt_portnbr);
+               //if (opt & 8) // -b
+               //if (opt & 0x10) // -F
+               //if (opt & 0x20) // -i
+       );
+
+       /* Used to check access(loginpath, X_OK) here. Pointless.
+        * exec will do this for us for free later. */
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       if (IS_INETD) {
+               sessions = make_new_session(0, 1);
+       } else {
+               master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
+               xlisten(master_fd, 1);
+               if (!(opt & OPT_FOREGROUND))
+                       bb_daemonize(DAEMON_CHDIR_ROOT);
        }
+#else
+       sessions = make_new_session();
+#endif
 
-       if (daemon(0, 0) < 0)
-               perror_msg_and_die("daemon");
-
-
-       maxfd = master_fd;
-
-       do {
-               struct tsession *ts;
-
-               FD_ZERO(&rdfdset);
-               FD_ZERO(&wrfdset);
-
-               /* select on the master socket, all telnet sockets and their
-                * ptys if there is room in their respective session buffers.
-                */
+       /* We don't want to die if just one session is broken */
+       signal(SIGPIPE, SIG_IGN);
 
+ again:
+       FD_ZERO(&rdfdset);
+       FD_ZERO(&wrfdset);
+       if (!IS_INETD) {
                FD_SET(master_fd, &rdfdset);
+               /* This is needed because free_session() does not
+                * take into account master_fd when it finds new
+                * maxfd among remaining fd's: */
+               if (master_fd > maxfd)
+                       maxfd = master_fd;
+       }
 
-               ts = sessions;
-               while (ts) {
-                       /* buf1 is used from socket to pty
-                        * buf2 is used from pty to socket
-                        */
-                       if (ts->size1 > 0) {
-                               FD_SET(ts->ptyfd, &wrfdset);  /* can write to pty */
-                       }
-                       if (ts->size1 < BUFSIZE) {
-                               FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
-                       }
-                       if (ts->size2 > 0) {
-                               FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
-                       }
-                       if (ts->size2 < BUFSIZE) {
-                               FD_SET(ts->ptyfd, &rdfdset);  /* can read from pty */
-                       }
-                       ts = ts->next;
-               }
-
-               selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
+       /* select on the master socket, all telnet sockets and their
+        * ptys if there is room in their session buffers. */
+       ts = sessions;
+       while (ts) {
+               /* buf1 is used from socket to pty
+                * buf2 is used from pty to socket */
+               if (ts->size1 > 0)       /* can write to pty */
+                       FD_SET(ts->ptyfd, &wrfdset);
+               if (ts->size1 < BUFSIZE) /* can read from socket */
+                       FD_SET(ts->sockfd_read, &rdfdset);
+               if (ts->size2 > 0)       /* can write to socket */
+                       FD_SET(ts->sockfd_write, &wrfdset);
+               if (ts->size2 < BUFSIZE) /* can read from pty */
+                       FD_SET(ts->ptyfd, &rdfdset);
+               ts = ts->next;
+       }
 
-               if (!selret)
-                       break;
+       selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
+       if (!selret)
+               return 0;
 
-               /* First check for and accept new sessions.  */
-               if (FD_ISSET(master_fd, &rdfdset)) {
-                       int fd, salen;
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+       /* First check for and accept new sessions. */
+       if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
+               int fd;
+               struct tsession *new_ts;
+
+               fd = accept(master_fd, NULL, 0);
+               if (fd < 0)
+                       goto again;
+               /* Create a new session and link it into our active list */
+               new_ts = make_new_session(fd, fd);
+               if (new_ts) {
+                       new_ts->next = sessions;
+                       sessions = new_ts;
+               } else {
+                       close(fd);
+               }
+       }
+#endif
 
-                       salen = sizeof(sa);
-                       if ((fd = accept(master_fd, (struct sockaddr *)&sa,
-                                               &salen)) < 0) {
+       /* Then check for data tunneling. */
+       ts = sessions;
+       while (ts) { /* For all sessions... */
+               struct tsession *next = ts->next; /* in case we free ts. */
+
+               if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
+                       int num_totty;
+                       char *ptr;
+                       /* Write to pty from buffer 1. */
+                       ptr = remove_iacs(ts, &num_totty);
+                       w = safe_write(ts->ptyfd, ptr, num_totty);
+                       /* needed? if (w < 0 && errno == EAGAIN) continue; */
+                       if (w < 0) {
+                               if (IS_INETD)
+                                       return 0;
+                               free_session(ts);
+                               ts = next;
                                continue;
-                       } else {
-                               /* Create a new session and link it into
-                                       our active list.  */
-                               struct tsession *new_ts = make_new_session(fd);
-                               if (new_ts) {
-                                       new_ts->next = sessions;
-                                       sessions = new_ts;
-                                       if (fd > maxfd)
-                                               maxfd = fd;
-                               } else {
-                                       close(fd);
-                               }
                        }
+                       ts->wridx1 += w;
+                       ts->size1 -= w;
+                       if (ts->wridx1 == BUFSIZE)
+                               ts->wridx1 = 0;
                }
 
-               /* Then check for data tunneling.  */
-
-               ts = sessions;
-               while (ts) { /* For all sessions...  */
-                       int maxlen, w, r;
-                       struct tsession *next = ts->next; /* in case we free ts. */
-
-                       if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
-                               int num_totty;
-                               char *ptr;
-                               /* Write to pty from buffer 1.  */
-
-                               ptr = remove_iacs(ts, &num_totty);
-
-                               w = write(ts->ptyfd, ptr, num_totty);
-                               if (w < 0) {
-                                       free_session(ts);
-                                       ts = next;
-                                       continue;
-                               }
-                               ts->wridx1 += w;
-                               ts->size1 -= w;
-                               if (ts->wridx1 == BUFSIZE)
-                                       ts->wridx1 = 0;
-                       }
-
-                       if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
-                               /* Write to socket from buffer 2.  */
-                               maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
-                               w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
-                               if (w < 0) {
-                                       free_session(ts);
-                                       ts = next;
-                                       continue;
-                               }
-                               ts->wridx2 += w;
-                               ts->size2 -= w;
-                               if (ts->wridx2 == BUFSIZE)
-                                       ts->wridx2 = 0;
+               if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) {
+                       /* Write to socket from buffer 2. */
+                       maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
+                       w = safe_write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen);
+                       /* needed? if (w < 0 && errno == EAGAIN) continue; */
+                       if (w < 0) {
+                               if (IS_INETD)
+                                       return 0;
+                               free_session(ts);
+                               ts = next;
+                               continue;
                        }
+                       ts->wridx2 += w;
+                       ts->size2 -= w;
+                       if (ts->wridx2 == BUFSIZE)
+                               ts->wridx2 = 0;
+               }
 
-                       if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
-                               /* Read from socket to buffer 1. */
-                               maxlen = MIN(BUFSIZE - ts->rdidx1,
-                                               BUFSIZE - ts->size1);
-                               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)) {
-                                       r--;
-                                       if(!r)
-                                               continue;
-                               }
-                               ts->rdidx1 += r;
-                               ts->size1 += r;
-                               if (ts->rdidx1 == BUFSIZE)
-                                       ts->rdidx1 = 0;
+               if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) {
+                       /* Read from socket to buffer 1. */
+                       maxlen = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
+                       r = safe_read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen);
+                       if (r < 0 && errno == EAGAIN) continue;
+                       if (r <= 0) {
+                               if (IS_INETD)
+                                       return 0;
+                               free_session(ts);
+                               ts = next;
+                               continue;
                        }
-
-                       if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
-                               /* Read from pty to buffer 2.  */
-                               maxlen = MIN(BUFSIZE - ts->rdidx2,
-                                               BUFSIZE - ts->size2);
-                               r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
-                               if (!r || (r < 0 && errno != EINTR)) {
-                                       free_session(ts);
-                                       ts = next;
+                       if (!ts->buf1[ts->rdidx1 + r - 1])
+                               if (!--r)
                                        continue;
-                               }
-                               ts->rdidx2 += r;
-                               ts->size2 += r;
-                               if (ts->rdidx2 == BUFSIZE)
-                                       ts->rdidx2 = 0;
-                       }
-
-                       if (ts->size1 == 0) {
+                       ts->rdidx1 += r;
+                       ts->size1 += r;
+                       if (ts->rdidx1 == BUFSIZE)
                                ts->rdidx1 = 0;
-                               ts->wridx1 = 0;
+               }
+
+               if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
+                       /* Read from pty to buffer 2. */
+                       maxlen = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
+                       r = safe_read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
+                       if (r < 0 && errno == EAGAIN) continue;
+                       if (r <= 0) {
+                               if (IS_INETD)
+                                       return 0;
+                               free_session(ts);
+                               ts = next;
+                               continue;
                        }
-                       if (ts->size2 == 0) {
+                       ts->rdidx2 += r;
+                       ts->size2 += r;
+                       if (ts->rdidx2 == BUFSIZE)
                                ts->rdidx2 = 0;
-                               ts->wridx2 = 0;
-                       }
-                       ts = next;
                }
 
-       } while (1);
-
-       return 0;
+               if (ts->size1 == 0) {
+                       ts->rdidx1 = 0;
+                       ts->wridx1 = 0;
+               }
+               if (ts->size2 == 0) {
+                       ts->rdidx2 = 0;
+                       ts->wridx2 = 0;
+               }
+               ts = next;
+       }
+       goto again;
 }