Prevent "`bootp_down' was declared implicitly `extern' and later `static'" warning
[oweals/busybox.git] / networking / telnetd.c
1 /* $Id: telnetd.c,v 1.12 2004/06/22 10:07:17 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(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(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(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(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         openlog(bb_applet_name, 0, LOG_USER);
426
427 #ifdef CONFIG_FEATURE_TELNETD_INETD
428         maxfd = 1;
429         sessions = make_new_session();
430 #else /* CONFIG_EATURE_TELNETD_INETD */
431         sessions = 0;
432
433         /* Grab a TCP socket.  */
434
435         master_fd = socket(AF_INET, SOCK_STREAM, 0);
436         if (master_fd < 0) {
437                 bb_perror_msg_and_die("socket");
438         }
439         (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
440
441         /* Set it to listen to specified port.  */
442
443         memset((void *)&sa, 0, sizeof(sa));
444         sa.sin_family = AF_INET;
445         sa.sin_port = htons(portnbr);
446
447         if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
448                 bb_perror_msg_and_die("bind");
449         }
450
451         if (listen(master_fd, 1) < 0) {
452                 bb_perror_msg_and_die("listen");
453         }
454
455         if (daemon(0, 0) < 0)
456                 bb_perror_msg_and_die("daemon");
457
458
459         maxfd = master_fd;
460 #endif /* CONFIG_FEATURE_TELNETD_INETD */
461
462         do {
463                 struct tsession *ts;
464
465                 FD_ZERO(&rdfdset);
466                 FD_ZERO(&wrfdset);
467
468                 /* select on the master socket, all telnet sockets and their
469                  * ptys if there is room in their respective session buffers.
470                  */
471
472 #ifndef CONFIG_FEATURE_TELNETD_INETD
473                 FD_SET(master_fd, &rdfdset);
474 #endif /* CONFIG_FEATURE_TELNETD_INETD */
475
476                 ts = sessions;
477 #ifndef CONFIG_FEATURE_TELNETD_INETD
478                 while (ts) {
479 #endif /* CONFIG_FEATURE_TELNETD_INETD */
480                         /* buf1 is used from socket to pty
481                          * buf2 is used from pty to socket
482                          */
483                         if (ts->size1 > 0) {
484                                 FD_SET(ts->ptyfd, &wrfdset);  /* can write to pty */
485                         }
486                         if (ts->size1 < BUFSIZE) {
487 #ifdef CONFIG_FEATURE_TELNETD_INETD
488                                 FD_SET(ts->sockfd_read, &rdfdset); /* can read from socket */
489 #else /* CONFIG_FEATURE_TELNETD_INETD */
490                                 FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
491 #endif /* CONFIG_FEATURE_TELNETD_INETD */
492                         }
493                         if (ts->size2 > 0) {
494 #ifdef CONFIG_FEATURE_TELNETD_INETD
495                                 FD_SET(ts->sockfd_write, &wrfdset); /* can write to socket */
496 #else /* CONFIG_FEATURE_TELNETD_INETD */
497                                 FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
498 #endif /* CONFIG_FEATURE_TELNETD_INETD */
499                         }
500                         if (ts->size2 < BUFSIZE) {
501                                 FD_SET(ts->ptyfd, &rdfdset);  /* can read from pty */
502                         }
503 #ifndef CONFIG_FEATURE_TELNETD_INETD
504                         ts = ts->next;
505                 }
506 #endif /* CONFIG_FEATURE_TELNETD_INETD */
507
508                 selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
509
510                 if (!selret)
511                         break;
512
513 #ifndef CONFIG_FEATURE_TELNETD_INETD
514                 /* First check for and accept new sessions.  */
515                 if (FD_ISSET(master_fd, &rdfdset)) {
516                         int fd, salen;
517
518                         salen = sizeof(sa);
519                         if ((fd = accept(master_fd, (struct sockaddr *)&sa,
520                                                 &salen)) < 0) {
521                                 continue;
522                         } else {
523                                 /* Create a new session and link it into
524                                         our active list.  */
525                                 struct tsession *new_ts = make_new_session(fd);
526                                 if (new_ts) {
527                                         new_ts->next = sessions;
528                                         sessions = new_ts;
529                                         if (fd > maxfd)
530                                                 maxfd = fd;
531                                 } else {
532                                         close(fd);
533                                 }
534                         }
535                 }
536
537                 /* Then check for data tunneling.  */
538
539                 ts = sessions;
540                 while (ts) { /* For all sessions...  */
541 #endif /* CONFIG_FEATURE_TELNETD_INETD */
542 #ifndef CONFIG_FEATURE_TELNETD_INETD
543                         struct tsession *next = ts->next; /* in case we free ts. */
544 #endif /* CONFIG_FEATURE_TELNETD_INETD */
545
546                         if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
547                                 int num_totty;
548                                 char *ptr;
549                                 /* Write to pty from buffer 1.  */
550
551                                 ptr = remove_iacs(ts, &num_totty);
552
553                                 w = write(ts->ptyfd, ptr, num_totty);
554                                 if (w < 0) {
555 #ifdef CONFIG_FEATURE_TELNETD_INETD
556                                         exit(0);
557 #else /* CONFIG_FEATURE_TELNETD_INETD */
558                                         free_session(ts);
559                                         ts = next;
560                                         continue;
561 #endif /* CONFIG_FEATURE_TELNETD_INETD */
562                                 }
563                                 ts->wridx1 += w;
564                                 ts->size1 -= w;
565                                 if (ts->wridx1 == BUFSIZE)
566                                         ts->wridx1 = 0;
567                         }
568
569 #ifdef CONFIG_FEATURE_TELNETD_INETD
570                         if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) {
571 #else /* CONFIG_FEATURE_TELNETD_INETD */
572                         if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
573 #endif /* CONFIG_FEATURE_TELNETD_INETD */
574                                 /* Write to socket from buffer 2.  */
575                                 maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
576 #ifdef CONFIG_FEATURE_TELNETD_INETD
577                                 w = write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen);
578                                 if (w < 0)
579                                         exit(0);
580 #else /* CONFIG_FEATURE_TELNETD_INETD */
581                                 w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
582                                 if (w < 0) {
583                                         free_session(ts);
584                                         ts = next;
585                                         continue;
586                                 }
587 #endif /* CONFIG_FEATURE_TELNETD_INETD */
588                                 ts->wridx2 += w;
589                                 ts->size2 -= w;
590                                 if (ts->wridx2 == BUFSIZE)
591                                         ts->wridx2 = 0;
592                         }
593
594 #ifdef CONFIG_FEATURE_TELNETD_INETD
595                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) {
596 #else /* CONFIG_FEATURE_TELNETD_INETD */
597                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
598 #endif /* CONFIG_FEATURE_TELNETD_INETD */
599                                 /* Read from socket to buffer 1. */
600                                 maxlen = MIN(BUFSIZE - ts->rdidx1,
601                                                 BUFSIZE - ts->size1);
602 #ifdef CONFIG_FEATURE_TELNETD_INETD
603                                 r = read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen);
604                                 if (!r || (r < 0 && errno != EINTR))
605                                         exit(0);
606 #else /* CONFIG_FEATURE_TELNETD_INETD */
607                                 r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
608                                 if (!r || (r < 0 && errno != EINTR)) {
609                                         free_session(ts);
610                                         ts = next;
611                                         continue;
612                                 }
613 #endif /* CONFIG_FEATURE_TELNETD_INETD */
614                                 if(!*(ts->buf1 + ts->rdidx1 + r - 1)) {
615                                         r--;
616                                         if(!r)
617                                                 continue;
618                                 }
619                                 ts->rdidx1 += r;
620                                 ts->size1 += r;
621                                 if (ts->rdidx1 == BUFSIZE)
622                                         ts->rdidx1 = 0;
623                         }
624
625                         if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
626                                 /* Read from pty to buffer 2.  */
627                                 maxlen = MIN(BUFSIZE - ts->rdidx2,
628                                                 BUFSIZE - ts->size2);
629                                 r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
630                                 if (!r || (r < 0 && errno != EINTR)) {
631 #ifdef CONFIG_FEATURE_TELNETD_INETD
632                                         exit(0);
633 #else /* CONFIG_FEATURE_TELNETD_INETD */
634                                         free_session(ts);
635                                         ts = next;
636                                         continue;
637 #endif /* CONFIG_FEATURE_TELNETD_INETD */
638                                 }
639                                 ts->rdidx2 += r;
640                                 ts->size2 += r;
641                                 if (ts->rdidx2 == BUFSIZE)
642                                         ts->rdidx2 = 0;
643                         }
644
645                         if (ts->size1 == 0) {
646                                 ts->rdidx1 = 0;
647                                 ts->wridx1 = 0;
648                         }
649                         if (ts->size2 == 0) {
650                                 ts->rdidx2 = 0;
651                                 ts->wridx2 = 0;
652                         }
653 #ifndef CONFIG_FEATURE_TELNETD_INETD
654                         ts = next;
655                 }
656 #endif /* CONFIG_FEATURE_TELNETD_INETD */
657
658         } while (1);
659
660         return 0;
661 }