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