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