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