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