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  if (bind(s, (struct sockaddr *)buf, ((af == AF_INET)?
147          (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6)))) == -1) {
148    close(s);
149    perror_exit("bind");
150  }
151
152  if (listen(s, 1) < 0) perror_exit("listen");
153  return s;
154}
155
156static void write_issue(char *tty)
157{
158  int size;
159  char ch = 0;
160  struct utsname u;
161  int fd = open(TT.issue_path, O_RDONLY);
162
163  if (fd < 0) return ;
164  uname(&u);
165  while ((size = readall(fd, &ch, 1)) > 0) {
166    if (ch == '\\' || ch == '%') {
167      if (readall(fd, &ch, 1) <= 0) perror_exit("readall!");
168      if (ch == 's') fputs(u.sysname, stdout);
169      if (ch == 'n'|| ch == 'h') fputs(u.nodename, stdout);
170      if (ch == 'r') fputs(u.release, stdout);
171      if (ch == 'm') fputs(u.machine, stdout);
172      if (ch == 'l') fputs(tty, stdout);
173    }
174    else if (ch == '\n') {
175      fputs("\n\r\0", stdout);
176    } else fputc(ch, stdout);
177  }
178  fflush(NULL);
179  close(fd);
180}
181
182static int new_session(int sockfd)
183{
184  char *argv_login[2]; //arguments for execvp cmd, NULL
185  char tty_name[30]; //tty name length.
186  int fd, flags, i = 1;
187  char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS,
188    IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA };
189
190  setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
191  flags = fcntl(sockfd, F_GETFL);
192  fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
193  if (toys.optflags & FLAG_i) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK);
194
195  writeall((toys.optflags & FLAG_i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
196  if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) {
197    flags = fcntl(fd, F_GETFL);
198    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
199    return fd;
200  }
201  if (TT.fork_pid < 0) perror_exit("fork");
202  write_issue(tty_name);
203  argv_login[0] = strdup(TT.login_path);
204  argv_login[1] = NULL;
205  execvp(argv_login[0], argv_login);
206  exit(EXIT_FAILURE);
207}
208
209static int handle_iacs(struct term_session *tm, int c, int fd)
210{
211  char *curr ,*start,*end;
212  int i = 0;
213
214  curr = start = tm->buff2+tm->buff2_avail;
215  end = tm->buff2 + c -1;
216  tm->rem = 0;
217  while (curr <= end) {
218    if (*curr != IAC){
219
220      if (*curr != '\r') {
221        toybuf[i++] = *curr++;
222        continue;
223      } else {
224        toybuf[i++] = *curr++;
225        curr++;
226        if (curr < end && (*curr == '\n' || *curr == '\0'))
227          curr++;
228        continue;
229      }
230    }
231
232    if ((curr + 1) > end) {
233      tm->rem = 1;
234      break;
235    }
236    if (*(curr+1) == IAC) { //IAC as data --> IAC IAC
237      toybuf[i++] = *(curr+1);
238      curr += 2; //IAC IAC --> 2 bytes
239      continue;
240    }
241    if (*(curr + 1) == NOP || *(curr + 1) == SE) {
242      curr += 2;
243      continue;
244    }
245
246    if (*(curr + 1) == SB ) {
247      if (*(curr+2) == TELOPT_NAWS) {
248        struct winsize ws;
249        if ((curr+8) >= end) {  //ensure we have data to process.
250          tm->rem = end - curr;
251          break;
252        }
253        ws.ws_col = (curr[3] << 8) | curr[4];
254        ws.ws_row = (curr[5] << 8) | curr[6];
255        ioctl(fd, TIOCSWINSZ, (char *)&ws);
256        curr += 9;
257        continue;
258      } else { //eat non-supported sub neg. options.
259        curr++, tm->rem++;
260        while (*curr != IAC && curr <= end) {
261          curr++;
262          tm->rem++;
263        }
264        if (*curr == IAC) {
265          tm->rem = 0;
266          continue;
267        } else break;
268      }
269    }
270    curr += 3; //skip non-supported 3 bytes.
271  }
272  memcpy(start, toybuf, i);
273  memcpy(start + i, end - tm->rem, tm->rem); //put remaining if we break;
274  return i;
275}
276
277static int dup_iacs(char *start, int fd, int len)
278{
279  char arr[] = {IAC, IAC};
280  char *needle = NULL;
281  int ret = 0, c, count = 0;
282
283  while (len) {
284    if (*start == IAC) {
285      count = writeall(fd, arr, sizeof(arr));
286      if (count != 2) break; //short write
287      start++;
288      ret++;
289      len--;
290      continue;
291    }
292    needle = memchr(start, IAC, len);
293    if (needle) c = needle - start;
294    else c = len;
295    count = writeall(fd, start, c);
296    if (count < 0) break;
297    len -= count;
298    ret += count;
299    start += count;
300  }
301  return ret;
302}
303
304void telnetd_main(void)
305{
306  errno = 0;
307  fd_set rd, wr;
308  struct term_session *tm = NULL;
309  struct timeval tv, *tv_ptr = NULL;
310  int pty_fd, new_fd, c = 0, w, master_fd = 0;
311  int inetd_m = toys.optflags & FLAG_i;
312
313  if (!(toys.optflags & FLAG_l)) TT.login_path = "/bin/login";
314  if (!(toys.optflags & FLAG_f)) TT.issue_path = "/etc/issue.net";
315  if (toys.optflags & FLAG_w) toys.optflags |= FLAG_F;
316  if (!inetd_m) {
317    master_fd = listen_socket();
318    fcntl(master_fd, F_SETFD, FD_CLOEXEC);
319    if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd;
320    if (!(toys.optflags & FLAG_F)) daemon(0, 0);
321  } else {
322    pty_fd = new_session(master_fd); //master_fd = 0
323    if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
324    tm = xzalloc(sizeof(struct term_session));
325    tm->child_pid = TT.fork_pid;
326    tm->new_fd = 0;
327    tm->pty_fd = pty_fd;
328    if (session_list) {
329      tm->next = session_list;
330      session_list = tm;
331    } else session_list = tm;
332  }
333
334  if ((toys.optflags & FLAG_w) && !session_list) {
335    tv.tv_sec = TT.w_sec;
336    tv.tv_usec = 0;
337    tv_ptr = &tv;
338  }
339  signal(SIGCHLD, generic_signal);
340
341  for (;;) {
342    FD_ZERO(&rd);
343    FD_ZERO(&wr);
344    if (!inetd_m) FD_SET(master_fd, &rd);
345
346    tm = session_list;
347    while (tm) {
348
349      if (tm->pty_fd > 0 && tm->buff1_avail < BUFSIZE) FD_SET(tm->pty_fd, &rd);
350      if (tm->new_fd >= 0 && tm->buff2_avail < BUFSIZE) FD_SET(tm->new_fd, &rd);
351      if (tm->pty_fd > 0 && (tm->buff2_avail - tm->buff2_written) > 0)
352        FD_SET(tm->pty_fd, &wr);
353      if (tm->new_fd >= 0 && (tm->buff1_avail - tm->buff1_written) > 0)
354        FD_SET(tm->new_fd, &wr);
355      tm = tm->next;
356    }
357
358
359    int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr);
360    if (!r) return; //timeout
361    if (r < -1) continue;
362
363    if (!inetd_m && FD_ISSET(master_fd, &rd)) { //accept new connection
364      new_fd = accept(master_fd, NULL, NULL);
365      if (new_fd < 0) continue;
366      tv_ptr = NULL;
367      fcntl(new_fd, F_SETFD, FD_CLOEXEC);
368      if (new_fd > TT.gmax_fd) TT.gmax_fd = new_fd;
369      pty_fd = new_session(new_fd);
370      if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
371
372      tm = xzalloc(sizeof(struct term_session));
373      tm->child_pid = TT.fork_pid;
374      tm->new_fd = new_fd;
375      tm->pty_fd = pty_fd;
376      if (session_list) {
377        tm->next = session_list;
378        session_list = tm;
379      } else session_list = tm;
380    }
381
382    tm = session_list;
383    for (;tm;tm=tm->next) {
384      if (FD_ISSET(tm->pty_fd, &rd)) {
385        if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail,
386                BUFSIZE-tm->buff1_avail)) <= 0) break;
387        tm->buff1_avail += c;
388        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
389                tm->buff1_avail - tm->buff1_written)) < 0) break;
390        tm->buff1_written += w;
391      }
392      if (FD_ISSET(tm->new_fd, &rd)) {
393        if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail,
394                BUFSIZE-tm->buff2_avail)) <= 0) break;
395        c = handle_iacs(tm, c, tm->pty_fd);
396        tm->buff2_avail += c;
397        if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written,
398                tm->buff2_avail - tm->buff2_written)) < 0) break;
399        tm->buff2_written += w;
400      }
401      if (FD_ISSET(tm->pty_fd, &wr)) {
402        if ((w = write(tm->pty_fd,  tm->buff2 + tm->buff2_written,
403                tm->buff2_avail - tm->buff2_written)) < 0) break;
404        tm->buff2_written += w;
405      }
406      if (FD_ISSET(tm->new_fd, &wr)) {
407        if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
408                tm->buff1_avail - tm->buff1_written)) < 0) break;
409        tm->buff1_written += w;
410      }
411      if (tm->buff1_written == tm->buff1_avail)
412        tm->buff1_written = tm->buff1_avail = 0;
413      if (tm->buff2_written == tm->buff2_avail)
414        tm->buff2_written = tm->buff2_avail = 0;
415      fflush(NULL);
416    }
417
418    // Loop to handle (unknown number of) SIGCHLD notifications
419    while (toys.signal) {
420      int status;
421      struct term_session *prev = NULL;
422      pid_t pid;
423
424      // funny little dance to avoid race conditions.
425      toys.signal = 0;
426      pid = waitpid(-1, &status, WNOHANG);
427      if (pid < 0) break;
428      toys.signal++;
429
430
431      for (tm = session_list; tm; tm = tm->next) {
432        if (tm->child_pid == pid) break;
433        prev = tm;
434      }
435      if (!tm) return; // reparented child we don't care about
436
437      if (toys.optflags & FLAG_i) exit(EXIT_SUCCESS);
438      if (!prev) session_list = session_list->next;
439      else prev->next = tm->next;
440      utmp_entry();
441      xclose(tm->pty_fd);
442      xclose(tm->new_fd);
443      free(tm);
444    }
445  }
446}
447