toybox/toys/pending/telnetd.c
<<
>>
Prefs
   1/* telnetd.c - Telnet Server 
   2 *
   3 * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com>
   4 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
   5 *
   6USE_TELNETD(NEWTOY(telnetd, "w#<0b:p#<0>65535=23f:l:FSKi[!wi]", TOYFLAG_USR|TOYFLAG_BIN))
   7
   8config TELNETD
   9  bool "telnetd"
  10  default n
  11  help
  12    Handle incoming telnet connections
  13
  14    -l LOGIN  Exec LOGIN on connect
  15    -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue
  16    -K Close connection as soon as login exits
  17    -p PORT   Port to listen on
  18    -b ADDR[:PORT]  Address to bind to
  19    -F Run in foreground
  20    -i Inetd mode
  21    -w SEC    Inetd 'wait' mode, linger time SEC
  22    -S Log to syslog (implied by -i or without -F and -w)
  23*/
  24
  25#define FOR_telnetd
  26#include "toys.h"
  27#include <arpa/telnet.h>
  28
  29GLOBALS(
  30    char *login_path;
  31    char *issue_path;
  32    int port;
  33    char *host_addr;
  34    long w_sec;
  35
  36    int gmax_fd;
  37    pid_t fork_pid;
  38)
  39
  40#define BUFSIZE 4*1024
  41struct term_session {
  42  int new_fd, pty_fd;
  43  pid_t child_pid;
  44  int buff1_avail, buff2_avail;
  45  int buff1_written, buff2_written;
  46  int rem;  //unprocessed data from socket
  47  char buff1[BUFSIZE], buff2[BUFSIZE];
  48  struct term_session *next;
  49};
  50
  51struct term_session *session_list = NULL;
  52
  53static void get_sockaddr(char *host, void *buf)
  54{
  55  in_port_t port_num = htons(TT.port);
  56  struct addrinfo hints, *result;
  57  int status, af = AF_UNSPEC;
  58  char *s;
  59
  60  // [ipv6]:port or exactly one :
  61  if (*host == '[') {
  62    host++;
  63    s = strchr(host, ']');
  64    if (s) *s++ = 0;
  65    else error_exit("bad address '%s'", host-1);
  66    af = AF_INET6;
  67  } else {
  68    s = strrchr(host, ':');
  69    if (s && strchr(host, ':') == s) {
  70      *s = 0;
  71      af = AF_INET;
  72    } else if (s && strchr(host, ':') != s) {
  73      af = AF_INET6;
  74      s = 0;
  75    }
  76  }
  77
  78  if (s++) {
  79    char *ss;
  80    unsigned long p = strtoul(s, &ss, 0);
  81    if (!*s || *ss || p > 65535) error_exit("bad port '%s'", s);
  82    port_num = htons(p);
  83  }
  84
  85  memset(&hints, 0 , sizeof(struct addrinfo));
  86  hints.ai_family = af;
  87  hints.ai_socktype = SOCK_STREAM;
  88
  89  status = getaddrinfo(host, NULL, &hints, &result);
  90  if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status));
  91
  92  memcpy(buf, result->ai_addr, result->ai_addrlen);
  93  freeaddrinfo(result);
  94
  95  if (af == AF_INET) ((struct sockaddr_in*)buf)->sin_port = port_num;
  96  else ((struct sockaddr_in6*)buf)->sin6_port = port_num;
  97}
  98
  99static int listen_socket(void)
 100{
 101  int s, af = AF_INET, yes = 1;
 102  char buf[sizeof(struct sockaddr_storage)];
 103
 104  memset(buf, 0, sizeof(buf));
 105  if (FLAG(b)) {
 106    get_sockaddr(TT.host_addr, buf);
 107    af = ((struct sockaddr *)buf)->sa_family;
 108  } else {
 109    ((struct sockaddr_in*)buf)->sin_port = htons(TT.port);
 110    ((struct sockaddr_in*)buf)->sin_family = af;
 111  }
 112  s = xsocket(af, SOCK_STREAM, 0);
 113  xsetsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
 114
 115  xbind(s, (struct sockaddr *)buf, ((af == AF_INET)?
 116          (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6))));
 117
 118  if (listen(s, 1) < 0) perror_exit("listen");
 119  return s;
 120}
 121
 122static void write_issue(char *tty)
 123{
 124  int size;
 125  char ch = 0;
 126  struct utsname u;
 127  int fd = open(TT.issue_path, O_RDONLY);
 128
 129  if (fd < 0) return ;
 130  uname(&u);
 131  while ((size = readall(fd, &ch, 1)) > 0) {
 132    if (ch == '\\' || ch == '%') {
 133      if (readall(fd, &ch, 1) <= 0) perror_exit("readall!");
 134      if (ch == 's') fputs(u.sysname, stdout);
 135      if (ch == 'n'|| ch == 'h') fputs(u.nodename, stdout);
 136      if (ch == 'r') fputs(u.release, stdout);
 137      if (ch == 'm') fputs(u.machine, stdout);
 138      if (ch == 'l') fputs(tty, stdout);
 139    }
 140    else if (ch == '\n') {
 141      fputs("\n\r\0", stdout);
 142    } else fputc(ch, stdout);
 143  }
 144  fflush(NULL);
 145  close(fd);
 146}
 147
 148static int new_session(int sockfd)
 149{
 150  char *argv_login[] = {NULL, "-h", NULL, NULL};
 151  char tty_name[30]; //tty name length.
 152  int fd, i = 1;
 153  char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS,
 154    IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA };
 155  struct sockaddr_storage sa;
 156  socklen_t sl = sizeof(sa);
 157
 158  setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
 159
 160  writeall(FLAG(i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
 161  if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) return fd;
 162  if (TT.fork_pid < 0) perror_exit("fork");
 163
 164  if (getpeername(sockfd, (void *)&sa, &sl)) perror_exit("getpeername");
 165  if (getnameinfo((void *)&sa, sl, toybuf, sizeof(toybuf), NULL, 0, NI_NUMERICHOST))
 166    perror_exit("getnameinfo");
 167
 168  write_issue(tty_name);
 169  argv_login[0] = TT.login_path;
 170  argv_login[2] = toybuf;
 171  execvp(argv_login[0], argv_login);
 172  exit(EXIT_FAILURE);
 173}
 174
 175static int handle_iacs(struct term_session *tm, int c, int fd)
 176{
 177  char *curr ,*start,*end;
 178  int i = 0;
 179
 180  curr = start = tm->buff2+tm->buff2_avail; 
 181  end = tm->buff2 + c -1;
 182  tm->rem = 0;
 183  while (curr <= end) {
 184    if (*curr != IAC){
 185
 186      if (*curr != '\r') {
 187        toybuf[i++] = *curr++;
 188        continue;
 189      } else {
 190        toybuf[i++] = *curr++;
 191        curr++;
 192        if (curr < end && (*curr == '\n' || *curr == '\0')) 
 193          curr++;
 194        continue;
 195      }
 196    }
 197
 198    if ((curr + 1) > end) {
 199      tm->rem = 1;
 200      break; 
 201    }
 202    if (*(curr+1) == IAC) { //IAC as data --> IAC IAC
 203      toybuf[i++] = *(curr+1);
 204      curr += 2; //IAC IAC --> 2 bytes
 205      continue;
 206    }
 207    if (*(curr + 1) == NOP || *(curr + 1) == SE) {
 208      curr += 2;
 209      continue;
 210    }
 211
 212    if (*(curr + 1) == SB ) {
 213      if (*(curr+2) == TELOPT_NAWS) {
 214        struct winsize ws;
 215        if ((curr+8) >= end) {  //ensure we have data to process.
 216          tm->rem = end - curr; 
 217          break;  
 218        }
 219        ws.ws_col = (curr[3] << 8) | curr[4];
 220        ws.ws_row = (curr[5] << 8) | curr[6];
 221        ioctl(fd, TIOCSWINSZ, (char *)&ws);
 222        curr += 9;
 223        continue;
 224      } else { //eat non-supported sub neg. options.
 225        curr++, tm->rem++;
 226        while (*curr != IAC && curr <= end) {
 227          curr++;
 228          tm->rem++;
 229        } 
 230        if (*curr == IAC) {
 231          tm->rem = 0;
 232          continue;
 233        } else break;
 234      }
 235    }
 236    curr += 3; //skip non-supported 3 bytes.
 237  }
 238  memcpy(start, toybuf, i);
 239  memcpy(start + i, end - tm->rem, tm->rem); //put remaining if we break;
 240  return i;
 241}
 242
 243static int dup_iacs(char *start, int fd, int len)
 244{
 245  char arr[] = {IAC, IAC};
 246  char *needle = NULL;
 247  int ret = 0, c, count = 0;
 248
 249  while (len) {
 250    if (*start == IAC) {
 251      count = writeall(fd, arr, sizeof(arr));
 252      if (count != 2) break; //short write
 253      start++;
 254      ret++;
 255      len--;
 256      continue;
 257    }
 258    needle = memchr(start, IAC, len);
 259    if (needle) c = needle - start;
 260    else c = len;
 261    count = writeall(fd, start, c);
 262    if (count < 0) break; 
 263    len -= count;
 264    ret += count;
 265    start += count;
 266  }
 267  return ret;
 268}
 269
 270void telnetd_main(void)
 271{
 272  fd_set rd, wr;
 273  struct term_session *tm = NULL;
 274  struct timeval tv, *tv_ptr = NULL;
 275  int pty_fd, new_fd, c = 0, w, master_fd = 0;
 276
 277  if (!FLAG(l)) TT.login_path = "/bin/login";
 278  if (!FLAG(f)) TT.issue_path = "/etc/issue.net";
 279  if (FLAG(w)) toys.optflags |= FLAG_F;
 280  if (!FLAG(i)) {
 281    master_fd = listen_socket();
 282    fcntl(master_fd, F_SETFD, FD_CLOEXEC);
 283    if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd;
 284    if (!FLAG(F)) daemon(0, 0);
 285  } else {
 286    pty_fd = new_session(master_fd); //master_fd = 0
 287    if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
 288    tm = xzalloc(sizeof(struct term_session));
 289    tm->child_pid = TT.fork_pid;
 290    tm->new_fd = 0;    
 291    tm->pty_fd = pty_fd;    
 292    if (session_list) {     
 293      tm->next = session_list;
 294      session_list = tm;    
 295    } else session_list = tm;
 296  }
 297
 298  if (FLAG(w) && !session_list) {
 299    tv.tv_sec = TT.w_sec;
 300    tv.tv_usec = 0;
 301    tv_ptr = &tv;
 302  }                
 303  signal(SIGCHLD, generic_signal);
 304
 305  for (;;) {
 306    FD_ZERO(&rd);
 307    FD_ZERO(&wr);
 308    if (!FLAG(i)) FD_SET(master_fd, &rd);
 309
 310    tm = session_list;
 311    while (tm) {
 312
 313      if (tm->pty_fd > 0 && tm->buff1_avail < BUFSIZE) FD_SET(tm->pty_fd, &rd);
 314      if (tm->new_fd >= 0 && tm->buff2_avail < BUFSIZE) FD_SET(tm->new_fd, &rd);
 315      if (tm->pty_fd > 0 && (tm->buff2_avail - tm->buff2_written) > 0)  
 316        FD_SET(tm->pty_fd, &wr);
 317      if (tm->new_fd >= 0 && (tm->buff1_avail - tm->buff1_written) > 0)  
 318        FD_SET(tm->new_fd, &wr);
 319      tm = tm->next;
 320    }
 321
 322
 323    int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr);
 324    if (!r) error_exit("select timed out");
 325    if (r < -1) continue;
 326
 327    if (!FLAG(i) && FD_ISSET(master_fd, &rd)) { //accept new connection
 328      new_fd = accept(master_fd, NULL, NULL);
 329      if (new_fd < 0) continue;
 330      tv_ptr = NULL;
 331      fcntl(new_fd, F_SETFD, FD_CLOEXEC);
 332      if (new_fd > TT.gmax_fd) TT.gmax_fd = new_fd;
 333      pty_fd = new_session(new_fd);
 334      if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
 335
 336      tm = xzalloc(sizeof(struct term_session));
 337      tm->child_pid = TT.fork_pid;
 338      tm->new_fd = new_fd;
 339      tm->pty_fd = pty_fd;
 340      if (session_list) {
 341        tm->next = session_list;
 342        session_list = tm;
 343      } else session_list = tm;
 344    }
 345
 346    tm = session_list;
 347    for (;tm;tm=tm->next) {
 348      if (FD_ISSET(tm->pty_fd, &rd)) {
 349        if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail,
 350                BUFSIZE-tm->buff1_avail)) <= 0) break;
 351        tm->buff1_avail += c;
 352        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + FLAG(i),
 353                tm->buff1_avail - tm->buff1_written)) < 0) break;
 354        tm->buff1_written += w;
 355      }
 356      if (FD_ISSET(tm->new_fd, &rd)) {
 357        if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail,
 358                BUFSIZE-tm->buff2_avail)) <= 0) {
 359          // The other side went away without a proper shutdown. Happens if
 360          // you exit telnet via ^]^D, leaving the socket in TIME_WAIT.
 361          xclose(tm->new_fd);
 362          tm->new_fd = -1;
 363          xclose(tm->pty_fd);
 364          tm->pty_fd = -1;
 365          break;
 366        }
 367        c = handle_iacs(tm, c, tm->pty_fd);
 368        tm->buff2_avail += c;
 369        if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written, 
 370                tm->buff2_avail - tm->buff2_written)) < 0) break;
 371        tm->buff2_written += w;
 372      }
 373      if (FD_ISSET(tm->pty_fd, &wr)) {
 374        if ((w = write(tm->pty_fd,  tm->buff2 + tm->buff2_written, 
 375                tm->buff2_avail - tm->buff2_written)) < 0) break;
 376        tm->buff2_written += w;
 377      }
 378      if (FD_ISSET(tm->new_fd, &wr)) {
 379        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + FLAG(i),
 380                tm->buff1_avail - tm->buff1_written)) < 0) break;
 381        tm->buff1_written += w;
 382      }
 383      if (tm->buff1_written == tm->buff1_avail)
 384        tm->buff1_written = tm->buff1_avail = 0;
 385      if (tm->buff2_written == tm->buff2_avail)
 386        tm->buff2_written = tm->buff2_avail = 0;
 387      fflush(NULL);
 388    }
 389
 390    // Loop to handle (unknown number of) SIGCHLD notifications
 391    while (toys.signal) {
 392      int status;
 393      struct term_session *prev = NULL;
 394      pid_t pid;
 395
 396      // funny little dance to avoid race conditions.
 397      toys.signal = 0;
 398      pid = waitpid(-1, &status, WNOHANG);
 399      if (pid <= 0) break;
 400      toys.signal++;
 401
 402      for (tm = session_list; tm; tm = tm->next) {
 403        if (tm->child_pid == pid) break;
 404        prev = tm;
 405      }
 406      if (!tm) error_exit("unexpected reparenting of %d", pid);
 407
 408      if (FLAG(i)) exit(EXIT_SUCCESS);
 409
 410      if (!prev) session_list = session_list->next;
 411      else prev->next = tm->next;
 412      xclose(tm->pty_fd);
 413      xclose(tm->new_fd);
 414      free(tm);
 415    }
 416  }
 417}
 418