Fix compile error, last_patch81 by Vladimir N. Oleynik
[oweals/busybox.git] / networking / telnetd.c
1 /* $Id: telnetd.c,v 1.4 2003/01/22 21:09:48 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 <string.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <netinet/in.h>
35 #include <fcntl.h>
36 #include <stdio.h>
37 #include <signal.h>
38 #include <termios.h>
39 #ifdef DEBUG
40 #define TELCMDS
41 #define TELOPTS
42 #endif
43 #include <arpa/telnet.h>
44 #include <ctype.h>
45 #include <sys/syslog.h>
46
47 #include "busybox.h"
48
49 #define BUFSIZE 4000
50
51 static const char *loginpath = 
52 #ifdef CONFIG_LOGIN
53 "/bin/login";
54 #else
55 "/bin/sh";
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   */
120 static char *
121 remove_iacs(struct tsession *ts, int *pnum_totty) {
122         unsigned char *ptr0 = ts->buf1 + ts->wridx1;
123         unsigned char *ptr = ptr0;
124         unsigned char *totty = ptr;
125         unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
126         int processed;
127         int num_totty;
128
129         while (ptr < end) {
130                 if (*ptr != IAC) {
131                         *totty++ = *ptr++;
132                 }
133                 else {
134                         if ((ptr+2) < end) {
135                         /* the entire IAC is contained in the buffer
136                         we were asked to process. */
137 #ifdef DEBUG
138                                 fprintf(stderr, "Ignoring IAC %s,%s\n",
139                                     *ptr, TELCMD(*(ptr+1)), TELOPT(*(ptr+2)));
140 #endif
141                                 ptr += 3;
142                         } else {
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
151         processed = ptr - ptr0;
152         num_totty = totty - ptr0;
153         /* the difference between processed and num_to tty
154            is all the iacs we removed from the stream.
155            Adjust buf1 accordingly. */
156         ts->wridx1 += processed - num_totty;
157         ts->size1 -= processed - num_totty;
158         *pnum_totty = num_totty;
159         /* move the chars meant for the terminal towards the end of the
160         buffer. */
161         return memmove(ptr - num_totty, ptr0, num_totty);
162 }
163
164
165 static int
166 getpty(char *line)
167 {
168         int p;
169 #ifdef CONFIG_FEATURE_DEVPTS
170         p = open("/dev/ptmx", 2);
171         if (p > 0) {
172                 grantpt(p);
173                 unlockpt(p);
174                 strcpy(line, ptsname(p));
175                 return(p);
176         }
177 #else
178         struct stat stb;
179         int i;
180         int j;
181
182         strcpy(line, "/dev/ptyXX");
183
184         for (i = 0; i < 16; i++) {
185                 line[8] = "pqrstuvwxyzabcde"[i];
186                 line[9] = '0';
187                 if (stat(line, &stb) < 0) {
188                         continue;
189                 }
190                 for (j = 0; j < 16; j++) {
191                         line[9] = j < 10 ? j + '0' : j - 10 + 'a';
192                         if ((p = open(line, O_RDWR | O_NOCTTY)) >= 0) {
193                                 line[5] = 't';
194                                 return p;
195                         }
196                 }
197         }
198 #endif /* CONFIG_FEATURE_DEVPTS */
199         return -1;
200 }
201
202
203 static void
204 send_iac(struct tsession *ts, unsigned char command, int option)
205 {
206         /* We rely on that there is space in the buffer for now.  */
207         char *b = ts->buf2 + ts->rdidx2;
208         *b++ = IAC;
209         *b++ = command;
210         *b++ = option;
211         ts->rdidx2 += 3;
212         ts->size2 += 3;
213 }
214
215
216 static struct tsession *
217 #ifdef CONFIG_FEATURE_TELNETD_INETD
218 make_new_session(void)
219 #else /* CONFIG_FEATURE_TELNETD_INETD */
220 make_new_session(int sockfd)
221 #endif /* CONFIG_FEATURE_TELNETD_INETD */
222 {
223         struct termios termbuf;
224         int pty, pid;
225         char tty_name[32];
226         struct tsession *ts = malloc(sizeof(struct tsession) + BUFSIZE * 2);
227
228         ts->buf1 = (char *)(&ts[1]);
229         ts->buf2 = ts->buf1 + BUFSIZE;
230
231 #ifdef CONFIG_FEATURE_TELNETD_INETD
232         ts->sockfd_read = 0;
233         ts->sockfd_write = 1;
234 #else /* CONFIG_FEATURE_TELNETD_INETD */
235         ts->sockfd = sockfd;
236 #endif /* CONFIG_FEATURE_TELNETD_INETD */
237
238         ts->rdidx1 = ts->wridx1 = ts->size1 = 0;
239         ts->rdidx2 = ts->wridx2 = ts->size2 = 0;
240
241         /* Got a new connection, set up a tty and spawn a shell.  */
242
243         pty = getpty(tty_name);
244
245         if (pty < 0) {
246                 syslog_msg(LOG_USER, LOG_ERR, "All network ports in use!");
247                 return 0;
248         }
249
250         if (pty > maxfd)
251                 maxfd = pty;
252
253         ts->ptyfd = pty;
254
255         /* Make the telnet client understand we will echo characters so it
256          * should not do it locally. We don't tell the client to run linemode,
257          * because we want to handle line editing and tab completion and other
258          * stuff that requires char-by-char support.
259          */
260
261         send_iac(ts, DO, TELOPT_ECHO);
262         send_iac(ts, DO, TELOPT_LFLOW);
263         send_iac(ts, WILL, TELOPT_ECHO);
264         send_iac(ts, WILL, TELOPT_SGA);
265
266
267         if ((pid = fork()) < 0) {
268                 syslog_msg(LOG_USER, LOG_ERR, "Can`t forking");
269         }
270         if (pid == 0) {
271                 /* In child, open the child's side of the tty.  */
272                 int i;
273
274                 for(i = 0; i <= maxfd; i++)
275                         close(i);
276                 /* make new process group */
277                 setsid();
278
279                 if (open(tty_name, O_RDWR /*| O_NOCTTY*/) < 0) {
280                         syslog_msg(LOG_USER, LOG_ERR, "Could not open tty");
281                         exit(1);
282                         }
283                 dup(0);
284                 dup(0);
285
286                 tcsetpgrp(0, getpid());
287
288                 /* The pseudo-terminal allocated to the client is configured to operate in
289                  * cooked mode, and with XTABS CRMOD enabled (see tty(4)).
290                  */
291
292                 tcgetattr(0, &termbuf);
293                 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
294                 termbuf.c_oflag |= ONLCR|XTABS;
295                 termbuf.c_iflag |= ICRNL;
296                 termbuf.c_iflag &= ~IXOFF;
297                 /*termbuf.c_lflag &= ~ICANON;*/
298                 tcsetattr(0, TCSANOW, &termbuf);
299
300                 print_login_issue(issuefile, NULL);
301
302                 /* exec shell, with correct argv and env */
303                 execv(loginpath, (char *const *)argv_init);
304
305                 /* NOT REACHED */
306                 syslog_msg(LOG_USER, LOG_ERR, "execv error");
307                 exit(1);
308         }
309
310         ts->shell_pid = pid;
311
312         return ts;
313 }
314
315 #ifndef CONFIG_FEATURE_TELNETD_INETD
316 static void
317 free_session(struct tsession *ts)
318 {
319         struct tsession *t = sessions;
320
321         /* Unlink this telnet session from the session list.  */
322         if(t == ts)
323                 sessions = ts->next;
324         else {
325                 while(t->next != ts)
326                         t = t->next;
327                 t->next = ts->next;
328         }
329
330         kill(ts->shell_pid, SIGKILL);
331
332         wait4(ts->shell_pid, NULL, 0, NULL);
333
334         close(ts->ptyfd);
335         close(ts->sockfd);
336
337         if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
338                 maxfd--;
339         if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
340                 maxfd--;
341
342         free(ts);
343 }
344 #endif /* CONFIG_FEATURE_TELNETD_INETD */
345
346 int
347 telnetd_main(int argc, char **argv)
348 {
349 #ifndef CONFIG_FEATURE_TELNETD_INETD
350         struct sockaddr_in sa;
351         int master_fd;
352 #endif /* CONFIG_FEATURE_TELNETD_INETD */
353         fd_set rdfdset, wrfdset;
354         int selret;
355 #ifndef CONFIG_FEATURE_TELNETD_INETD
356         int on = 1;
357         int portnbr = 23;
358 #endif /* CONFIG_FEATURE_TELNETD_INETD */
359         int c;
360         static const char options[] =
361 #ifdef CONFIG_FEATURE_TELNETD_INETD
362                 "f:l:";
363 #else /* CONFIG_EATURE_TELNETD_INETD */
364                 "f:l:p:";
365 #endif /* CONFIG_FEATURE_TELNETD_INETD */
366
367         for (;;) {
368                 c = getopt( argc, argv, options);
369                 if (c == EOF) break;
370                 switch (c) {
371                         case 'f':
372                                 issuefile = strdup (optarg);
373                                 break;
374                         case 'l':
375                                 loginpath = strdup (optarg);
376                                 break;
377 #ifndef CONFIG_FEATURE_TELNETD_INETD
378                         case 'p':
379                                 portnbr = atoi(optarg);
380                                 break;
381 #endif /* CONFIG_FEATURE_TELNETD_INETD */
382                         default:
383                                 show_usage();
384                 }
385         }
386
387         if (access(loginpath, X_OK) < 0) {
388                 error_msg_and_die ("'%s' unavailable.", loginpath);
389         }
390
391         argv_init[0] = loginpath;
392
393 #ifdef CONFIG_FEATURE_TELNETD_INETD
394         maxfd = 1;
395         sessions = make_new_session();
396 #else /* CONFIG_EATURE_TELNETD_INETD */
397         sessions = 0;
398
399         /* Grab a TCP socket.  */
400
401         master_fd = socket(AF_INET, SOCK_STREAM, 0);
402         if (master_fd < 0) {
403                 perror_msg_and_die("socket");
404         }
405         (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
406
407         /* Set it to listen to specified port.  */
408
409         memset((void *)&sa, 0, sizeof(sa));
410         sa.sin_family = AF_INET;
411         sa.sin_port = htons(portnbr);
412
413         if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
414                 perror_msg_and_die("bind");
415         }
416
417         if (listen(master_fd, 1) < 0) {
418                 perror_msg_and_die("listen");
419         }
420
421         if (daemon(0, 0) < 0)
422                 perror_msg_and_die("daemon");
423
424
425         maxfd = master_fd;
426 #endif /* CONFIG_FEATURE_TELNETD_INETD */
427
428         do {
429                 struct tsession *ts;
430
431                 FD_ZERO(&rdfdset);
432                 FD_ZERO(&wrfdset);
433
434                 /* select on the master socket, all telnet sockets and their
435                  * ptys if there is room in their respective session buffers.
436                  */
437
438 #ifndef CONFIG_FEATURE_TELNETD_INETD
439                 FD_SET(master_fd, &rdfdset);
440 #endif /* CONFIG_FEATURE_TELNETD_INETD */
441
442                 ts = sessions;
443 #ifndef CONFIG_FEATURE_TELNETD_INETD
444                 while (ts) {
445 #endif /* CONFIG_FEATURE_TELNETD_INETD */
446                         /* buf1 is used from socket to pty
447                          * buf2 is used from pty to socket
448                          */
449                         if (ts->size1 > 0) {
450                                 FD_SET(ts->ptyfd, &wrfdset);  /* can write to pty */
451                         }
452                         if (ts->size1 < BUFSIZE) {
453 #ifdef CONFIG_FEATURE_TELNETD_INETD
454                                 FD_SET(ts->sockfd_read, &rdfdset); /* can read from socket */
455 #else /* CONFIG_FEATURE_TELNETD_INETD */
456                                 FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
457 #endif /* CONFIG_FEATURE_TELNETD_INETD */
458                         }
459                         if (ts->size2 > 0) {
460 #ifdef CONFIG_FEATURE_TELNETD_INETD
461                                 FD_SET(ts->sockfd_write, &wrfdset); /* can write to socket */
462 #else /* CONFIG_FEATURE_TELNETD_INETD */
463                                 FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
464 #endif /* CONFIG_FEATURE_TELNETD_INETD */
465                         }
466                         if (ts->size2 < BUFSIZE) {
467                                 FD_SET(ts->ptyfd, &rdfdset);  /* can read from pty */
468                         }
469 #ifndef CONFIG_FEATURE_TELNETD_INETD
470                         ts = ts->next;
471                 }
472 #endif /* CONFIG_FEATURE_TELNETD_INETD */
473
474                 selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
475
476                 if (!selret)
477                         break;
478
479 #ifndef CONFIG_FEATURE_TELNETD_INETD
480                 /* First check for and accept new sessions.  */
481                 if (FD_ISSET(master_fd, &rdfdset)) {
482                         int fd, salen;
483
484                         salen = sizeof(sa);
485                         if ((fd = accept(master_fd, (struct sockaddr *)&sa,
486                                                 &salen)) < 0) {
487                                 continue;
488                         } else {
489                                 /* Create a new session and link it into
490                                         our active list.  */
491                                 struct tsession *new_ts = make_new_session(fd);
492                                 if (new_ts) {
493                                         new_ts->next = sessions;
494                                         sessions = new_ts;
495                                         if (fd > maxfd)
496                                                 maxfd = fd;
497                                 } else {
498                                         close(fd);
499                                 }
500                         }
501                 }
502
503                 /* Then check for data tunneling.  */
504
505                 ts = sessions;
506                 while (ts) { /* For all sessions...  */
507 #endif /* CONFIG_FEATURE_TELNETD_INETD */
508                         int maxlen, w, r;
509 #ifndef CONFIG_FEATURE_TELNETD_INETD
510                         struct tsession *next = ts->next; /* in case we free ts. */
511 #endif /* CONFIG_FEATURE_TELNETD_INETD */
512                         
513                         if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
514                                 int num_totty;
515                                 char *ptr;
516                                 /* Write to pty from buffer 1.  */
517
518                                 ptr = remove_iacs(ts, &num_totty);
519
520                                 w = write(ts->ptyfd, ptr, num_totty);
521                                 if (w < 0) {
522 #ifdef CONFIG_FEATURE_TELNETD_INETD
523                                         exit(0);
524 #else /* CONFIG_FEATURE_TELNETD_INETD */
525                                         free_session(ts);
526                                         ts = next;
527                                         continue;
528 #endif /* CONFIG_FEATURE_TELNETD_INETD */
529                                 }
530                                 ts->wridx1 += w;
531                                 ts->size1 -= w;
532                                 if (ts->wridx1 == BUFSIZE)
533                                         ts->wridx1 = 0;
534                         }
535
536 #ifdef CONFIG_FEATURE_TELNETD_INETD
537                         if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) {
538 #else /* CONFIG_FEATURE_TELNETD_INETD */
539                         if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
540 #endif /* CONFIG_FEATURE_TELNETD_INETD */
541                                 /* Write to socket from buffer 2.  */
542                                 maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
543 #ifdef CONFIG_FEATURE_TELNETD_INETD
544                                 w = write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen);
545                                 if (w < 0)
546                                         exit(0);
547 #else /* CONFIG_FEATURE_TELNETD_INETD */
548                                 w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
549                                 if (w < 0) {
550                                         free_session(ts);
551                                         ts = next;
552                                         continue;
553                                 }
554 #endif /* CONFIG_FEATURE_TELNETD_INETD */
555                                 ts->wridx2 += w;
556                                 ts->size2 -= w;
557                                 if (ts->wridx2 == BUFSIZE)
558                                         ts->wridx2 = 0;
559                         }
560
561 #ifdef CONFIG_FEATURE_TELNETD_INETD
562                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) {
563 #else /* CONFIG_FEATURE_TELNETD_INETD */
564                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
565 #endif /* CONFIG_FEATURE_TELNETD_INETD */
566                                 /* Read from socket to buffer 1. */
567                                 maxlen = MIN(BUFSIZE - ts->rdidx1,
568                                                 BUFSIZE - ts->size1);
569 #ifdef CONFIG_FEATURE_TELNETD_INETD
570                                 r = read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen);
571                                 if (!r || (r < 0 && errno != EINTR))
572                                         exit(0);
573 #else /* CONFIG_FEATURE_TELNETD_INETD */
574                                 r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
575                                 if (!r || (r < 0 && errno != EINTR)) {
576                                         free_session(ts);
577                                         ts = next;
578                                         continue;
579                                 }
580 #endif /* CONFIG_FEATURE_TELNETD_INETD */
581                                 if(!*(ts->buf1 + ts->rdidx1 + r - 1)) {
582                                         r--;
583                                         if(!r)
584                                                 continue;
585                                 }
586                                 ts->rdidx1 += r;
587                                 ts->size1 += r;
588                                 if (ts->rdidx1 == BUFSIZE)
589                                         ts->rdidx1 = 0;
590                         }
591
592                         if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
593                                 /* Read from pty to buffer 2.  */
594                                 maxlen = MIN(BUFSIZE - ts->rdidx2,
595                                                 BUFSIZE - ts->size2);
596                                 r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
597                                 if (!r || (r < 0 && errno != EINTR)) {
598 #ifdef CONFIG_FEATURE_TELNETD_INETD
599                                         exit(0);
600 #else /* CONFIG_FEATURE_TELNETD_INETD */
601                                         free_session(ts);
602                                         ts = next;
603                                         continue;
604 #endif /* CONFIG_FEATURE_TELNETD_INETD */
605                                 }
606                                 ts->rdidx2 += r;
607                                 ts->size2 += r;
608                                 if (ts->rdidx2 == BUFSIZE)
609                                         ts->rdidx2 = 0;
610                         }
611
612                         if (ts->size1 == 0) {
613                                 ts->rdidx1 = 0;
614                                 ts->wridx1 = 0;
615                         }
616                         if (ts->size2 == 0) {
617                                 ts->rdidx2 = 0;
618                                 ts->wridx2 = 0;
619                         }
620 #ifndef CONFIG_FEATURE_TELNETD_INETD
621                         ts = next;
622                 }
623 #endif /* CONFIG_FEATURE_TELNETD_INETD */
624
625         } while (1);
626
627         return 0;
628 }