Manousaridis Angelos writes:
[oweals/busybox.git] / networking / telnetd.c
1 /* $Id: telnetd.c,v 1.8 2003/09/12 11:27:15 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 ;
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         int maxlen, w, r;
367
368 #ifndef CONFIG_LOGIN
369         loginpath = DEFAULT_SHELL;
370 #endif
371
372         for (;;) {
373                 c = getopt( argc, argv, options);
374                 if (c == EOF) break;
375                 switch (c) {
376                         case 'f':
377                                 issuefile = strdup (optarg);
378                                 break;
379                         case 'l':
380                                 loginpath = strdup (optarg);
381                                 break;
382 #ifndef CONFIG_FEATURE_TELNETD_INETD
383                         case 'p':
384                                 portnbr = atoi(optarg);
385                                 break;
386 #endif /* CONFIG_FEATURE_TELNETD_INETD */
387                         default:
388                                 bb_show_usage();
389                 }
390         }
391
392         if (access(loginpath, X_OK) < 0) {
393                 bb_error_msg_and_die ("'%s' unavailable.", loginpath);
394         }
395
396         argv_init[0] = loginpath;
397
398 #ifdef CONFIG_FEATURE_TELNETD_INETD
399         maxfd = 1;
400         sessions = make_new_session();
401 #else /* CONFIG_EATURE_TELNETD_INETD */
402         sessions = 0;
403
404         /* Grab a TCP socket.  */
405
406         master_fd = socket(AF_INET, SOCK_STREAM, 0);
407         if (master_fd < 0) {
408                 bb_perror_msg_and_die("socket");
409         }
410         (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
411
412         /* Set it to listen to specified port.  */
413
414         memset((void *)&sa, 0, sizeof(sa));
415         sa.sin_family = AF_INET;
416         sa.sin_port = htons(portnbr);
417
418         if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
419                 bb_perror_msg_and_die("bind");
420         }
421
422         if (listen(master_fd, 1) < 0) {
423                 bb_perror_msg_and_die("listen");
424         }
425
426         if (daemon(0, 0) < 0)
427                 bb_perror_msg_and_die("daemon");
428
429
430         maxfd = master_fd;
431 #endif /* CONFIG_FEATURE_TELNETD_INETD */
432
433         do {
434                 struct tsession *ts;
435
436                 FD_ZERO(&rdfdset);
437                 FD_ZERO(&wrfdset);
438
439                 /* select on the master socket, all telnet sockets and their
440                  * ptys if there is room in their respective session buffers.
441                  */
442
443 #ifndef CONFIG_FEATURE_TELNETD_INETD
444                 FD_SET(master_fd, &rdfdset);
445 #endif /* CONFIG_FEATURE_TELNETD_INETD */
446
447                 ts = sessions;
448 #ifndef CONFIG_FEATURE_TELNETD_INETD
449                 while (ts) {
450 #endif /* CONFIG_FEATURE_TELNETD_INETD */
451                         /* buf1 is used from socket to pty
452                          * buf2 is used from pty to socket
453                          */
454                         if (ts->size1 > 0) {
455                                 FD_SET(ts->ptyfd, &wrfdset);  /* can write to pty */
456                         }
457                         if (ts->size1 < BUFSIZE) {
458 #ifdef CONFIG_FEATURE_TELNETD_INETD
459                                 FD_SET(ts->sockfd_read, &rdfdset); /* can read from socket */
460 #else /* CONFIG_FEATURE_TELNETD_INETD */
461                                 FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
462 #endif /* CONFIG_FEATURE_TELNETD_INETD */
463                         }
464                         if (ts->size2 > 0) {
465 #ifdef CONFIG_FEATURE_TELNETD_INETD
466                                 FD_SET(ts->sockfd_write, &wrfdset); /* can write to socket */
467 #else /* CONFIG_FEATURE_TELNETD_INETD */
468                                 FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
469 #endif /* CONFIG_FEATURE_TELNETD_INETD */
470                         }
471                         if (ts->size2 < BUFSIZE) {
472                                 FD_SET(ts->ptyfd, &rdfdset);  /* can read from pty */
473                         }
474 #ifndef CONFIG_FEATURE_TELNETD_INETD
475                         ts = ts->next;
476                 }
477 #endif /* CONFIG_FEATURE_TELNETD_INETD */
478
479                 selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
480
481                 if (!selret)
482                         break;
483
484 #ifndef CONFIG_FEATURE_TELNETD_INETD
485                 /* First check for and accept new sessions.  */
486                 if (FD_ISSET(master_fd, &rdfdset)) {
487                         int fd, salen;
488
489                         salen = sizeof(sa);
490                         if ((fd = accept(master_fd, (struct sockaddr *)&sa,
491                                                 &salen)) < 0) {
492                                 continue;
493                         } else {
494                                 /* Create a new session and link it into
495                                         our active list.  */
496                                 struct tsession *new_ts = make_new_session(fd);
497                                 if (new_ts) {
498                                         new_ts->next = sessions;
499                                         sessions = new_ts;
500                                         if (fd > maxfd)
501                                                 maxfd = fd;
502                                 } else {
503                                         close(fd);
504                                 }
505                         }
506                 }
507
508                 /* Then check for data tunneling.  */
509
510                 ts = sessions;
511                 while (ts) { /* For all sessions...  */
512 #endif /* CONFIG_FEATURE_TELNETD_INETD */
513 #ifndef CONFIG_FEATURE_TELNETD_INETD
514                         struct tsession *next = ts->next; /* in case we free ts. */
515 #endif /* CONFIG_FEATURE_TELNETD_INETD */
516                         
517                         if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
518                                 int num_totty;
519                                 char *ptr;
520                                 /* Write to pty from buffer 1.  */
521
522                                 ptr = remove_iacs(ts, &num_totty);
523
524                                 w = write(ts->ptyfd, ptr, num_totty);
525                                 if (w < 0) {
526 #ifdef CONFIG_FEATURE_TELNETD_INETD
527                                         exit(0);
528 #else /* CONFIG_FEATURE_TELNETD_INETD */
529                                         free_session(ts);
530                                         ts = next;
531                                         continue;
532 #endif /* CONFIG_FEATURE_TELNETD_INETD */
533                                 }
534                                 ts->wridx1 += w;
535                                 ts->size1 -= w;
536                                 if (ts->wridx1 == BUFSIZE)
537                                         ts->wridx1 = 0;
538                         }
539
540 #ifdef CONFIG_FEATURE_TELNETD_INETD
541                         if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) {
542 #else /* CONFIG_FEATURE_TELNETD_INETD */
543                         if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
544 #endif /* CONFIG_FEATURE_TELNETD_INETD */
545                                 /* Write to socket from buffer 2.  */
546                                 maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
547 #ifdef CONFIG_FEATURE_TELNETD_INETD
548                                 w = write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen);
549                                 if (w < 0)
550                                         exit(0);
551 #else /* CONFIG_FEATURE_TELNETD_INETD */
552                                 w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
553                                 if (w < 0) {
554                                         free_session(ts);
555                                         ts = next;
556                                         continue;
557                                 }
558 #endif /* CONFIG_FEATURE_TELNETD_INETD */
559                                 ts->wridx2 += w;
560                                 ts->size2 -= w;
561                                 if (ts->wridx2 == BUFSIZE)
562                                         ts->wridx2 = 0;
563                         }
564
565 #ifdef CONFIG_FEATURE_TELNETD_INETD
566                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) {
567 #else /* CONFIG_FEATURE_TELNETD_INETD */
568                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
569 #endif /* CONFIG_FEATURE_TELNETD_INETD */
570                                 /* Read from socket to buffer 1. */
571                                 maxlen = MIN(BUFSIZE - ts->rdidx1,
572                                                 BUFSIZE - ts->size1);
573 #ifdef CONFIG_FEATURE_TELNETD_INETD
574                                 r = read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen);
575                                 if (!r || (r < 0 && errno != EINTR))
576                                         exit(0);
577 #else /* CONFIG_FEATURE_TELNETD_INETD */
578                                 r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
579                                 if (!r || (r < 0 && errno != EINTR)) {
580                                         free_session(ts);
581                                         ts = next;
582                                         continue;
583                                 }
584 #endif /* CONFIG_FEATURE_TELNETD_INETD */
585                                 if(!*(ts->buf1 + ts->rdidx1 + r - 1)) {
586                                         r--;
587                                         if(!r)
588                                                 continue;
589                                 }
590                                 ts->rdidx1 += r;
591                                 ts->size1 += r;
592                                 if (ts->rdidx1 == BUFSIZE)
593                                         ts->rdidx1 = 0;
594                         }
595
596                         if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
597                                 /* Read from pty to buffer 2.  */
598                                 maxlen = MIN(BUFSIZE - ts->rdidx2,
599                                                 BUFSIZE - ts->size2);
600                                 r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
601                                 if (!r || (r < 0 && errno != EINTR)) {
602 #ifdef CONFIG_FEATURE_TELNETD_INETD
603                                         exit(0);
604 #else /* CONFIG_FEATURE_TELNETD_INETD */
605                                         free_session(ts);
606                                         ts = next;
607                                         continue;
608 #endif /* CONFIG_FEATURE_TELNETD_INETD */
609                                 }
610                                 ts->rdidx2 += r;
611                                 ts->size2 += r;
612                                 if (ts->rdidx2 == BUFSIZE)
613                                         ts->rdidx2 = 0;
614                         }
615
616                         if (ts->size1 == 0) {
617                                 ts->rdidx1 = 0;
618                                 ts->wridx1 = 0;
619                         }
620                         if (ts->size2 == 0) {
621                                 ts->rdidx2 = 0;
622                                 ts->wridx2 = 0;
623                         }
624 #ifndef CONFIG_FEATURE_TELNETD_INETD
625                         ts = next;
626                 }
627 #endif /* CONFIG_FEATURE_TELNETD_INETD */
628
629         } while (1);
630
631         return 0;
632 }