changeset 1162:213c00e7978e draft

telnet and telnetd from Ashwini Sharma's guys.
author Rob Landley <rob@landley.net>
date Mon, 23 Dec 2013 09:33:48 -0600
parents 9dc339b4ac2c
children 972d4fc0c8a2
files toys/pending/telnet.c toys/pending/telnetd.c
diffstat 2 files changed, 837 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toys/pending/telnet.c	Mon Dec 23 09:33:48 2013 -0600
@@ -0,0 +1,386 @@
+/* telnet.c - Telnet client.
+ *
+ * Copyright 2012 Madhur Verma <mad.flexi@gmail.com>
+ * Copyright 2013 Kyungwan Han <asura321@gmail.com>
+ * Modified by Ashwini Kumar <ak.ashwini1981@gmail.com>
+ *
+ * Not in SUSv4.
+
+USE_TELNET(NEWTOY(telnet, "<1>2", TOYFLAG_BIN))
+
+config TELNET
+  bool "telnet"
+  default n
+  help
+    usage: telnet HOST [PORT]
+    Connect to telnet server
+*/
+
+#define FOR_telnet
+#include "toys.h"
+#include <arpa/telnet.h>
+#include <netinet/in.h>
+#include  <sys/poll.h>
+
+GLOBALS(
+  int port;
+  int sfd;
+  char buff[128];
+  int pbuff;
+  char iac[256];
+  int piac;
+  char *ttype;
+  struct termios def_term;
+  struct termios raw_term;
+  uint8_t term_ok;
+  uint8_t term_mode;
+  uint8_t flags;
+  unsigned win_width;
+  unsigned win_height;
+  unsigned signalno;
+)
+
+#define DATABUFSIZE 128
+#define IACBUFSIZE  256
+#define CM_TRY      0
+#define CM_ON       1
+#define CM_OFF      2
+#define UF_ECHO     0x01
+#define UF_SGA      0x02
+
+/*
+ * creates a socket of family INET/INET6 and protocol TCP and connects
+ * it to HOST at PORT.
+ * if successful then returns SOCK othrwise error
+ */
+static int xconnect_inet_tcp(char *host, int port)
+{
+  int ret;
+  struct addrinfo *info, *rp;
+  char buf[32];
+
+  rp = xzalloc(sizeof(struct addrinfo));
+  rp->ai_family = AF_UNSPEC;
+  rp->ai_socktype = SOCK_STREAM;
+  rp->ai_protocol = IPPROTO_TCP;
+  sprintf(buf, "%d", port);
+
+  ret = getaddrinfo(host, buf, rp, &info);
+  if(ret || !info) perror_exit("BAD ADDRESS: can't find : %s ", host);
+  free(rp);
+
+  for (rp = info; rp; rp = rp->ai_next) 
+    if ( (rp->ai_family == AF_INET) || (rp->ai_family == AF_INET6)) break;
+
+  if (!rp) error_exit("Invalid IP %s", host);
+
+  ret = xsocket(rp->ai_family, SOCK_STREAM, IPPROTO_TCP);
+  if(connect(ret, rp->ai_addr, rp->ai_addrlen) == -1) perror_exit("connect");
+
+  freeaddrinfo(info);
+  return ret;
+}
+
+// sets terminal mode: LINE or CHARACTER based om internal stat.
+static char const es[] = "\r\nEscape character is ";
+static void set_mode(void)
+{
+  if (TT.flags & UF_ECHO) {
+    if (TT.term_mode == CM_TRY) {
+      TT.term_mode = CM_ON;
+      printf("\r\nEntering character mode%s'^]'.\r\n", es);
+      if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
+    }
+  } else {
+    if (TT.term_mode != CM_OFF) {
+      TT.term_mode = CM_OFF;
+      printf("\r\nEntering line mode%s'^C'.\r\n", es);
+      if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+    }
+  }
+}
+
+// flushes all data in IAC buff to server.
+static void flush_iac(void)
+{
+  int wlen = write(TT.sfd, TT.iac, TT.piac);
+
+  if(wlen <= 0) error_msg("IAC : send failed.");
+  TT.piac = 0;
+}
+
+// puts DATA in iac buff of length LEN and updates iac buff pointer.
+static void put_iac(int len, ...)
+{
+  va_list va; 
+
+  if(TT.piac + len >= IACBUFSIZE) flush_iac();
+  va_start(va, len);
+  for(;len > 0; TT.iac[TT.piac++] = (uint8_t)va_arg(va, int), len--);
+  va_end(va);
+}
+
+// puts string STR in iac buff and updates iac buff pointer.
+static void str_iac(char *str)
+{
+  int len = strlen(str);
+
+  if(TT.piac + len + 1 >= IACBUFSIZE) flush_iac();
+  strcpy(&TT.iac[TT.piac], str);
+  TT.piac += len+1;
+}
+
+static void handle_esc(void)
+{
+  char input;
+
+  if(TT.signalno && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
+  write(1,"\r\nConsole escape. Commands are:\r\n\n"
+      " l  go to line mode\r\n"
+      " c  go to character mode\r\n"
+      " z  suspend telnet\r\n"
+      " e  exit telnet\r\n", 114);
+
+  if (read(STDIN_FILENO, &input, 1) <= 0) {
+    if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+    exit(0);
+  }
+
+  switch (input) {
+  case 'l':
+    if (!TT.signalno) {
+      TT.term_mode = CM_TRY;
+      TT.flags &= ~(UF_ECHO | UF_SGA);
+      set_mode();
+      put_iac(6, IAC,DONT,TELOPT_ECHO,IAC,DONT, TELOPT_SGA);
+      flush_iac();
+      goto ret;
+    }
+    break;
+  case 'c':
+    if (TT.signalno) {
+      TT.term_mode = CM_TRY;
+      TT.flags |= (UF_ECHO | UF_SGA);
+      set_mode();
+      put_iac(6, IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
+      flush_iac();
+      goto ret;
+    }
+    break;
+  case 'z':
+    if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+    kill(0, SIGTSTP);
+    if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term);
+    break;
+  case 'e':
+    if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+    exit(0);
+  default: break;
+  }
+
+  write(1, "continuing...\r\n", 15);
+  if (TT.signalno && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+
+ret:
+  TT.signalno = 0;
+}
+
+/*
+ * handles telnet SUB NEGOTIATIONS
+ * only terminal type is supported.
+ */
+static void handle_negotiations(void)
+{
+  char opt = TT.buff[TT.pbuff++];
+
+  switch(opt) {
+  case TELOPT_TTYPE:
+    opt =  TT.buff[TT.pbuff++];
+    if(opt == TELQUAL_SEND) {
+      put_iac(4, IAC,SB,TELOPT_TTYPE,TELQUAL_IS);
+      str_iac(TT.ttype);
+      put_iac(2, IAC,SE);
+    }
+    break;
+  default: break;
+  }
+}
+
+/*
+ * handles server's DO DONT WILL WONT requests.
+ * supports ECHO, SGA, TTYPE, NAWS
+ */
+static void handle_ddww(char ddww)
+{
+  char opt = TT.buff[TT.pbuff++];
+
+  switch (opt) {
+  case TELOPT_ECHO: /* ECHO */
+    if (ddww == DO) put_iac(3, IAC,WONT,TELOPT_ECHO);
+    if(ddww == DONT) break;
+    if (TT.flags & UF_ECHO) {
+        if (ddww == WILL) return;
+      } else if (ddww == WONT) return;
+    if (TT.term_mode != CM_OFF) TT.flags ^= UF_ECHO;
+    (TT.flags & UF_ECHO)? put_iac(3, IAC,DO,TELOPT_ECHO) : 
+      put_iac(3, IAC,DONT,TELOPT_ECHO);
+    set_mode();
+    printf("\r\n");
+    break;
+
+  case TELOPT_SGA: /* Supress GO Ahead */
+    if (TT.flags & UF_SGA){ if (ddww == WILL) return;
+    } else if (ddww == WONT) return;
+
+    TT.flags ^= UF_SGA;
+    (TT.flags & UF_SGA)? put_iac(3, IAC,DO,TELOPT_SGA) :
+      put_iac(3, IAC,DONT,TELOPT_SGA);
+    break;
+
+  case TELOPT_TTYPE: /* Terminal Type */
+    (TT.ttype)? put_iac(3, IAC,WILL,TELOPT_TTYPE):
+      put_iac(3, IAC,WONT,TELOPT_TTYPE);
+    break;
+
+  case TELOPT_NAWS: /* Window Size */
+    put_iac(3, IAC,WILL,TELOPT_NAWS);
+    put_iac(9, IAC,SB,TELOPT_NAWS,(TT.win_width >> 8) & 0xff,
+        TT.win_width & 0xff,(TT.win_height >> 8) & 0xff,
+        TT.win_height & 0xff,IAC,SE);
+    break;
+
+  default: /* Default behaviour is to say NO */
+    if(ddww == WILL) put_iac(3, IAC,DONT,opt);
+    if(ddww == DO) put_iac(3, IAC,WONT,opt);
+    break;
+  }
+}
+
+/*
+ * parses data which is read from server of length LEN.
+ * and passes it to console.
+ */
+static int read_server(int len)
+{
+  int i = 0;
+  char curr;
+  TT.pbuff = 0;
+
+  do {
+    curr = TT.buff[TT.pbuff++];
+    if (curr == IAC) {
+      curr = TT.buff[TT.pbuff++];
+      switch (curr) {
+      case DO:    /* FALLTHROUGH */
+      case DONT:    /* FALLTHROUGH */
+      case WILL:    /* FALLTHROUGH */
+      case WONT:
+        handle_ddww(curr);
+        break;
+      case SB:
+        handle_negotiations();
+        break;
+      case SE:
+        break;
+      default: break;
+      }
+    } else {
+      toybuf[i++] = curr;
+      if (curr == '\r') { curr = TT.buff[TT.pbuff++];
+        if (curr != '\0') TT.pbuff--;
+      }
+    }
+  } while (TT.pbuff < len);
+
+  if (i) write(STDIN_FILENO, toybuf, i);
+  return 0;
+}
+
+/*
+ * parses data which is read from console of length LEN
+ * and passes it to server.
+ */
+static void write_server(int len)
+{
+  char *c = (char*)TT.buff;
+  int i = 0;
+
+  for (; len > 0; len--, c++) {
+    if (*c == 0x1d) {
+      handle_esc();
+      return;
+    }
+    toybuf[i++] = *c;
+    if (*c == IAC) toybuf[i++] = *c; /* IAC -> IAC IAC */
+    else if (*c == '\r') toybuf[i++] = '\0'; /* CR -> CR NUL */
+  }
+  if(i) write(TT.sfd, toybuf, i);
+}
+
+/*
+ * SIGINT signal handling.
+ * only sets signalno which get handle in main loop.
+ */
+static void handle_sigint(int signo)
+{
+  TT.signalno = signo;
+}
+
+void telnet_main(void)
+{
+  int set = 1, len;
+  struct pollfd pfds[2];
+
+  TT.port = 23; //TELNET_PORT
+  TT.win_width = 80; //columns
+  TT.win_height = 24; //rows
+
+  if(toys.optc == 2) TT.port = atoi(toys.optargs[1]);
+  if(TT.port <= 0 || TT.port > 65535) 
+    error_exit("PORT can be non-zero upto 65535.");
+
+  TT.ttype = getenv("TERM");
+  if(!TT.ttype) TT.ttype = "";
+  if(strlen(TT.ttype) > IACBUFSIZE-1) TT.ttype[IACBUFSIZE - 1] = '\0';
+
+  if (!tcgetattr(0, &TT.def_term)) {
+    TT.term_ok = 1;
+    TT.raw_term = TT.def_term;
+    cfmakeraw(&TT.raw_term);
+  }
+  terminal_size(&TT.win_width, &TT.win_height);
+
+  TT.sfd = xconnect_inet_tcp(toys.optargs[0], TT.port);
+  setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set));
+  setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set));
+
+  pfds[0].fd = STDIN_FILENO;
+  pfds[0].events = POLLIN;
+  pfds[1].fd = TT.sfd;
+  pfds[1].events = POLLIN;
+
+  TT.piac = TT.pbuff = 0;
+  TT.signalno = 0;
+  signal(SIGINT, handle_sigint);
+  while(1) {
+    if(TT.piac) flush_iac();
+    if(poll(pfds, 2, -1) < 0) {
+      (TT.signalno)?handle_esc():sleep(1);
+      continue;
+    }
+    if(pfds[0].revents) {
+      len = read(STDIN_FILENO, TT.buff, DATABUFSIZE);
+      if(len > 0) write_server(len);
+      else return;
+    }
+    if(pfds[1].revents) {
+      len = read(TT.sfd, TT.buff, DATABUFSIZE);
+      if(len > 0) read_server(len);
+      else {
+        printf("Connection closed by foreign host\r\n");
+        if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term);
+        exit(1);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toys/pending/telnetd.c	Mon Dec 23 09:33:48 2013 -0600
@@ -0,0 +1,451 @@
+/* telnetd.c - Telnet Server 
+ *
+ * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com>
+ * Copyright 2013 Kyungwan Han <asura321@gmail.com>
+ *
+USE_TELNETD(NEWTOY(telnetd, "w#<0b:p#<0>65535=23f:l:FSKi[!wi]", TOYFLAG_USR|TOYFLAG_BIN))
+
+config TELNETD
+  bool "telnetd"
+  default n
+  help
+    Handle incoming telnet connections
+
+    -l LOGIN  Exec LOGIN on connect
+    -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue
+    -K Close connection as soon as login exits
+    -p PORT   Port to listen on
+    -b ADDR[:PORT]  Address to bind to
+    -F Run in foreground
+    -i Inetd mode
+    -w SEC    Inetd 'wait' mode, linger time SEC
+    -S Log to syslog (implied by -i or without -F and -w)
+*/
+
+#define FOR_telnetd
+#include "toys.h"
+#include <utmp.h>
+GLOBALS(
+    char *login_path;
+    char *issue_path;
+    int port;
+    char *host_addr;
+    long w_sec;
+
+    int gmax_fd;
+    int sig;
+    pid_t fork_pid;
+)
+
+
+# define IAC         255  /* interpret as command: */
+# define DONT        254  /* you are not to use option */
+# define DO          253  /* please, you use option */
+# define WONT        252  /* I won't use option */
+# define WILL        251  /* I will use option */
+# define SB          250  /* interpret as subnegotiation */
+# define SE          240  /* end sub negotiation */
+# define NOP         241  /* No Operation */
+# define TELOPT_ECHO   1  /* echo */
+# define TELOPT_SGA    3  /* suppress go ahead */
+# define TELOPT_TTYPE 24  /* terminal type */
+# define TELOPT_NAWS  31  /* window size */
+
+#define BUFSIZE 4*1024
+struct term_session {
+  int new_fd, pty_fd;
+  pid_t child_pid;
+  int buff1_avail, buff2_avail;
+  int buff1_written, buff2_written;
+  int rem;  //unprocessed data from socket
+  char buff1[BUFSIZE], buff2[BUFSIZE];
+  struct term_session *next;
+};
+
+struct term_session *session_list = NULL;
+
+static void get_sockaddr(char *host, void *buf)
+{
+  in_port_t port_num = htons(TT.port);
+  struct addrinfo hints, *result;
+  int status, af = AF_UNSPEC;
+  char *s;
+
+  // [ipv6]:port or exactly one :
+  if (*host == '[') {
+    host++;
+    s = strchr(host, ']');
+    if (s) *s++ = 0;
+    else error_exit("bad address '%s'", host-1);
+    af = AF_INET6;
+  } else {
+    s = strrchr(host, ':');
+    if (s && strchr(host, ':') == s) {
+      *s = 0;
+      af = AF_INET;
+    } else if (s && strchr(host, ':') != s) {
+      af = AF_INET6;
+      s = 0;
+    }
+  }
+
+  if (s++) {
+    char *ss;
+    unsigned long p = strtoul(s, &ss, 0);
+    if (!*s || *ss || p > 65535) error_exit("bad port '%s'", s);
+    port_num = htons(p);
+  }
+
+  memset(&hints, 0 , sizeof(struct addrinfo));
+  hints.ai_family = af;
+  hints.ai_socktype = SOCK_STREAM;
+
+  status = getaddrinfo(host, NULL, &hints, &result);
+  if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status));
+
+  memcpy(buf, result->ai_addr, result->ai_addrlen);
+  freeaddrinfo(result);
+
+  if (af == AF_INET) ((struct sockaddr_in*)buf)->sin_port = port_num;
+  else ((struct sockaddr_in6*)buf)->sin6_port = port_num;
+}
+
+static void utmp_entry(void)                                                                                                                                 
+{               
+  struct utmp entry;
+  struct utmp *utp_ptr;
+  pid_t pid = getpid();
+
+  utmpname(_PATH_UTMP);
+  setutent(); //start from start
+  while ((utp_ptr = getutent()) != NULL) {
+    if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break;
+  }             
+  if (!utp_ptr) entry.ut_type = DEAD_PROCESS;
+  time(&entry.ut_time);  
+  setutent();   
+  pututline(&entry);     
+}
+
+static int listen_socket(void)
+{
+  int s, af = AF_INET, yes = 1;
+  char buf[sizeof(struct sockaddr_storage)];
+
+  memset(buf, 0, sizeof(buf));
+  if (toys.optflags & FLAG_b) {
+    get_sockaddr(TT.host_addr, buf);
+    af = ((struct sockaddr *)buf)->sa_family;
+  } else {
+    ((struct sockaddr_in*)buf)->sin_port = htons(TT.port);
+    ((struct sockaddr_in*)buf)->sin_family = af;
+  }
+  s = xsocket(af, SOCK_STREAM, 0);
+  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) == -1) 
+    perror_exit("setsockopt");
+
+  if (bind(s, (struct sockaddr *)buf, ((af == AF_INET)?
+          (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6)))) == -1) {
+    close(s);
+    perror_exit("bind");
+  }
+
+  if (listen(s, 1) < 0) perror_exit("listen");
+  return s;
+}
+
+static void write_issue(char *tty)
+{
+  int size;
+  char ch = 0;
+  struct utsname u;
+  int fd = open(TT.issue_path, O_RDONLY);
+
+  if (fd < 0) return ;
+  uname(&u);
+  while ((size = readall(fd, &ch, 1)) > 0) {
+    if (ch == '\\' || ch == '%') {
+      if (readall(fd, &ch, 1) <= 0) perror_exit("readall!");
+      if (ch == 's') fputs(u.sysname, stdout);
+      if (ch == 'n'|| ch == 'h') fputs(u.nodename, stdout);
+      if (ch == 'r') fputs(u.release, stdout);
+      if (ch == 'm') fputs(u.machine, stdout);
+      if (ch == 'l') fputs(tty, stdout);
+    }
+    else if (ch == '\n') {
+      fputs("\n\r\0", stdout);
+    } else fputc(ch, stdout);
+  }
+  fflush(NULL);
+  close(fd);
+}
+
+static int new_session(int sockfd)
+{
+  char *argv_login[2]; //arguments for execvp cmd, NULL
+  char tty_name[30]; //tty name length.
+  int fd, flags, i = 1;
+  char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS,
+    IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA };
+
+  setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
+  flags = fcntl(sockfd, F_GETFL);
+  fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
+  if (toys.optflags & FLAG_i) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK);
+
+  writeall((toys.optflags & FLAG_i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
+  if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) {
+    flags = fcntl(fd, F_GETFL);
+    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+    return fd;
+  }
+  if (TT.fork_pid < 0) perror_exit("fork");
+  write_issue(tty_name);
+  argv_login[0] = strdup(TT.login_path);
+  argv_login[1] = NULL;
+  execvp(argv_login[0], argv_login);
+  exit(EXIT_FAILURE);
+}
+
+static int handle_iacs(struct term_session *tm, int c, int fd)
+{
+  char *curr ,*start,*end;
+  int i = 0;
+
+  curr = start = tm->buff2+tm->buff2_avail; 
+  end = tm->buff2 + c -1;
+  tm->rem = 0;
+  while (curr <= end) {
+    if (*curr != IAC){
+
+      if (*curr != '\r') {
+        toybuf[i++] = *curr++;
+        continue;
+      } else {
+        toybuf[i++] = *curr++;
+        curr++;
+        if (curr < end && (*curr == '\n' || *curr == '\0')) 
+          curr++;
+        continue;
+      }
+    }
+
+    if ((curr + 1) > end) {
+      tm->rem = 1;
+      break; 
+    }
+    if (*(curr+1) == IAC) { //IAC as data --> IAC IAC
+      toybuf[i++] = *(curr+1);
+      curr += 2; //IAC IAC --> 2 bytes
+      continue;
+    }
+    if (*(curr + 1) == NOP || *(curr + 1) == SE) {
+      curr += 2;
+      continue;
+    }
+
+    if (*(curr + 1) == SB ) {
+      if (*(curr+2) == TELOPT_NAWS) {
+        struct winsize ws;
+        if ((curr+8) >= end) {  //ensure we have data to process.
+          tm->rem = end - curr; 
+          break;  
+        }
+        ws.ws_col = (curr[3] << 8) | curr[4];
+        ws.ws_row = (curr[5] << 8) | curr[6];
+        ioctl(fd, TIOCSWINSZ, (char *)&ws);
+        curr += 9;
+        continue;
+      } else { //eat non-supported sub neg. options.
+        curr++, tm->rem++;
+        while (*curr != IAC && curr <= end) {
+          curr++;
+          tm->rem++;
+        } 
+        if (*curr == IAC) {
+          tm->rem = 0;
+          continue;
+        } else break;
+      }
+    }
+    curr += 3; //skip non-supported 3 bytes.
+  }
+  memcpy(start, toybuf, i);
+  memcpy(start + i, end - tm->rem, tm->rem); //put remaining if we break;
+  return i;
+}
+
+static int dup_iacs(char *start, int fd, int len)
+{
+  char arr[] = {IAC, IAC};
+  char *needle = NULL;
+  int ret = 0, c, count = 0;
+
+  while (len) {
+    if (*start == IAC) {
+      count = writeall(fd, arr, sizeof(arr));
+      if (count != 2) break; //short write
+      start++;
+      ret++;
+      len--;
+      continue;
+    }
+    needle = memchr(start, IAC, len);
+    if (needle) c = needle - start;
+    else c = len;
+    count = writeall(fd, start, c);
+    if (count < 0) break; 
+    len -= count;
+    ret += count;
+    start += count;
+  }
+  return ret;
+}
+
+static void kill_session(void)
+{
+  int status;
+  struct term_session *tm, *prev = NULL;
+  pid_t pid = wait(&status);
+  TT.sig = 0; //ASAP
+
+  tm = session_list;
+  if (toys.optflags & FLAG_i) exit(EXIT_SUCCESS);
+
+  if (pid < 0) return;
+  while (tm) {
+    if (tm->child_pid == pid) break;
+    prev = tm;
+    tm = tm->next;
+  }
+  if (!tm) return; //paranoia
+  if (!prev) session_list = session_list->next;
+  else prev->next = tm->next;
+  utmp_entry();
+  xclose(tm->pty_fd);
+  xclose(tm->new_fd);
+  free(tm);
+}
+
+static void session_handler(int sig) 
+{
+  TT.sig = sig;
+}
+
+void telnetd_main(void)
+{
+  errno = 0;
+  fd_set rd, wr;
+  struct term_session *tm = NULL;
+  struct timeval tv, *tv_ptr = NULL;
+  int pty_fd, new_fd, c = 0, w, master_fd = 0;
+  int inetd_m = (toys.optflags & FLAG_i);
+
+  TT.sig = 0;
+  if (!(toys.optflags & FLAG_l)) TT.login_path = "/bin/login";
+  if (!(toys.optflags & FLAG_f)) TT.issue_path = "/etc/issue.net";
+  if (toys.optflags & FLAG_w) toys.optflags |= FLAG_F;
+  if (!inetd_m) {
+    master_fd = listen_socket();
+    fcntl(master_fd, F_SETFD, FD_CLOEXEC);
+    if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd;
+    if (!(toys.optflags & FLAG_F)) daemonize(); 
+  } else {
+    pty_fd = new_session(master_fd); //master_fd = 0
+    if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
+    tm = xzalloc(sizeof(struct term_session));
+    tm->child_pid = TT.fork_pid;
+    tm->new_fd = 0;    
+    tm->pty_fd = pty_fd;    
+    if (session_list) {     
+      tm->next = session_list;
+      session_list = tm;    
+    } else session_list = tm;
+  }
+
+  if ((toys.optflags & FLAG_w) && !session_list) {
+    tv.tv_sec = TT.w_sec;
+    tv.tv_usec = 0;
+    tv_ptr = &tv;
+  }                
+  signal(SIGCHLD, session_handler);
+
+  for (;;) {
+    FD_ZERO(&rd);
+    FD_ZERO(&wr);
+    if (!inetd_m) FD_SET(master_fd, &rd);
+
+    tm = session_list;
+    while (tm) {
+
+      if (tm->pty_fd > 0 && tm->buff1_avail < BUFSIZE) FD_SET(tm->pty_fd, &rd);
+      if (tm->new_fd >= 0 && tm->buff2_avail < BUFSIZE) FD_SET(tm->new_fd, &rd);
+      if (tm->pty_fd > 0 && (tm->buff2_avail - tm->buff2_written) > 0)  
+        FD_SET(tm->pty_fd, &wr);
+      if (tm->new_fd >= 0 && (tm->buff1_avail - tm->buff1_written) > 0)  
+        FD_SET(tm->new_fd, &wr);
+      tm = tm->next;
+    }
+
+
+    int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr);
+    if (!r) return; //timeout
+    if (r < -1) continue;
+
+    if (!inetd_m && FD_ISSET(master_fd, &rd)) { //accept new connection
+      new_fd = accept(master_fd, NULL, NULL);
+      if (new_fd < 0) continue;
+      tv_ptr = NULL;
+      fcntl(new_fd, F_SETFD, FD_CLOEXEC);
+      if (new_fd > TT.gmax_fd) TT.gmax_fd = new_fd;
+      pty_fd = new_session(new_fd);
+      if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
+
+      tm = xzalloc(sizeof(struct term_session));
+      tm->child_pid = TT.fork_pid;
+      tm->new_fd = new_fd;
+      tm->pty_fd = pty_fd;
+      if (session_list) {
+        tm->next = session_list;
+        session_list = tm;
+      } else session_list = tm;
+    }
+
+    tm = session_list;
+    for (;tm;tm=tm->next) {
+      if (FD_ISSET(tm->pty_fd, &rd)) {
+        if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail,
+                BUFSIZE-tm->buff1_avail)) <= 0) break;
+        tm->buff1_avail += c;
+        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
+                tm->buff1_avail - tm->buff1_written)) < 0) break;
+        tm->buff1_written += w;
+      }
+      if (FD_ISSET(tm->new_fd, &rd)) {
+        if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail,
+                BUFSIZE-tm->buff2_avail)) <= 0) break;
+        c = handle_iacs(tm, c, tm->pty_fd);
+        tm->buff2_avail += c;
+        if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written, 
+                tm->buff2_avail - tm->buff2_written)) < 0) break;
+        tm->buff2_written += w;
+      }
+      if (FD_ISSET(tm->pty_fd, &wr)) {
+        if ((w = write(tm->pty_fd,  tm->buff2 + tm->buff2_written, 
+                tm->buff2_avail - tm->buff2_written)) < 0) break;
+        tm->buff2_written += w;
+      }
+      if (FD_ISSET(tm->new_fd, &wr)) {
+        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
+                tm->buff1_avail - tm->buff1_written)) < 0) break;
+        tm->buff1_written += w;
+      }
+      if (tm->buff1_written == tm->buff1_avail)
+        tm->buff1_written = tm->buff1_avail = 0;
+      if (tm->buff2_written == tm->buff2_avail)
+        tm->buff2_written = tm->buff2_avail = 0;
+      fflush(NULL);
+    }
+    if (TT.sig) kill_session();
+  }
+}