telnetd: fix comment
[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)
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                         free_session(ts);
496                 } else {
497                         if (ts->size1 > 0)       /* can write to pty */
498                                 FD_SET(ts->ptyfd, &wrfdset);
499                         if (ts->size1 < BUFSIZE) /* can read from socket */
500                                 FD_SET(ts->sockfd_read, &rdfdset);
501                         if (ts->size2 > 0)       /* can write to socket */
502                                 FD_SET(ts->sockfd_write, &wrfdset);
503                         if (ts->size2 < BUFSIZE) /* can read from pty */
504                                 FD_SET(ts->ptyfd, &rdfdset);
505                 }
506                 ts = next;
507         }
508         if (!IS_INETD) {
509                 FD_SET(master_fd, &rdfdset);
510                 /* This is needed because free_session() does not
511                  * take into account master_fd when it finds new
512                  * maxfd among remaining fd's */
513                 if (master_fd > maxfd)
514                         maxfd = master_fd;
515         }
516
517         count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
518         if (count < 0)
519                 goto again; /* EINTR or ENOMEM */
520
521 #if ENABLE_FEATURE_TELNETD_STANDALONE
522         /* First check for and accept new sessions. */
523         if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
524                 int fd;
525                 struct tsession *new_ts;
526
527                 fd = accept(master_fd, NULL, NULL);
528                 if (fd < 0)
529                         goto again;
530                 /* Create a new session and link it into our active list */
531                 new_ts = make_new_session(fd);
532                 if (new_ts) {
533                         new_ts->next = sessions;
534                         sessions = new_ts;
535                 } else {
536                         close(fd);
537                 }
538         }
539 #endif
540
541         /* Then check for data tunneling. */
542         ts = sessions;
543         while (ts) { /* For all sessions... */
544                 struct tsession *next = ts->next; /* in case we free ts. */
545
546                 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
547                         int num_totty;
548                         unsigned char *ptr;
549                         /* Write to pty from buffer 1. */
550                         ptr = remove_iacs(ts, &num_totty);
551                         count = safe_write(ts->ptyfd, ptr, num_totty);
552                         if (count < 0) {
553                                 if (errno == EAGAIN)
554                                         goto skip1;
555                                 if (IS_INETD)
556                                         return 0;
557                                 goto kill_session;
558                         }
559                         ts->size1 -= count;
560                         ts->wridx1 += count;
561                         if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
562                                 ts->wridx1 = 0;
563                 }
564  skip1:
565                 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
566                         /* Write to socket from buffer 2. */
567                         count = MIN(BUFSIZE - ts->wridx2, ts->size2);
568                         count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
569                         if (count < 0) {
570                                 if (errno == EAGAIN)
571                                         goto skip2;
572                                 if (IS_INETD)
573                                         return 0;
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                                 if (IS_INETD)
605                                         return 0;
606                                 goto kill_session;
607                         }
608                         /* Ignore trailing NUL if it is there */
609                         if (!TS_BUF1[ts->rdidx1 + count - 1]) {
610                                 --count;
611                         }
612                         ts->size1 += count;
613                         ts->rdidx1 += count;
614                         if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
615                                 ts->rdidx1 = 0;
616                 }
617  skip3:
618                 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
619                         /* Read from pty to buffer 2. */
620                         count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
621                         count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
622                         if (count <= 0) {
623                                 if (count < 0 && errno == EAGAIN)
624                                         goto skip4;
625                                 if (IS_INETD)
626                                         return 0;
627                                 goto kill_session;
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                 continue;
637  kill_session:
638                 free_session(ts);
639                 ts = next;
640         }
641
642         goto again;
643 }