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 <utmp.h>
  28GLOBALS(
  29    char *login_path;
  30    char *issue_path;
  31    int port;
  32    char *host_addr;
  33    long w_sec;
  34
  35    int gmax_fd;
  36    pid_t fork_pid;
  37)
  38
  39
  40# define IAC         255  /* interpret as command: */
  41# define DONT        254  /* you are not to use option */
  42# define DO          253  /* please, you use option */
  43# define WONT        252  /* I won't use option */
  44# define WILL        251  /* I will use option */
  45# define SB          250  /* interpret as subnegotiation */
  46# define SE          240  /* end sub negotiation */
  47# define NOP         241  /* No Operation */
  48# define TELOPT_ECHO   1  /* echo */
  49# define TELOPT_SGA    3  /* suppress go ahead */
  50# define TELOPT_TTYPE 24  /* terminal type */
  51# define TELOPT_NAWS  31  /* window size */
  52
  53#define BUFSIZE 4*1024
  54struct term_session {
  55  int new_fd, pty_fd;
  56  pid_t child_pid;
  57  int buff1_avail, buff2_avail;
  58  int buff1_written, buff2_written;
  59  int rem;  //unprocessed data from socket
  60  char buff1[BUFSIZE], buff2[BUFSIZE];
  61  struct term_session *next;
  62};
  63
  64struct term_session *session_list = NULL;
  65
  66static void get_sockaddr(char *host, void *buf)
  67{
  68  in_port_t port_num = htons(TT.port);
  69  struct addrinfo hints, *result;
  70  int status, af = AF_UNSPEC;
  71  char *s;
  72
  73  // [ipv6]:port or exactly one :
  74  if (*host == '[') {
  75    host++;
  76    s = strchr(host, ']');
  77    if (s) *s++ = 0;
  78    else error_exit("bad address '%s'", host-1);
  79    af = AF_INET6;
  80  } else {
  81    s = strrchr(host, ':');
  82    if (s && strchr(host, ':') == s) {
  83      *s = 0;
  84      af = AF_INET;
  85    } else if (s && strchr(host, ':') != s) {
  86      af = AF_INET6;
  87      s = 0;
  88    }
  89  }
  90
  91  if (s++) {
  92    char *ss;
  93    unsigned long p = strtoul(s, &ss, 0);
  94    if (!*s || *ss || p > 65535) error_exit("bad port '%s'", s);
  95    port_num = htons(p);
  96  }
  97
  98  memset(&hints, 0 , sizeof(struct addrinfo));
  99  hints.ai_family = af;
 100  hints.ai_socktype = SOCK_STREAM;
 101
 102  status = getaddrinfo(host, NULL, &hints, &result);
 103  if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status));
 104
 105  memcpy(buf, result->ai_addr, result->ai_addrlen);
 106  freeaddrinfo(result);
 107
 108  if (af == AF_INET) ((struct sockaddr_in*)buf)->sin_port = port_num;
 109  else ((struct sockaddr_in6*)buf)->sin6_port = port_num;
 110}
 111
 112static void utmp_entry(void)
 113{               
 114  struct utmp entry;
 115  struct utmp *utp_ptr;
 116  pid_t pid = getpid();
 117
 118  utmpname(_PATH_UTMP);
 119  setutent(); //start from start
 120  while ((utp_ptr = getutent()) != NULL) {
 121    if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break;
 122  }             
 123  if (!utp_ptr) entry.ut_type = DEAD_PROCESS;
 124  time(&entry.ut_time);  
 125  setutent();   
 126  pututline(&entry);     
 127}
 128
 129static int listen_socket(void)
 130{
 131  int s, af = AF_INET, yes = 1;
 132  char buf[sizeof(struct sockaddr_storage)];
 133
 134  memset(buf, 0, sizeof(buf));
 135  if (toys.optflags & FLAG_b) {
 136    get_sockaddr(TT.host_addr, buf);
 137    af = ((struct sockaddr *)buf)->sa_family;
 138  } else {
 139    ((struct sockaddr_in*)buf)->sin_port = htons(TT.port);
 140    ((struct sockaddr_in*)buf)->sin_family = af;
 141  }
 142  s = xsocket(af, SOCK_STREAM, 0);
 143  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) == -1) 
 144    perror_exit("setsockopt");
 145
 146  xbind(s, (struct sockaddr *)buf, ((af == AF_INET)?
 147          (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6))));
 148
 149  if (listen(s, 1) < 0) perror_exit("listen");
 150  return s;
 151}
 152
 153static void write_issue(char *tty)
 154{
 155  int size;
 156  char ch = 0;
 157  struct utsname u;
 158  int fd = open(TT.issue_path, O_RDONLY);
 159
 160  if (fd < 0) return ;
 161  uname(&u);
 162  while ((size = readall(fd, &ch, 1)) > 0) {
 163    if (ch == '\\' || ch == '%') {
 164      if (readall(fd, &ch, 1) <= 0) perror_exit("readall!");
 165      if (ch == 's') fputs(u.sysname, stdout);
 166      if (ch == 'n'|| ch == 'h') fputs(u.nodename, stdout);
 167      if (ch == 'r') fputs(u.release, stdout);
 168      if (ch == 'm') fputs(u.machine, stdout);
 169      if (ch == 'l') fputs(tty, stdout);
 170    }
 171    else if (ch == '\n') {
 172      fputs("\n\r\0", stdout);
 173    } else fputc(ch, stdout);
 174  }
 175  fflush(NULL);
 176  close(fd);
 177}
 178
 179static int new_session(int sockfd)
 180{
 181  char *argv_login[2]; //arguments for execvp cmd, NULL
 182  char tty_name[30]; //tty name length.
 183  int fd, flags, i = 1;
 184  char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS,
 185    IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA };
 186
 187  setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
 188  flags = fcntl(sockfd, F_GETFL);
 189  fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
 190  if (toys.optflags & FLAG_i) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK);
 191
 192  writeall((toys.optflags & FLAG_i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
 193  if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) {
 194    flags = fcntl(fd, F_GETFL);
 195    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
 196    return fd;
 197  }
 198  if (TT.fork_pid < 0) perror_exit("fork");
 199  write_issue(tty_name);
 200  argv_login[0] = strdup(TT.login_path);
 201  argv_login[1] = NULL;
 202  execvp(argv_login[0], argv_login);
 203  exit(EXIT_FAILURE);
 204}
 205
 206static int handle_iacs(struct term_session *tm, int c, int fd)
 207{
 208  char *curr ,*start,*end;
 209  int i = 0;
 210
 211  curr = start = tm->buff2+tm->buff2_avail; 
 212  end = tm->buff2 + c -1;
 213  tm->rem = 0;
 214  while (curr <= end) {
 215    if (*curr != IAC){
 216
 217      if (*curr != '\r') {
 218        toybuf[i++] = *curr++;
 219        continue;
 220      } else {
 221        toybuf[i++] = *curr++;
 222        curr++;
 223        if (curr < end && (*curr == '\n' || *curr == '\0')) 
 224          curr++;
 225        continue;
 226      }
 227    }
 228
 229    if ((curr + 1) > end) {
 230      tm->rem = 1;
 231      break; 
 232    }
 233    if (*(curr+1) == IAC) { //IAC as data --> IAC IAC
 234      toybuf[i++] = *(curr+1);
 235      curr += 2; //IAC IAC --> 2 bytes
 236      continue;
 237    }
 238    if (*(curr + 1) == NOP || *(curr + 1) == SE) {
 239      curr += 2;
 240      continue;
 241    }
 242
 243    if (*(curr + 1) == SB ) {
 244      if (*(curr+2) == TELOPT_NAWS) {
 245        struct winsize ws;
 246        if ((curr+8) >= end) {  //ensure we have data to process.
 247          tm->rem = end - curr; 
 248          break;  
 249        }
 250        ws.ws_col = (curr[3] << 8) | curr[4];
 251        ws.ws_row = (curr[5] << 8) | curr[6];
 252        ioctl(fd, TIOCSWINSZ, (char *)&ws);
 253        curr += 9;
 254        continue;
 255      } else { //eat non-supported sub neg. options.
 256        curr++, tm->rem++;
 257        while (*curr != IAC && curr <= end) {
 258          curr++;
 259          tm->rem++;
 260        } 
 261        if (*curr == IAC) {
 262          tm->rem = 0;
 263          continue;
 264        } else break;
 265      }
 266    }
 267    curr += 3; //skip non-supported 3 bytes.
 268  }
 269  memcpy(start, toybuf, i);
 270  memcpy(start + i, end - tm->rem, tm->rem); //put remaining if we break;
 271  return i;
 272}
 273
 274static int dup_iacs(char *start, int fd, int len)
 275{
 276  char arr[] = {IAC, IAC};
 277  char *needle = NULL;
 278  int ret = 0, c, count = 0;
 279
 280  while (len) {
 281    if (*start == IAC) {
 282      count = writeall(fd, arr, sizeof(arr));
 283      if (count != 2) break; //short write
 284      start++;
 285      ret++;
 286      len--;
 287      continue;
 288    }
 289    needle = memchr(start, IAC, len);
 290    if (needle) c = needle - start;
 291    else c = len;
 292    count = writeall(fd, start, c);
 293    if (count < 0) break; 
 294    len -= count;
 295    ret += count;
 296    start += count;
 297  }
 298  return ret;
 299}
 300
 301void telnetd_main(void)
 302{
 303  errno = 0;
 304  fd_set rd, wr;
 305  struct term_session *tm = NULL;
 306  struct timeval tv, *tv_ptr = NULL;
 307  int pty_fd, new_fd, c = 0, w, master_fd = 0;
 308  int inetd_m = toys.optflags & FLAG_i;
 309
 310  if (!(toys.optflags & FLAG_l)) TT.login_path = "/bin/login";
 311  if (!(toys.optflags & FLAG_f)) TT.issue_path = "/etc/issue.net";
 312  if (toys.optflags & FLAG_w) toys.optflags |= FLAG_F;
 313  if (!inetd_m) {
 314    master_fd = listen_socket();
 315    fcntl(master_fd, F_SETFD, FD_CLOEXEC);
 316    if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd;
 317    if (!(toys.optflags & FLAG_F)) daemon(0, 0); 
 318  } else {
 319    pty_fd = new_session(master_fd); //master_fd = 0
 320    if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
 321    tm = xzalloc(sizeof(struct term_session));
 322    tm->child_pid = TT.fork_pid;
 323    tm->new_fd = 0;    
 324    tm->pty_fd = pty_fd;    
 325    if (session_list) {     
 326      tm->next = session_list;
 327      session_list = tm;    
 328    } else session_list = tm;
 329  }
 330
 331  if ((toys.optflags & FLAG_w) && !session_list) {
 332    tv.tv_sec = TT.w_sec;
 333    tv.tv_usec = 0;
 334    tv_ptr = &tv;
 335  }                
 336  signal(SIGCHLD, generic_signal);
 337
 338  for (;;) {
 339    FD_ZERO(&rd);
 340    FD_ZERO(&wr);
 341    if (!inetd_m) FD_SET(master_fd, &rd);
 342
 343    tm = session_list;
 344    while (tm) {
 345
 346      if (tm->pty_fd > 0 && tm->buff1_avail < BUFSIZE) FD_SET(tm->pty_fd, &rd);
 347      if (tm->new_fd >= 0 && tm->buff2_avail < BUFSIZE) FD_SET(tm->new_fd, &rd);
 348      if (tm->pty_fd > 0 && (tm->buff2_avail - tm->buff2_written) > 0)  
 349        FD_SET(tm->pty_fd, &wr);
 350      if (tm->new_fd >= 0 && (tm->buff1_avail - tm->buff1_written) > 0)  
 351        FD_SET(tm->new_fd, &wr);
 352      tm = tm->next;
 353    }
 354
 355
 356    int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr);
 357    if (!r) return; //timeout
 358    if (r < -1) continue;
 359
 360    if (!inetd_m && FD_ISSET(master_fd, &rd)) { //accept new connection
 361      new_fd = accept(master_fd, NULL, NULL);
 362      if (new_fd < 0) continue;
 363      tv_ptr = NULL;
 364      fcntl(new_fd, F_SETFD, FD_CLOEXEC);
 365      if (new_fd > TT.gmax_fd) TT.gmax_fd = new_fd;
 366      pty_fd = new_session(new_fd);
 367      if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
 368
 369      tm = xzalloc(sizeof(struct term_session));
 370      tm->child_pid = TT.fork_pid;
 371      tm->new_fd = new_fd;
 372      tm->pty_fd = pty_fd;
 373      if (session_list) {
 374        tm->next = session_list;
 375        session_list = tm;
 376      } else session_list = tm;
 377    }
 378
 379    tm = session_list;
 380    for (;tm;tm=tm->next) {
 381      if (FD_ISSET(tm->pty_fd, &rd)) {
 382        if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail,
 383                BUFSIZE-tm->buff1_avail)) <= 0) break;
 384        tm->buff1_avail += c;
 385        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
 386                tm->buff1_avail - tm->buff1_written)) < 0) break;
 387        tm->buff1_written += w;
 388      }
 389      if (FD_ISSET(tm->new_fd, &rd)) {
 390        if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail,
 391                BUFSIZE-tm->buff2_avail)) <= 0) break;
 392        c = handle_iacs(tm, c, tm->pty_fd);
 393        tm->buff2_avail += c;
 394        if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written, 
 395                tm->buff2_avail - tm->buff2_written)) < 0) break;
 396        tm->buff2_written += w;
 397      }
 398      if (FD_ISSET(tm->pty_fd, &wr)) {
 399        if ((w = write(tm->pty_fd,  tm->buff2 + tm->buff2_written, 
 400                tm->buff2_avail - tm->buff2_written)) < 0) break;
 401        tm->buff2_written += w;
 402      }
 403      if (FD_ISSET(tm->new_fd, &wr)) {
 404        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
 405                tm->buff1_avail - tm->buff1_written)) < 0) break;
 406        tm->buff1_written += w;
 407      }
 408      if (tm->buff1_written == tm->buff1_avail)
 409        tm->buff1_written = tm->buff1_avail = 0;
 410      if (tm->buff2_written == tm->buff2_avail)
 411        tm->buff2_written = tm->buff2_avail = 0;
 412      fflush(NULL);
 413    }
 414
 415    // Loop to handle (unknown number of) SIGCHLD notifications
 416    while (toys.signal) {
 417      int status;
 418      struct term_session *prev = NULL;
 419      pid_t pid;
 420
 421      // funny little dance to avoid race conditions.
 422      toys.signal = 0;
 423      pid = waitpid(-1, &status, WNOHANG);
 424      if (pid < 0) break;
 425      toys.signal++;
 426
 427
 428      for (tm = session_list; tm; tm = tm->next) {
 429        if (tm->child_pid == pid) break;
 430        prev = tm;
 431      }
 432      if (!tm) return; // reparented child we don't care about
 433
 434      if (toys.optflags & FLAG_i) exit(EXIT_SUCCESS);
 435      if (!prev) session_list = session_list->next;
 436      else prev->next = tm->next;
 437      utmp_entry();
 438      xclose(tm->pty_fd);
 439      xclose(tm->new_fd);
 440      free(tm);
 441    }
 442  }
 443}
 444