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