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