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