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