7f748641b7aec671415694aa35fb4d7a43968ec7
[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         close(0);
233         xopen(tty_name, O_RDWR); /* becomes our ctty */
234         xdup2(0, 1);
235         xdup2(0, 2);
236         tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
237
238         /* The pseudo-terminal allocated to the client is configured to operate in
239          * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
240         tcgetattr(0, &termbuf);
241         termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
242         termbuf.c_oflag |= ONLCR | XTABS;
243         termbuf.c_iflag |= ICRNL;
244         termbuf.c_iflag &= ~IXOFF;
245         /*termbuf.c_lflag &= ~ICANON;*/
246         tcsetattr(0, TCSANOW, &termbuf);
247
248         /* Uses FILE-based I/O to stdout, but does fflush(stdout),
249          * so should be safe with vfork.
250          * I fear, though, that some users will have ridiculously big
251          * issue files, and they may block writing to fd 1,
252          * (parent is supposed to read it, but parent waits
253          * for vforked child to exec!) */
254         print_login_issue(issuefile, tty_name);
255
256         /* Exec shell / login / whatever */
257         login_argv[0] = loginpath;
258         login_argv[1] = NULL;
259         /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
260          * exec external program */
261         BB_EXECVP(loginpath, (char **)login_argv);
262         /* _exit is safer with vfork, and we shouldn't send message
263          * to remote clients anyway */
264         _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/
265 }
266
267 /* Must match getopt32 string */
268 enum {
269         OPT_WATCHCHILD = (1 << 2), /* -K */
270         OPT_INETD      = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
271         OPT_PORT       = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
272         OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
273 };
274
275 #if ENABLE_FEATURE_TELNETD_STANDALONE
276
277 static void
278 free_session(struct tsession *ts)
279 {
280         struct tsession *t = sessions;
281
282         if (option_mask32 & OPT_INETD)
283                 exit(EXIT_SUCCESS);
284
285         /* Unlink this telnet session from the session list */
286         if (t == ts)
287                 sessions = ts->next;
288         else {
289                 while (t->next != ts)
290                         t = t->next;
291                 t->next = ts->next;
292         }
293
294 #if 0
295         /* It was said that "normal" telnetd just closes ptyfd,
296          * doesn't send SIGKILL. When we close ptyfd,
297          * kernel sends SIGHUP to processes having slave side opened. */
298         kill(ts->shell_pid, SIGKILL);
299         wait4(ts->shell_pid, NULL, 0, NULL);
300 #endif
301         close(ts->ptyfd);
302         close(ts->sockfd_read);
303         /* We do not need to close(ts->sockfd_write), it's the same
304          * as sockfd_read unless we are in inetd mode. But in inetd mode
305          * we do not reach this */
306         free(ts);
307
308         /* Scan all sessions and find new maxfd */
309         maxfd = 0;
310         ts = sessions;
311         while (ts) {
312                 if (maxfd < ts->ptyfd)
313                         maxfd = ts->ptyfd;
314                 if (maxfd < ts->sockfd_read)
315                         maxfd = ts->sockfd_read;
316 #if 0
317                 /* Again, sockfd_write == sockfd_read here */
318                 if (maxfd < ts->sockfd_write)
319                         maxfd = ts->sockfd_write;
320 #endif
321                 ts = ts->next;
322         }
323 }
324
325 #else /* !FEATURE_TELNETD_STANDALONE */
326
327 /* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
328 #define free_session(ts) return 0
329
330 #endif
331
332 static void handle_sigchld(int sig UNUSED_PARAM)
333 {
334         pid_t pid;
335         struct tsession *ts;
336
337         /* Looping: more than one child may have exited */
338         while (1) {
339                 pid = wait_any_nohang(NULL);
340                 if (pid <= 0)
341                         break;
342                 ts = sessions;
343                 while (ts) {
344                         if (ts->shell_pid == pid) {
345                                 ts->shell_pid = -1;
346                                 break;
347                         }
348                         ts = ts->next;
349                 }
350         }
351 }
352
353 int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
354 int telnetd_main(int argc UNUSED_PARAM, char **argv)
355 {
356         fd_set rdfdset, wrfdset;
357         unsigned opt;
358         int count;
359         struct tsession *ts;
360 #if ENABLE_FEATURE_TELNETD_STANDALONE
361 #define IS_INETD (opt & OPT_INETD)
362         int master_fd = master_fd; /* be happy, gcc */
363         unsigned portnbr = 23;
364         char *opt_bindaddr = NULL;
365         char *opt_portnbr;
366 #else
367         enum {
368                 IS_INETD = 1,
369                 master_fd = -1,
370                 portnbr = 23,
371         };
372 #endif
373         /* Even if !STANDALONE, we accept (and ignore) -i, thus people
374          * don't need to guess whether it's ok to pass -i to us */
375         opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
376                         &issuefile, &loginpath
377                         USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
378         if (!IS_INETD /*&& !re_execed*/) {
379                 /* inform that we start in standalone mode?
380                  * May be useful when people forget to give -i */
381                 /*bb_error_msg("listening for connections");*/
382                 if (!(opt & OPT_FOREGROUND)) {
383                         /* DAEMON_CHDIR_ROOT was giving inconsistent
384                          * behavior with/without -F, -i */
385                         bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
386                 }
387         }
388         /* Redirect log to syslog early, if needed */
389         if (IS_INETD || !(opt & OPT_FOREGROUND)) {
390                 openlog(applet_name, 0, LOG_USER);
391                 logmode = LOGMODE_SYSLOG;
392         }
393         USE_FEATURE_TELNETD_STANDALONE(
394                 if (opt & OPT_PORT)
395                         portnbr = xatou16(opt_portnbr);
396         );
397
398         /* Used to check access(loginpath, X_OK) here. Pointless.
399          * exec will do this for us for free later. */
400
401 #if ENABLE_FEATURE_TELNETD_STANDALONE
402         if (IS_INETD) {
403                 sessions = make_new_session(0);
404                 if (!sessions) /* pty opening or vfork problem, exit */
405                         return 1; /* make_new_session prints error message */
406         } else {
407                 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
408                 xlisten(master_fd, 1);
409         }
410 #else
411         sessions = make_new_session();
412         if (!sessions) /* pty opening or vfork problem, exit */
413                 return 1; /* make_new_session prints error message */
414 #endif
415
416         /* We don't want to die if just one session is broken */
417         signal(SIGPIPE, SIG_IGN);
418
419         if (opt & OPT_WATCHCHILD)
420                 signal(SIGCHLD, handle_sigchld);
421         else /* prevent dead children from becoming zombies */
422                 signal(SIGCHLD, SIG_IGN);
423
424 /*
425    This is how the buffers are used. The arrows indicate the movement
426    of data.
427    +-------+     wridx1++     +------+     rdidx1++     +----------+
428    |       | <--------------  | buf1 | <--------------  |          |
429    |       |     size1--      +------+     size1++      |          |
430    |  pty  |                                            |  socket  |
431    |       |     rdidx2++     +------+     wridx2++     |          |
432    |       |  --------------> | buf2 |  --------------> |          |
433    +-------+     size2++      +------+     size2--      +----------+
434
435    size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
436    size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
437
438    Each session has got two buffers. Buffers are circular. If sizeN == 0,
439    buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
440    rdidxN == wridxN.
441 */
442  again:
443         FD_ZERO(&rdfdset);
444         FD_ZERO(&wrfdset);
445
446         /* Select on the master socket, all telnet sockets and their
447          * ptys if there is room in their session buffers.
448          * NB: scalability problem: we recalculate entire bitmap
449          * before each select. Can be a problem with 500+ connections. */
450         ts = sessions;
451         while (ts) {
452                 struct tsession *next = ts->next; /* in case we free ts. */
453                 if (ts->shell_pid == -1) {
454                         /* Child died and we detected that */
455                         free_session(ts);
456                 } else {
457                         if (ts->size1 > 0)       /* can write to pty */
458                                 FD_SET(ts->ptyfd, &wrfdset);
459                         if (ts->size1 < BUFSIZE) /* can read from socket */
460                                 FD_SET(ts->sockfd_read, &rdfdset);
461                         if (ts->size2 > 0)       /* can write to socket */
462                                 FD_SET(ts->sockfd_write, &wrfdset);
463                         if (ts->size2 < BUFSIZE) /* can read from pty */
464                                 FD_SET(ts->ptyfd, &rdfdset);
465                 }
466                 ts = next;
467         }
468         if (!IS_INETD) {
469                 FD_SET(master_fd, &rdfdset);
470                 /* This is needed because free_session() does not
471                  * take master_fd into account when it finds new
472                  * maxfd among remaining fd's */
473                 if (master_fd > maxfd)
474                         maxfd = master_fd;
475         }
476
477         count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
478         if (count < 0)
479                 goto again; /* EINTR or ENOMEM */
480
481 #if ENABLE_FEATURE_TELNETD_STANDALONE
482         /* First check for and accept new sessions. */
483         if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
484                 int fd;
485                 struct tsession *new_ts;
486
487                 fd = accept(master_fd, NULL, NULL);
488                 if (fd < 0)
489                         goto again;
490                 /* Create a new session and link it into our active list */
491                 new_ts = make_new_session(fd);
492                 if (new_ts) {
493                         new_ts->next = sessions;
494                         sessions = new_ts;
495                 } else {
496                         close(fd);
497                 }
498         }
499 #endif
500
501         /* Then check for data tunneling. */
502         ts = sessions;
503         while (ts) { /* For all sessions... */
504                 struct tsession *next = ts->next; /* in case we free ts. */
505
506                 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
507                         int num_totty;
508                         unsigned char *ptr;
509                         /* Write to pty from buffer 1. */
510                         ptr = remove_iacs(ts, &num_totty);
511                         count = safe_write(ts->ptyfd, ptr, num_totty);
512                         if (count < 0) {
513                                 if (errno == EAGAIN)
514                                         goto skip1;
515                                 goto kill_session;
516                         }
517                         ts->size1 -= count;
518                         ts->wridx1 += count;
519                         if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
520                                 ts->wridx1 = 0;
521                 }
522  skip1:
523                 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
524                         /* Write to socket from buffer 2. */
525                         count = MIN(BUFSIZE - ts->wridx2, ts->size2);
526                         count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
527                         if (count < 0) {
528                                 if (errno == EAGAIN)
529                                         goto skip2;
530                                 goto kill_session;
531                         }
532                         ts->size2 -= count;
533                         ts->wridx2 += count;
534                         if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
535                                 ts->wridx2 = 0;
536                 }
537  skip2:
538                 /* Should not be needed, but... remove_iacs is actually buggy
539                  * (it cannot process iacs which wrap around buffer's end)!
540                  * Since properly fixing it requires writing bigger code,
541                  * we rely instead on this code making it virtually impossible
542                  * to have wrapped iac (people don't type at 2k/second).
543                  * It also allows for bigger reads in common case. */
544                 if (ts->size1 == 0) {
545                         ts->rdidx1 = 0;
546                         ts->wridx1 = 0;
547                 }
548                 if (ts->size2 == 0) {
549                         ts->rdidx2 = 0;
550                         ts->wridx2 = 0;
551                 }
552
553                 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
554                         /* Read from socket to buffer 1. */
555                         count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
556                         count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
557                         if (count <= 0) {
558                                 if (count < 0 && errno == EAGAIN)
559                                         goto skip3;
560                                 goto kill_session;
561                         }
562                         /* Ignore trailing NUL if it is there */
563                         if (!TS_BUF1[ts->rdidx1 + count - 1]) {
564                                 --count;
565                         }
566                         ts->size1 += count;
567                         ts->rdidx1 += count;
568                         if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
569                                 ts->rdidx1 = 0;
570                 }
571  skip3:
572                 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
573                         /* Read from pty to buffer 2. */
574                         count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
575                         count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
576                         if (count <= 0) {
577                                 if (count < 0 && errno == EAGAIN)
578                                         goto skip4;
579                                 goto kill_session;
580                         }
581                         ts->size2 += count;
582                         ts->rdidx2 += count;
583                         if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
584                                 ts->rdidx2 = 0;
585                 }
586  skip4:
587                 ts = next;
588                 continue;
589  kill_session:
590                 free_session(ts);
591                 ts = next;
592         }
593
594         goto again;
595 }