cccf03dfd54944bd88ff059eab52392117511db5
[oweals/busybox.git] / networking / telnetd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Simple telnet server
4  * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
5  *
6  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
7  *
8  * ---------------------------------------------------------------------------
9  * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
10  ****************************************************************************
11  *
12  * The telnetd manpage says it all:
13  *
14  *   Telnetd operates by allocating a pseudo-terminal device (see pty(4))  for
15  *   a client, then creating a login process which has the slave side of the
16  *   pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
17  *   master side of the pseudo-terminal, implementing the telnet protocol and
18  *   passing characters between the remote client and the login process.
19  *
20  * Vladimir Oleynik <dzo@simtreas.ru> 2001
21  *     Set process group corrections, initial busybox port
22  */
23
24 #define DEBUG 0
25
26 #include "libbb.h"
27
28 #if DEBUG
29 #define TELCMDS
30 #define TELOPTS
31 #endif
32 #include <arpa/telnet.h>
33 #include <sys/syslog.h>
34
35
36 /* Structure that describes a session */
37 struct tsession {
38         struct tsession *next;
39         int sockfd_read, sockfd_write, ptyfd;
40         int shell_pid;
41
42         /* two circular buffers */
43         /*char *buf1, *buf2;*/
44 /*#define TS_BUF1 ts->buf1*/
45 /*#define TS_BUF2 TS_BUF2*/
46 #define TS_BUF1 ((unsigned char*)(ts + 1))
47 #define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE)
48         int rdidx1, wridx1, size1;
49         int rdidx2, wridx2, size2;
50 };
51
52 /* Two buffers are directly after tsession in malloced memory.
53  * Make whole thing fit in 4k */
54 enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
55
56
57 /* Globals */
58 static int maxfd;
59 static struct tsession *sessions;
60 #if ENABLE_LOGIN
61 static const char *loginpath = "/bin/login";
62 #else
63 static const char *loginpath = DEFAULT_SHELL;
64 #endif
65 static const char *issuefile = "/etc/issue.net";
66
67
68 /*
69    Remove all IAC's from buf1 (received IACs are ignored and must be removed
70    so as to not be interpreted by the terminal).  Make an uninterrupted
71    string of characters fit for the terminal.  Do this by packing
72    all characters meant for the terminal sequentially towards the end of buf.
73
74    Return a pointer to the beginning of the characters meant for the terminal.
75    and make *num_totty the number of characters that should be sent to
76    the terminal.
77
78    Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
79    past (bf + len) then that IAC will be left unprocessed and *processed
80    will be less than len.
81
82    FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
83    what is the escape character?  We aren't handling that situation here.
84
85    CR-LF ->'s CR mapping is also done here, for convenience.
86
87    NB: may fail to remove iacs which wrap around buffer!
88  */
89 static unsigned char *
90 remove_iacs(struct tsession *ts, int *pnum_totty)
91 {
92         unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
93         unsigned char *ptr = ptr0;
94         unsigned char *totty = ptr;
95         unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
96         int num_totty;
97
98         while (ptr < end) {
99                 if (*ptr != IAC) {
100                         char c = *ptr;
101
102                         *totty++ = c;
103                         ptr++;
104                         /* We now map \r\n ==> \r for pragmatic reasons.
105                          * Many client implementations send \r\n when
106                          * the user hits the CarriageReturn key.
107                          */
108                         if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
109                                 ptr++;
110                 } else {
111                         /*
112                          * TELOPT_NAWS support!
113                          */
114                         if ((ptr+2) >= end) {
115                                 /* only the beginning of the IAC is in the
116                                 buffer we were asked to process, we can't
117                                 process this char. */
118                                 break;
119                         }
120
121                         /*
122                          * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
123                          */
124                         else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
125                                 struct winsize ws;
126
127                                 if ((ptr+8) >= end)
128                                         break;  /* incomplete, can't process */
129                                 ws.ws_col = (ptr[3] << 8) | ptr[4];
130                                 ws.ws_row = (ptr[5] << 8) | ptr[6];
131                                 ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
132                                 ptr += 9;
133                         } else {
134                                 /* skip 3-byte IAC non-SB cmd */
135 #if DEBUG
136                                 fprintf(stderr, "Ignoring IAC %s,%s\n",
137                                         TELCMD(ptr[1]), TELOPT(ptr[2]));
138 #endif
139                                 ptr += 3;
140                         }
141                 }
142         }
143
144         num_totty = totty - ptr0;
145         *pnum_totty = num_totty;
146         /* the difference between ptr and totty is number of iacs
147            we removed from the stream. Adjust buf1 accordingly. */
148         if ((ptr - totty) == 0) /* 99.999% of cases */
149                 return ptr0;
150         ts->wridx1 += ptr - totty;
151         ts->size1 -= ptr - totty;
152         /* move chars meant for the terminal towards the end of the buffer */
153         return memmove(ptr - num_totty, ptr0, num_totty);
154 }
155
156
157 static int
158 getpty(char *line, int size)
159 {
160         int p;
161 #if ENABLE_FEATURE_DEVPTS
162         p = open("/dev/ptmx", O_RDWR);
163         if (p > 0) {
164                 const char *name;
165                 grantpt(p);
166                 unlockpt(p);
167                 name = ptsname(p);
168                 if (!name) {
169                         bb_perror_msg("ptsname error (is /dev/pts mounted?)");
170                         return -1;
171                 }
172                 safe_strncpy(line, name, size);
173                 return p;
174         }
175 #else
176         struct stat stb;
177         int i;
178         int j;
179
180         strcpy(line, "/dev/ptyXX");
181
182         for (i = 0; i < 16; i++) {
183                 line[8] = "pqrstuvwxyzabcde"[i];
184                 line[9] = '0';
185                 if (stat(line, &stb) < 0) {
186                         continue;
187                 }
188                 for (j = 0; j < 16; j++) {
189                         line[9] = j < 10 ? j + '0' : j - 10 + 'a';
190                         if (DEBUG)
191                                 fprintf(stderr, "Trying to open device: %s\n", line);
192                         p = open(line, O_RDWR | O_NOCTTY);
193                         if (p >= 0) {
194                                 line[5] = 't';
195                                 return p;
196                         }
197                 }
198         }
199 #endif /* FEATURE_DEVPTS */
200         return -1;
201 }
202
203
204 static struct tsession *
205 make_new_session(
206                 USE_FEATURE_TELNETD_STANDALONE(int sock)
207                 SKIP_FEATURE_TELNETD_STANDALONE(void)
208 ) {
209         const char *login_argv[2];
210         struct termios termbuf;
211         int fd, pid;
212         char tty_name[32];
213         struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
214
215         /*ts->buf1 = (char *)(ts + 1);*/
216         /*ts->buf2 = ts->buf1 + BUFSIZE;*/
217
218         /* Got a new connection, set up a tty. */
219         fd = getpty(tty_name, 32);
220         if (fd < 0) {
221                 bb_error_msg("can't create pty");
222                 return NULL;
223         }
224         if (fd > maxfd)
225                 maxfd = fd;
226         ts->ptyfd = fd;
227         ndelay_on(fd);
228 #if ENABLE_FEATURE_TELNETD_STANDALONE
229         ts->sockfd_read = sock;
230         ndelay_on(sock);
231         if (!sock) { /* We are called with fd 0 - we are in inetd mode */
232                 sock++; /* so use fd 1 for output */
233                 ndelay_on(sock);
234         }
235         ts->sockfd_write = sock;
236         if (sock > maxfd)
237                 maxfd = sock;
238 #else
239         /* ts->sockfd_read = 0; - done by xzalloc */
240         ts->sockfd_write = 1;
241         ndelay_on(0);
242         ndelay_on(1);
243 #endif
244         /* Make the telnet client understand we will echo characters so it
245          * should not do it locally. We don't tell the client to run linemode,
246          * because we want to handle line editing and tab completion and other
247          * stuff that requires char-by-char support. */
248         {
249                 static const char iacs_to_send[] ALIGN1 = {
250                         IAC, DO, TELOPT_ECHO,
251                         IAC, DO, TELOPT_NAWS,
252                         IAC, DO, TELOPT_LFLOW,
253                         IAC, WILL, TELOPT_ECHO,
254                         IAC, WILL, TELOPT_SGA
255                 };
256                 memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
257                 ts->rdidx2 = sizeof(iacs_to_send);
258                 ts->size2 = sizeof(iacs_to_send);
259         }
260
261         fflush(NULL); /* flush all streams */
262         pid = vfork(); /* NOMMU-friendly */
263         if (pid < 0) {
264                 free(ts);
265                 close(fd);
266                 /* sock will be closed by caller */
267                 bb_perror_msg("vfork");
268                 return NULL;
269         }
270         if (pid > 0) {
271                 /* Parent */
272                 ts->shell_pid = pid;
273                 return ts;
274         }
275
276         /* Child */
277         /* Careful - we are after vfork! */
278
279         /* make new session and process group */
280         setsid();
281
282         /* open the child's side of the tty. */
283         /* NB: setsid() disconnects from any previous ctty's. Therefore
284          * we must open child's side of the tty AFTER setsid! */
285         fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
286         dup2(fd, 0);
287         dup2(fd, 1);
288         dup2(fd, 2);
289         while (fd > 2) close(fd--);
290         tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
291
292         /* The pseudo-terminal allocated to the client is configured to operate in
293          * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
294         tcgetattr(0, &termbuf);
295         termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
296         termbuf.c_oflag |= ONLCR | XTABS;
297         termbuf.c_iflag |= ICRNL;
298         termbuf.c_iflag &= ~IXOFF;
299         /*termbuf.c_lflag &= ~ICANON;*/
300         tcsetattr(0, TCSANOW, &termbuf);
301
302         /* Uses FILE-based I/O to stdout, but does fflush(stdout),
303          * so should be safe with vfork.
304          * I fear, though, that some users will have ridiculously big
305          * issue files, and they may block writing to fd 1. */
306         print_login_issue(issuefile, NULL);
307
308         /* Exec shell / login / whatever */
309         login_argv[0] = loginpath;
310         login_argv[1] = NULL;
311         execvp(loginpath, (char **)login_argv);
312         /* Safer with vfork, and we shouldn't send message
313          * to remote clients anyway */
314         _exit(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
315 }
316
317 /* Must match getopt32 string */
318 enum {
319         OPT_WATCHCHILD = (1 << 2), /* -K */
320         OPT_INETD      = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
321         OPT_PORT       = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
322         OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
323 };
324
325 #if ENABLE_FEATURE_TELNETD_STANDALONE
326
327 static void
328 free_session(struct tsession *ts)
329 {
330         struct tsession *t = sessions;
331
332         if (option_mask32 & OPT_INETD)
333                 exit(0);
334
335         /* Unlink this telnet session from the session list */
336         if (t == ts)
337                 sessions = ts->next;
338         else {
339                 while (t->next != ts)
340                         t = t->next;
341                 t->next = ts->next;
342         }
343
344 #if 0
345         /* It was said that "normal" telnetd just closes ptyfd,
346          * doesn't send SIGKILL. When we close ptyfd,
347          * kernel sends SIGHUP to processes having slave side opened. */
348         kill(ts->shell_pid, SIGKILL);
349         wait4(ts->shell_pid, NULL, 0, NULL);
350 #endif
351         close(ts->ptyfd);
352         close(ts->sockfd_read);
353         /* We do not need to close(ts->sockfd_write), it's the same
354          * as sockfd_read unless we are in inetd mode. But in inetd mode
355          * we do not reach this */
356         free(ts);
357
358         /* Scan all sessions and find new maxfd */
359         maxfd = 0;
360         ts = sessions;
361         while (ts) {
362                 if (maxfd < ts->ptyfd)
363                         maxfd = ts->ptyfd;
364                 if (maxfd < ts->sockfd_read)
365                         maxfd = ts->sockfd_read;
366 #if 0
367                 /* Again, sockfd_write == sockfd_read here */
368                 if (maxfd < ts->sockfd_write)
369                         maxfd = ts->sockfd_write;
370 #endif
371                 ts = ts->next;
372         }
373 }
374
375 #else /* !FEATURE_TELNETD_STANDALONE */
376
377 /* Used in main() only, thus exits. */
378 #define free_session(ts) return 0
379
380 #endif
381
382 static void handle_sigchld(int sig)
383 {
384         pid_t pid;
385         struct tsession *ts;
386
387         pid = waitpid(-1, &sig, WNOHANG);
388         if (pid > 0) {
389                 ts = sessions;
390                 while (ts) {
391                         if (ts->shell_pid == pid) {
392                                 ts->shell_pid = -1;
393                                 return;
394                         }
395                         ts = ts->next;
396                 }
397         }
398 }
399
400
401 int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
402 int telnetd_main(int argc, char **argv)
403 {
404         fd_set rdfdset, wrfdset;
405         unsigned opt;
406         int count;
407         struct tsession *ts;
408 #if ENABLE_FEATURE_TELNETD_STANDALONE
409 #define IS_INETD (opt & OPT_INETD)
410         int master_fd = master_fd; /* be happy, gcc */
411         unsigned portnbr = 23;
412         char *opt_bindaddr = NULL;
413         char *opt_portnbr;
414 #else
415         enum {
416                 IS_INETD = 1,
417                 master_fd = -1,
418                 portnbr = 23,
419         };
420 #endif
421         /* Even if !STANDALONE, we accept (and ignore) -i, thus people
422          * don't need to guess whether it's ok to pass -i to us */
423         opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
424                         &issuefile, &loginpath
425                         USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
426         if (!IS_INETD /*&& !re_execed*/) {
427                 /* inform that we start in standalone mode?
428                  * May be useful when people forget to give -i */
429                 /*bb_error_msg("listening for connections");*/
430                 if (!(opt & OPT_FOREGROUND)) {
431                         /* DAEMON_CHDIR_ROOT was giving inconsistent
432                          * behavior with/without -F, -i */
433                         bb_daemonize_or_rexec(0 /*DAEMON_CHDIR_ROOT*/, argv);
434                 }
435         }
436         /* Redirect log to syslog early, if needed */
437         if (IS_INETD || !(opt & OPT_FOREGROUND)) {
438                 openlog(applet_name, 0, LOG_USER);
439                 logmode = LOGMODE_SYSLOG;
440         }
441         USE_FEATURE_TELNETD_STANDALONE(
442                 if (opt & OPT_PORT)
443                         portnbr = xatou16(opt_portnbr);
444         );
445
446         /* Used to check access(loginpath, X_OK) here. Pointless.
447          * exec will do this for us for free later. */
448
449 #if ENABLE_FEATURE_TELNETD_STANDALONE
450         if (IS_INETD) {
451                 sessions = make_new_session(0);
452                 if (!sessions) /* pty opening or vfork problem, exit */
453                         return 1; /* make_new_session prints error message */
454         } else {
455                 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
456                 xlisten(master_fd, 1);
457         }
458 #else
459         sessions = make_new_session();
460         if (!sessions) /* pty opening or vfork problem, exit */
461                 return 1; /* make_new_session prints error message */
462 #endif
463
464         /* We don't want to die if just one session is broken */
465         signal(SIGPIPE, SIG_IGN);
466
467         if (opt & OPT_WATCHCHILD)
468                 signal(SIGCHLD, handle_sigchld);
469
470 /*
471    This is how the buffers are used. The arrows indicate the movement
472    of data.
473    +-------+     wridx1++     +------+     rdidx1++     +----------+
474    |       | <--------------  | buf1 | <--------------  |          |
475    |       |     size1--      +------+     size1++      |          |
476    |  pty  |                                            |  socket  |
477    |       |     rdidx2++     +------+     wridx2++     |          |
478    |       |  --------------> | buf2 |  --------------> |          |
479    +-------+     size2++      +------+     size2--      +----------+
480
481    size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
482    size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
483
484    Each session has got two buffers. Buffers are circular. If sizeN == 0,
485    buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
486    rdidxN == wridxN.
487 */
488  again:
489         FD_ZERO(&rdfdset);
490         FD_ZERO(&wrfdset);
491
492         /* Select on the master socket, all telnet sockets and their
493          * ptys if there is room in their session buffers.
494          * NB: scalability problem: we recalculate entire bitmap
495          * before each select. Can be a problem with 500+ connections. */
496         ts = sessions;
497         while (ts) {
498                 struct tsession *next = ts->next; /* in case we free ts. */
499                 if (ts->shell_pid == -1) {
500                         /* Child died ad we detected that */
501                         free_session(ts);
502                 } else {
503                         if (ts->size1 > 0)       /* can write to pty */
504                                 FD_SET(ts->ptyfd, &wrfdset);
505                         if (ts->size1 < BUFSIZE) /* can read from socket */
506                                 FD_SET(ts->sockfd_read, &rdfdset);
507                         if (ts->size2 > 0)       /* can write to socket */
508                                 FD_SET(ts->sockfd_write, &wrfdset);
509                         if (ts->size2 < BUFSIZE) /* can read from pty */
510                                 FD_SET(ts->ptyfd, &rdfdset);
511                 }
512                 ts = next;
513         }
514         if (!IS_INETD) {
515                 FD_SET(master_fd, &rdfdset);
516                 /* This is needed because free_session() does not
517                  * take into account master_fd when it finds new
518                  * maxfd among remaining fd's */
519                 if (master_fd > maxfd)
520                         maxfd = master_fd;
521         }
522
523         count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
524         if (count < 0)
525                 goto again; /* EINTR or ENOMEM */
526
527 #if ENABLE_FEATURE_TELNETD_STANDALONE
528         /* First check for and accept new sessions. */
529         if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
530                 int fd;
531                 struct tsession *new_ts;
532
533                 fd = accept(master_fd, NULL, NULL);
534                 if (fd < 0)
535                         goto again;
536                 /* Create a new session and link it into our active list */
537                 new_ts = make_new_session(fd);
538                 if (new_ts) {
539                         new_ts->next = sessions;
540                         sessions = new_ts;
541                 } else {
542                         close(fd);
543                 }
544         }
545 #endif
546
547         /* Then check for data tunneling. */
548         ts = sessions;
549         while (ts) { /* For all sessions... */
550                 struct tsession *next = ts->next; /* in case we free ts. */
551
552                 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
553                         int num_totty;
554                         unsigned char *ptr;
555                         /* Write to pty from buffer 1. */
556                         ptr = remove_iacs(ts, &num_totty);
557                         count = safe_write(ts->ptyfd, ptr, num_totty);
558                         if (count < 0) {
559                                 if (errno == EAGAIN)
560                                         goto skip1;
561                                 goto kill_session;
562                         }
563                         ts->size1 -= count;
564                         ts->wridx1 += count;
565                         if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
566                                 ts->wridx1 = 0;
567                 }
568  skip1:
569                 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
570                         /* Write to socket from buffer 2. */
571                         count = MIN(BUFSIZE - ts->wridx2, ts->size2);
572                         count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
573                         if (count < 0) {
574                                 if (errno == EAGAIN)
575                                         goto skip2;
576                                 goto kill_session;
577                         }
578                         ts->size2 -= count;
579                         ts->wridx2 += count;
580                         if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
581                                 ts->wridx2 = 0;
582                 }
583  skip2:
584                 /* Should not be needed, but... remove_iacs is actually buggy
585                  * (it cannot process iacs which wrap around buffer's end)!
586                  * Since properly fixing it requires writing bigger code,
587                  * we rely instead on this code making it virtually impossible
588                  * to have wrapped iac (people don't type at 2k/second).
589                  * It also allows for bigger reads in common case. */
590                 if (ts->size1 == 0) {
591                         ts->rdidx1 = 0;
592                         ts->wridx1 = 0;
593                 }
594                 if (ts->size2 == 0) {
595                         ts->rdidx2 = 0;
596                         ts->wridx2 = 0;
597                 }
598
599                 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
600                         /* Read from socket to buffer 1. */
601                         count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
602                         count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
603                         if (count <= 0) {
604                                 if (count < 0 && errno == EAGAIN)
605                                         goto skip3;
606                                 goto kill_session;
607                         }
608                         /* Ignore trailing NUL if it is there */
609                         if (!TS_BUF1[ts->rdidx1 + count - 1]) {
610                                 --count;
611                         }
612                         ts->size1 += count;
613                         ts->rdidx1 += count;
614                         if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
615                                 ts->rdidx1 = 0;
616                 }
617  skip3:
618                 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
619                         /* Read from pty to buffer 2. */
620                         count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
621                         count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
622                         if (count <= 0) {
623                                 if (count < 0 && errno == EAGAIN)
624                                         goto skip4;
625                                 goto kill_session;
626                         }
627                         ts->size2 += count;
628                         ts->rdidx2 += count;
629                         if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
630                                 ts->rdidx2 = 0;
631                 }
632  skip4:
633                 ts = next;
634                 continue;
635  kill_session:
636                 free_session(ts);
637                 ts = next;
638         }
639
640         goto again;
641 }