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