source: trunk/mindi-busybox/networking/telnetd.c @ 904

Last change on this file since 904 was 821, checked in by bruno, 13 years ago

Addition of busybox 1.2.1 as a mindi-busybox new package
This should avoid delivering binary files in mindi not built there (Fedora and Debian are quite serious about that)

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