Fix kbuild bugs noticed by Bernhard Fischer <rep.nop@aon.at>
[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 1 */
25 #undef DEBUG
26
27 #include "busybox.h"
28
29 #ifdef DEBUG
30 #define TELCMDS
31 #define TELOPTS
32 #endif
33 #include <arpa/telnet.h>
34 #include <sys/syslog.h>
35
36
37 #define BUFSIZE 4000
38
39 #ifdef CONFIG_FEATURE_IPV6
40 #define SOCKET_TYPE     AF_INET6
41 typedef struct sockaddr_in6 sockaddr_type;
42 #else
43 #define SOCKET_TYPE     AF_INET
44 typedef struct sockaddr_in sockaddr_type;
45 #endif
46
47
48 #ifdef CONFIG_LOGIN
49 static const char *loginpath = "/bin/login";
50 #else
51 static const char *loginpath;
52 #endif
53 static const char *issuefile = "/etc/issue.net";
54
55 /* shell name and arguments */
56
57 static const char *argv_init[] = {NULL, NULL};
58
59 /* structure that describes a session */
60
61 struct tsession {
62 #ifdef CONFIG_FEATURE_TELNETD_INETD
63         int sockfd_read, sockfd_write, ptyfd;
64 #else /* CONFIG_FEATURE_TELNETD_INETD */
65         struct tsession *next;
66         int sockfd, ptyfd;
67 #endif /* CONFIG_FEATURE_TELNETD_INETD */
68         int shell_pid;
69         /* two circular buffers */
70         char *buf1, *buf2;
71         int rdidx1, wridx1, size1;
72         int rdidx2, wridx2, size2;
73 };
74
75 /*
76
77    This is how the buffers are used. The arrows indicate the movement
78    of data.
79
80    +-------+     wridx1++     +------+     rdidx1++     +----------+
81    |       | <--------------  | buf1 | <--------------  |          |
82    |       |     size1--      +------+     size1++      |          |
83    |  pty  |                                            |  socket  |
84    |       |     rdidx2++     +------+     wridx2++     |          |
85    |       |  --------------> | buf2 |  --------------> |          |
86    +-------+     size2++      +------+     size2--      +----------+
87
88    Each session has got two buffers.
89
90 */
91
92 static int maxfd;
93
94 static struct tsession *sessions;
95
96
97 /*
98
99    Remove all IAC's from the buffer pointed to by bf (received IACs are ignored
100    and must be removed so as to not be interpreted by the terminal).  Make an
101    uninterrupted string of characters fit for the terminal.  Do this by packing
102    all characters meant for the terminal sequentially towards the end of bf.
103
104    Return a pointer to the beginning of the characters meant for the terminal.
105    and make *num_totty the number of characters that should be sent to
106    the terminal.
107
108    Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
109    past (bf + len) then that IAC will be left unprocessed and *processed will be
110    less than len.
111
112    FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
113    what is the escape character?  We aren't handling that situation here.
114
115    CR-LF ->'s CR mapping is also done here, for convenience
116
117   */
118 static char *
119 remove_iacs(struct tsession *ts, int *pnum_totty) {
120         unsigned char *ptr0 = (unsigned char *)ts->buf1 + ts->wridx1;
121         unsigned char *ptr = ptr0;
122         unsigned char *totty = ptr;
123         unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
124         int processed;
125         int num_totty;
126
127         while (ptr < end) {
128                 if (*ptr != IAC) {
129                         int c = *ptr;
130                         *totty++ = *ptr++;
131                         /* We now map \r\n ==> \r for pragmatic reasons.
132                          * Many client implementations send \r\n when
133                          * the user hits the CarriageReturn key.
134                          */
135                         if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end)
136                                 ptr++;
137                 }
138                 else {
139                         /*
140                          * TELOPT_NAWS support!
141                          */
142                         if ((ptr+2) >= end) {
143                                 /* only the beginning of the IAC is in the
144                                 buffer we were asked to process, we can't
145                                 process this char. */
146                                 break;
147                         }
148
149                         /*
150                          * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
151                          */
152                         else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
153                                 struct winsize ws;
154                                 if ((ptr+8) >= end)
155                                         break;  /* incomplete, can't process */
156                                 ws.ws_col = (ptr[3] << 8) | ptr[4];
157                                 ws.ws_row = (ptr[5] << 8) | ptr[6];
158                                 (void) ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
159                                 ptr += 9;
160                         }
161                         else {
162                                 /* skip 3-byte IAC non-SB cmd */
163 #ifdef DEBUG
164                                 fprintf(stderr, "Ignoring IAC %s,%s\n",
165                                         TELCMD(*(ptr+1)), TELOPT(*(ptr+2)));
166 #endif
167                                 ptr += 3;
168                         }
169                 }
170         }
171
172         processed = ptr - ptr0;
173         num_totty = totty - ptr0;
174         /* the difference between processed and num_to tty
175            is all the iacs we removed from the stream.
176            Adjust buf1 accordingly. */
177         ts->wridx1 += processed - num_totty;
178         ts->size1 -= processed - num_totty;
179         *pnum_totty = num_totty;
180         /* move the chars meant for the terminal towards the end of the
181         buffer. */
182         return memmove(ptr - num_totty, ptr0, num_totty);
183 }
184
185
186 static int
187 getpty(char *line)
188 {
189         int p;
190 #ifdef CONFIG_FEATURE_DEVPTS
191         p = open("/dev/ptmx", O_RDWR);
192         if (p > 0) {
193                 const char *name;
194                 grantpt(p);
195                 unlockpt(p);
196                 name = ptsname(p);
197                 if (!name) {
198                         bb_perror_msg("ptsname error (is /dev/pts mounted?)");
199                         return -1;
200                 }
201                 strcpy(line, name);
202                 return p;
203         }
204 #else
205         struct stat stb;
206         int i;
207         int j;
208
209         strcpy(line, "/dev/ptyXX");
210
211         for (i = 0; i < 16; i++) {
212                 line[8] = "pqrstuvwxyzabcde"[i];
213                 line[9] = '0';
214                 if (stat(line, &stb) < 0) {
215                         continue;
216                 }
217                 for (j = 0; j < 16; j++) {
218                         line[9] = j < 10 ? j + '0' : j - 10 + 'a';
219 #ifdef DEBUG
220                         fprintf(stderr, "Trying to open device: %s\n", line);
221 #endif
222                         p = open(line, O_RDWR | O_NOCTTY);
223                         if (p >= 0) {
224                                 line[5] = 't';
225                                 return p;
226                         }
227                 }
228         }
229 #endif /* CONFIG_FEATURE_DEVPTS */
230         return -1;
231 }
232
233
234 static void
235 send_iac(struct tsession *ts, unsigned char command, int option)
236 {
237         /* We rely on that there is space in the buffer for now.  */
238         char *b = ts->buf2 + ts->rdidx2;
239         *b++ = IAC;
240         *b++ = command;
241         *b++ = option;
242         ts->rdidx2 += 3;
243         ts->size2 += 3;
244 }
245
246
247 static struct tsession *
248 #ifdef CONFIG_FEATURE_TELNETD_INETD
249 make_new_session(void)
250 #else /* CONFIG_FEATURE_TELNETD_INETD */
251 make_new_session(int sockfd)
252 #endif /* CONFIG_FEATURE_TELNETD_INETD */
253 {
254         struct termios termbuf;
255         int pty, pid;
256         char tty_name[32];
257         struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
258
259         ts->buf1 = (char *)(&ts[1]);
260         ts->buf2 = ts->buf1 + BUFSIZE;
261
262 #ifdef CONFIG_FEATURE_TELNETD_INETD
263         ts->sockfd_write = 1;
264 #else /* CONFIG_FEATURE_TELNETD_INETD */
265         ts->sockfd = sockfd;
266 #endif /* CONFIG_FEATURE_TELNETD_INETD */
267
268         /* Got a new connection, set up a tty and spawn a shell.  */
269
270         pty = getpty(tty_name);
271
272         if (pty < 0) {
273                 bb_error_msg("all terminals in use");
274                 return 0;
275         }
276
277         if (pty > maxfd)
278                 maxfd = pty;
279
280         ts->ptyfd = pty;
281
282         /* Make the telnet client understand we will echo characters so it
283          * should not do it locally. We don't tell the client to run linemode,
284          * because we want to handle line editing and tab completion and other
285          * stuff that requires char-by-char support.
286          */
287
288         send_iac(ts, DO, TELOPT_ECHO);
289         send_iac(ts, DO, TELOPT_NAWS);
290         send_iac(ts, DO, TELOPT_LFLOW);
291         send_iac(ts, WILL, TELOPT_ECHO);
292         send_iac(ts, WILL, TELOPT_SGA);
293
294         if ((pid = fork()) < 0) {
295                 bb_perror_msg("fork");
296         }
297         if (pid == 0) {
298                 /* In child, open the child's side of the tty.  */
299                 int i;
300
301                 for(i = 0; i <= maxfd; i++)
302                         close(i);
303                 /* make new process group */
304                 setsid();
305
306                 xopen(tty_name, O_RDWR /*| O_NOCTTY*/);
307                 dup(0);
308                 dup(0);
309
310                 tcsetpgrp(0, getpid());
311
312                 /* The pseudo-terminal allocated to the client is configured to operate in
313                  * cooked mode, and with XTABS CRMOD enabled (see tty(4)).
314                  */
315
316                 tcgetattr(0, &termbuf);
317                 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
318                 termbuf.c_oflag |= ONLCR|XTABS;
319                 termbuf.c_iflag |= ICRNL;
320                 termbuf.c_iflag &= ~IXOFF;
321                 /*termbuf.c_lflag &= ~ICANON;*/
322                 tcsetattr(0, TCSANOW, &termbuf);
323
324                 print_login_issue(issuefile, NULL);
325
326                 /* exec shell, with correct argv and env */
327                 execv(loginpath, (char *const *)argv_init);
328
329                 /* NOT REACHED */
330                 bb_perror_msg_and_die("execv");
331         }
332
333         ts->shell_pid = pid;
334
335         return ts;
336 }
337
338 #ifndef CONFIG_FEATURE_TELNETD_INETD
339 static void
340 free_session(struct tsession *ts)
341 {
342         struct tsession *t = sessions;
343
344         /* Unlink this telnet session from the session list.  */
345         if (t == ts)
346                 sessions = ts->next;
347         else {
348                 while(t->next != ts)
349                         t = t->next;
350                 t->next = ts->next;
351         }
352
353         kill(ts->shell_pid, SIGKILL);
354
355         wait4(ts->shell_pid, NULL, 0, NULL);
356
357         close(ts->ptyfd);
358         close(ts->sockfd);
359
360         if (ts->ptyfd == maxfd || ts->sockfd == maxfd)
361                 maxfd--;
362         if (ts->ptyfd == maxfd || ts->sockfd == maxfd)
363                 maxfd--;
364
365         free(ts);
366 }
367 #endif /* CONFIG_FEATURE_TELNETD_INETD */
368
369 int
370 telnetd_main(int argc, char **argv)
371 {
372         unsigned opt;
373         fd_set rdfdset, wrfdset;
374         int selret;
375 #ifndef CONFIG_FEATURE_TELNETD_INETD
376         sockaddr_type sa;
377         int master_fd;
378         int on = 1;
379         unsigned portnbr = 23;
380         struct in_addr bind_addr = { .s_addr = 0x0 };
381         char *opt_portnbr, *opt_bindaddr;
382 #endif /* CONFIG_FEATURE_TELNETD_INETD */
383         int maxlen, w, r;
384
385 #ifndef CONFIG_LOGIN
386         loginpath = DEFAULT_SHELL;
387 #endif
388
389         /* We use inetd-style operation unconditionally
390          * (no --foreground option), user most likely will
391          * look into syslog for all errors, even early ones.
392          * Direct all output to syslog at once.
393          */
394         openlog(applet_name, 0, LOG_USER);
395         logmode = LOGMODE_SYSLOG;
396
397         opt = getopt32(argc, argv, "f:l:" SKIP_FEATURE_TELNETD_INETD("p:b:"),
398                         &issuefile, &loginpath
399                         SKIP_FEATURE_TELNETD_INETD(, &opt_portnbr, &opt_bindaddr));
400         //if (opt & 1) // -f
401         //if (opt & 2) // -l
402 #ifndef CONFIG_FEATURE_TELNETD_INETD
403         if (opt & 4) portnbr = xatou16(opt_portnbr); // -p
404         if (opt & 8) // -b
405                 if (inet_aton(opt_bindaddr, &bind_addr) == 0) bb_show_usage();
406 #endif /* CONFIG_FEATURE_TELNETD_INETD */
407
408         if (access(loginpath, X_OK) < 0) {
409                 bb_error_msg_and_die("'%s' unavailable", loginpath);
410         }
411
412         argv_init[0] = loginpath;
413
414 #ifdef CONFIG_FEATURE_TELNETD_INETD
415         maxfd = 1;
416         sessions = make_new_session();
417 #else /* CONFIG_EATURE_TELNETD_INETD */
418         sessions = 0;
419
420         /* Grab a TCP socket.  */
421
422         master_fd = xsocket(SOCKET_TYPE, SOCK_STREAM, 0);
423         (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
424
425         /* Set it to listen to specified port.  */
426
427         memset((void *)&sa, 0, sizeof(sa));
428 #ifdef CONFIG_FEATURE_IPV6
429         sa.sin6_family = AF_INET6;
430         sa.sin6_port = htons(portnbr);
431         /* sa.sin6_addr = bind_addr6; */
432 #else
433         sa.sin_family = AF_INET;
434         sa.sin_port = htons(portnbr);
435         sa.sin_addr = bind_addr;
436 #endif
437
438         xbind(master_fd, (struct sockaddr *) &sa, sizeof(sa));
439         xlisten(master_fd, 1);
440         xdaemon(0, 0);
441
442         maxfd = master_fd;
443 #endif /* CONFIG_FEATURE_TELNETD_INETD */
444
445         while(1) {
446                 struct tsession *ts;
447
448                 FD_ZERO(&rdfdset);
449                 FD_ZERO(&wrfdset);
450
451                 /* select on the master socket, all telnet sockets and their
452                  * ptys if there is room in their respective session buffers.
453                  */
454
455 #ifndef CONFIG_FEATURE_TELNETD_INETD
456                 FD_SET(master_fd, &rdfdset);
457 #endif /* CONFIG_FEATURE_TELNETD_INETD */
458
459                 ts = sessions;
460 #ifndef CONFIG_FEATURE_TELNETD_INETD
461                 while (ts) {
462 #endif /* CONFIG_FEATURE_TELNETD_INETD */
463                         /* buf1 is used from socket to pty
464                          * buf2 is used from pty to socket
465                          */
466                         if (ts->size1 > 0) {
467                                 FD_SET(ts->ptyfd, &wrfdset);  /* can write to pty */
468                         }
469                         if (ts->size1 < BUFSIZE) {
470 #ifdef CONFIG_FEATURE_TELNETD_INETD
471                                 FD_SET(ts->sockfd_read, &rdfdset); /* can read from socket */
472 #else /* CONFIG_FEATURE_TELNETD_INETD */
473                                 FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
474 #endif /* CONFIG_FEATURE_TELNETD_INETD */
475                         }
476                         if (ts->size2 > 0) {
477 #ifdef CONFIG_FEATURE_TELNETD_INETD
478                                 FD_SET(ts->sockfd_write, &wrfdset); /* can write to socket */
479 #else /* CONFIG_FEATURE_TELNETD_INETD */
480                                 FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
481 #endif /* CONFIG_FEATURE_TELNETD_INETD */
482                         }
483                         if (ts->size2 < BUFSIZE) {
484                                 FD_SET(ts->ptyfd, &rdfdset);  /* can read from pty */
485                         }
486 #ifndef CONFIG_FEATURE_TELNETD_INETD
487                         ts = ts->next;
488                 }
489 #endif /* CONFIG_FEATURE_TELNETD_INETD */
490
491                 selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
492
493                 if (!selret)
494                         break;
495
496 #ifndef CONFIG_FEATURE_TELNETD_INETD
497                 /* First check for and accept new sessions.  */
498                 if (FD_ISSET(master_fd, &rdfdset)) {
499                         int fd;
500                         socklen_t salen;
501
502                         salen = sizeof(sa);
503                         fd = accept(master_fd, (struct sockaddr *)&sa, &salen);
504                         if (fd < 0) {
505                                 continue;
506                         } else {
507                                 /* Create a new session and link it into
508                                         our active list.  */
509                                 struct tsession *new_ts = make_new_session(fd);
510                                 if (new_ts) {
511                                         new_ts->next = sessions;
512                                         sessions = new_ts;
513                                         if (fd > maxfd)
514                                                 maxfd = fd;
515                                 } else {
516                                         close(fd);
517                                 }
518                         }
519                 }
520
521                 /* Then check for data tunneling.  */
522
523                 ts = sessions;
524                 while (ts) { /* For all sessions...  */
525 #endif /* CONFIG_FEATURE_TELNETD_INETD */
526 #ifndef CONFIG_FEATURE_TELNETD_INETD
527                         struct tsession *next = ts->next; /* in case we free ts. */
528 #endif /* CONFIG_FEATURE_TELNETD_INETD */
529
530                         if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
531                                 int num_totty;
532                                 char *ptr;
533                                 /* Write to pty from buffer 1.  */
534
535                                 ptr = remove_iacs(ts, &num_totty);
536
537                                 w = write(ts->ptyfd, ptr, num_totty);
538                                 if (w < 0) {
539 #ifdef CONFIG_FEATURE_TELNETD_INETD
540                                         exit(0);
541 #else /* CONFIG_FEATURE_TELNETD_INETD */
542                                         free_session(ts);
543                                         ts = next;
544                                         continue;
545 #endif /* CONFIG_FEATURE_TELNETD_INETD */
546                                 }
547                                 ts->wridx1 += w;
548                                 ts->size1 -= w;
549                                 if (ts->wridx1 == BUFSIZE)
550                                         ts->wridx1 = 0;
551                         }
552
553 #ifdef CONFIG_FEATURE_TELNETD_INETD
554                         if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) {
555 #else /* CONFIG_FEATURE_TELNETD_INETD */
556                         if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
557 #endif /* CONFIG_FEATURE_TELNETD_INETD */
558                                 /* Write to socket from buffer 2.  */
559                                 maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
560 #ifdef CONFIG_FEATURE_TELNETD_INETD
561                                 w = write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen);
562                                 if (w < 0)
563                                         exit(0);
564 #else /* CONFIG_FEATURE_TELNETD_INETD */
565                                 w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
566                                 if (w < 0) {
567                                         free_session(ts);
568                                         ts = next;
569                                         continue;
570                                 }
571 #endif /* CONFIG_FEATURE_TELNETD_INETD */
572                                 ts->wridx2 += w;
573                                 ts->size2 -= w;
574                                 if (ts->wridx2 == BUFSIZE)
575                                         ts->wridx2 = 0;
576                         }
577
578 #ifdef CONFIG_FEATURE_TELNETD_INETD
579                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) {
580 #else /* CONFIG_FEATURE_TELNETD_INETD */
581                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
582 #endif /* CONFIG_FEATURE_TELNETD_INETD */
583                                 /* Read from socket to buffer 1. */
584                                 maxlen = MIN(BUFSIZE - ts->rdidx1,
585                                                 BUFSIZE - ts->size1);
586 #ifdef CONFIG_FEATURE_TELNETD_INETD
587                                 r = read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen);
588                                 if (!r || (r < 0 && errno != EINTR))
589                                         exit(0);
590 #else /* CONFIG_FEATURE_TELNETD_INETD */
591                                 r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
592                                 if (!r || (r < 0 && errno != EINTR)) {
593                                         free_session(ts);
594                                         ts = next;
595                                         continue;
596                                 }
597 #endif /* CONFIG_FEATURE_TELNETD_INETD */
598                                 if (!*(ts->buf1 + ts->rdidx1 + r - 1)) {
599                                         r--;
600                                         if (!r)
601                                                 continue;
602                                 }
603                                 ts->rdidx1 += r;
604                                 ts->size1 += r;
605                                 if (ts->rdidx1 == BUFSIZE)
606                                         ts->rdidx1 = 0;
607                         }
608
609                         if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
610                                 /* Read from pty to buffer 2.  */
611                                 maxlen = MIN(BUFSIZE - ts->rdidx2,
612                                                 BUFSIZE - ts->size2);
613                                 r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
614                                 if (!r || (r < 0 && errno != EINTR)) {
615 #ifdef CONFIG_FEATURE_TELNETD_INETD
616                                         exit(0);
617 #else /* CONFIG_FEATURE_TELNETD_INETD */
618                                         free_session(ts);
619                                         ts = next;
620                                         continue;
621 #endif /* CONFIG_FEATURE_TELNETD_INETD */
622                                 }
623                                 ts->rdidx2 += r;
624                                 ts->size2 += r;
625                                 if (ts->rdidx2 == BUFSIZE)
626                                         ts->rdidx2 = 0;
627                         }
628
629                         if (ts->size1 == 0) {
630                                 ts->rdidx1 = 0;
631                                 ts->wridx1 = 0;
632                         }
633                         if (ts->size2 == 0) {
634                                 ts->rdidx2 = 0;
635                                 ts->wridx2 = 0;
636                         }
637 #ifndef CONFIG_FEATURE_TELNETD_INETD
638                         ts = next;
639                 }
640 #endif /* CONFIG_FEATURE_TELNETD_INETD */
641
642         } /* while(1) */
643
644         return 0;
645 }