diff options
Diffstat (limited to 'networking')
-rw-r--r-- | networking/Makefile.in | 1 | ||||
-rw-r--r-- | networking/config.in | 1 | ||||
-rw-r--r-- | networking/telnetd.c | 538 |
3 files changed, 540 insertions, 0 deletions
diff --git a/networking/Makefile.in b/networking/Makefile.in index 425b5d3d9..510c8119d 100644 --- a/networking/Makefile.in +++ b/networking/Makefile.in @@ -32,6 +32,7 @@ NETWORKING-$(CONFIG_PING) += ping.o NETWORKING-$(CONFIG_PING6) += ping6.o NETWORKING-$(CONFIG_ROUTE) += route.o NETWORKING-$(CONFIG_TELNET) += telnet.o +NETWORKING-$(CONFIG_TELNETD) += telnetd.o NETWORKING-$(CONFIG_TFTP) += tftp.o NETWORKING-$(CONFIG_TRACEROUTE) += traceroute.o NETWORKING-$(CONFIG_UDHCPC) += udhcpc.o diff --git a/networking/config.in b/networking/config.in index bc11d83a7..4438a8c6c 100644 --- a/networking/config.in +++ b/networking/config.in @@ -34,6 +34,7 @@ bool 'telnet' CONFIG_TELNET if [ "$CONFIG_TELNET" = "y" ]; then bool ' Pass TERM type to remote host' CONFIG_FEATURE_TELNET_TTYPE fi +bool 'telnetd' CONFIG_TELNETD bool 'tftp' CONFIG_TFTP if [ "$CONFIG_TFTP" = "y" ]; then bool ' Enable "get" command' CONFIG_FEATURE_TFTP_GET diff --git a/networking/telnetd.c b/networking/telnetd.c new file mode 100644 index 000000000..edc018a2a --- /dev/null +++ b/networking/telnetd.c @@ -0,0 +1,538 @@ +/* $Id: telnetd.c,v 1.1 2002/09/30 20:52:04 andersen Exp $ + * + * Simple telnet server + * Bjorn Wesen, Axis Communications AB (bjornw@axis.com) + * + * This file is distributed under the Gnu Public License (GPL), + * please see the file LICENSE for further information. + * + * --------------------------------------------------------------------------- + * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN + **************************************************************************** + * + * The telnetd manpage says it all: + * + * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for + * a client, then creating a login process which has the slave side of the + * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the + * master side of the pseudo-terminal, implementing the telnet protocol and + * passing characters between the remote client and the login process. + * + * Vladimir Oleynik <dzo@simtreas.ru> 2001 + * Set process group corrections, initial busybox port + */ + +/*#define DEBUG 1 */ + +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <stdio.h> +#include <signal.h> +#include <termios.h> +#ifdef DEBUG +#define TELCMDS +#define TELOPTS +#endif +#include <arpa/telnet.h> +#include <ctype.h> +#include <sys/syslog.h> + +#include "busybox.h" + +#define BUFSIZE 4000 + +static const char *loginpath = "/bin/sh"; + +/* shell name and arguments */ + +static const char *argv_init[] = {NULL, NULL}; + +/* structure that describes a session */ + +struct tsession { + struct tsession *next; + int sockfd, ptyfd; + int shell_pid; + /* two circular buffers */ + char *buf1, *buf2; + int rdidx1, wridx1, size1; + int rdidx2, wridx2, size2; +}; + +/* + + This is how the buffers are used. The arrows indicate the movement + of data. + + +-------+ wridx1++ +------+ rdidx1++ +----------+ + | | <-------------- | buf1 | <-------------- | | + | | size1-- +------+ size1++ | | + | pty | | socket | + | | rdidx2++ +------+ wridx2++ | | + | | --------------> | buf2 | --------------> | | + +-------+ size2++ +------+ size2-- +----------+ + + Each session has got two buffers. + +*/ + +static int maxfd; + +static struct tsession *sessions; + + +/* + + Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored + and must be removed so as to not be interpreted by the terminal). Make an + uninterrupted string of characters fit for the terminal. Do this by packing + all characters meant for the terminal sequentially towards the end of bf. + + Return a pointer to the beginning of the characters meant for the terminal. + and make *num_totty the number of characters that should be sent to + the terminal. + + Note - If an IAC (3 byte quantity) starts before (bf + len) but extends + past (bf + len) then that IAC will be left unprocessed and *processed will be + less than len. + + FIXME - if we mean to send 0xFF to the terminal then it will be escaped, + what is the escape character? We aren't handling that situation here. + + */ +static char * +remove_iacs(struct tsession *ts, int *pnum_totty) { + unsigned char *ptr0 = ts->buf1 + ts->wridx1; + unsigned char *ptr = ptr0; + unsigned char *totty = ptr; + unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); + int processed; + int num_totty; + + while (ptr < end) { + if (*ptr != IAC) { + *totty++ = *ptr++; + } + else { + if ((ptr+2) < end) { + /* the entire IAC is contained in the buffer + we were asked to process. */ +#ifdef DEBUG + fprintf(stderr, "Ignoring IAC %s,%s\n", + *ptr, TELCMD(*(ptr+1)), TELOPT(*(ptr+2))); +#endif + ptr += 3; + } else { + /* only the beginning of the IAC is in the + buffer we were asked to process, we can't + process this char. */ + break; + } + } + } + + processed = ptr - ptr0; + num_totty = totty - ptr0; + /* the difference between processed and num_to tty + is all the iacs we removed from the stream. + Adjust buf1 accordingly. */ + ts->wridx1 += processed - num_totty; + ts->size1 -= processed - num_totty; + *pnum_totty = num_totty; + /* move the chars meant for the terminal towards the end of the + buffer. */ + return memmove(ptr - num_totty, ptr0, num_totty); +} + + +static int +getpty(char *line) +{ + int p; +#ifdef HAVE_DEVPTS_FS + p = open("/dev/ptmx", 2); + if (p > 0) { + grantpt(p); + unlockpt(p); + strcpy(line, ptsname(p)); + return(p); + } +#else + struct stat stb; + int i; + int j; + + strcpy(line, "/dev/ptyXX"); + + for (i = 0; i < 16; i++) { + line[8] = "pqrstuvwxyzabcde"[i]; + line[9] = '0'; + if (stat(line, &stb) < 0) { + continue; + } + for (j = 0; j < 16; j++) { + line[9] = j < 10 ? j + '0' : j - 10 + 'a'; + if ((p = open(line, O_RDWR | O_NOCTTY)) >= 0) { + line[5] = 't'; + return p; + } + } + } +#endif /* HAVE_DEVPTS_FS */ + return -1; +} + + +static void +send_iac(struct tsession *ts, unsigned char command, int option) +{ + /* We rely on that there is space in the buffer for now. */ + char *b = ts->buf2 + ts->rdidx2; + *b++ = IAC; + *b++ = command; + *b++ = option; + ts->rdidx2 += 3; + ts->size2 += 3; +} + + +static struct tsession * +make_new_session(int sockfd) +{ + struct termios termbuf; + int pty, pid; + char tty_name[32]; + struct tsession *ts = malloc(sizeof(struct tsession) + BUFSIZE * 2); + + ts->buf1 = (char *)(&ts[1]); + ts->buf2 = ts->buf1 + BUFSIZE; + + ts->sockfd = sockfd; + + ts->rdidx1 = ts->wridx1 = ts->size1 = 0; + ts->rdidx2 = ts->wridx2 = ts->size2 = 0; + + /* Got a new connection, set up a tty and spawn a shell. */ + + pty = getpty(tty_name); + + if (pty < 0) { + syslog_msg(LOG_USER, LOG_ERR, "All network ports in use!"); + return 0; + } + + if (pty > maxfd) + maxfd = pty; + + ts->ptyfd = pty; + + /* Make the telnet client understand we will echo characters so it + * should not do it locally. We don't tell the client to run linemode, + * because we want to handle line editing and tab completion and other + * stuff that requires char-by-char support. + */ + + send_iac(ts, DO, TELOPT_ECHO); + send_iac(ts, DO, TELOPT_LFLOW); + send_iac(ts, WILL, TELOPT_ECHO); + send_iac(ts, WILL, TELOPT_SGA); + + + if ((pid = fork()) < 0) { + syslog_msg(LOG_USER, LOG_ERR, "Can`t forking"); + } + if (pid == 0) { + /* In child, open the child's side of the tty. */ + int i; + + for(i = 0; i <= maxfd; i++) + close(i); + /* make new process group */ + setsid(); + + if (open(tty_name, O_RDWR /*| O_NOCTTY*/) < 0) { + syslog_msg(LOG_USER, LOG_ERR, "Could not open tty"); + exit(1); + } + dup(0); + dup(0); + + tcsetpgrp(0, getpid()); + + /* The pseudo-terminal allocated to the client is configured to operate in + * cooked mode, and with XTABS CRMOD enabled (see tty(4)). + */ + + tcgetattr(0, &termbuf); + termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */ + termbuf.c_oflag |= ONLCR|XTABS; + termbuf.c_iflag |= ICRNL; + termbuf.c_iflag &= ~IXOFF; + /*termbuf.c_lflag &= ~ICANON;*/ + tcsetattr(0, TCSANOW, &termbuf); + + /* exec shell, with correct argv and env */ + execv(loginpath, (char *const *)argv_init); + + /* NOT REACHED */ + syslog_msg(LOG_USER, LOG_ERR, "execv error"); + exit(1); + } + + ts->shell_pid = pid; + + return ts; +} + +static void +free_session(struct tsession *ts) +{ + struct tsession *t = sessions; + + /* Unlink this telnet session from the session list. */ + if(t == ts) + sessions = ts->next; + else { + while(t->next != ts) + t = t->next; + t->next = ts->next; + } + + kill(ts->shell_pid, SIGKILL); + + wait4(ts->shell_pid, NULL, 0, NULL); + + close(ts->ptyfd); + close(ts->sockfd); + + if(ts->ptyfd == maxfd || ts->sockfd == maxfd) + maxfd--; + if(ts->ptyfd == maxfd || ts->sockfd == maxfd) + maxfd--; + + free(ts); +} + +int +telnetd_main(int argc, char **argv) +{ + struct sockaddr_in sa; + int master_fd; + fd_set rdfdset, wrfdset; + int selret; + int on = 1; + int portnbr = 23; + int c; + + /* check if user supplied a port number */ + + for (;;) { + c = getopt( argc, argv, "p:l:"); + if (c == EOF) break; + switch (c) { + case 'p': + portnbr = atoi(optarg); + break; + case 'l': + loginpath = strdup (optarg); + break; + default: + show_usage(); + } + } + + if (access(loginpath, X_OK) < 0) { + error_msg_and_die ("'%s' unavailable.", loginpath); + } + + argv_init[0] = loginpath; + sessions = 0; + + /* Grab a TCP socket. */ + + master_fd = socket(AF_INET, SOCK_STREAM, 0); + if (master_fd < 0) { + perror_msg_and_die("socket"); + } + (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + /* Set it to listen to specified port. */ + + memset((void *)&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(portnbr); + + if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + perror_msg_and_die("bind"); + } + + if (listen(master_fd, 1) < 0) { + perror_msg_and_die("listen"); + } + + if (daemon(0, 0) < 0) + perror_msg_and_die("daemon"); + + + maxfd = master_fd; + + do { + struct tsession *ts; + + FD_ZERO(&rdfdset); + FD_ZERO(&wrfdset); + + /* select on the master socket, all telnet sockets and their + * ptys if there is room in their respective session buffers. + */ + + FD_SET(master_fd, &rdfdset); + + ts = sessions; + while (ts) { + /* buf1 is used from socket to pty + * buf2 is used from pty to socket + */ + if (ts->size1 > 0) { + FD_SET(ts->ptyfd, &wrfdset); /* can write to pty */ + } + if (ts->size1 < BUFSIZE) { + FD_SET(ts->sockfd, &rdfdset); /* can read from socket */ + } + if (ts->size2 > 0) { + FD_SET(ts->sockfd, &wrfdset); /* can write to socket */ + } + if (ts->size2 < BUFSIZE) { + FD_SET(ts->ptyfd, &rdfdset); /* can read from pty */ + } + ts = ts->next; + } + + selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0); + + if (!selret) + break; + + /* First check for and accept new sessions. */ + if (FD_ISSET(master_fd, &rdfdset)) { + int fd, salen; + + salen = sizeof(sa); + if ((fd = accept(master_fd, (struct sockaddr *)&sa, + &salen)) < 0) { + continue; + } else { + /* Create a new session and link it into + our active list. */ + struct tsession *new_ts = make_new_session(fd); + if (new_ts) { + new_ts->next = sessions; + sessions = new_ts; + if (fd > maxfd) + maxfd = fd; + } else { + close(fd); + } + } + } + + /* Then check for data tunneling. */ + + ts = sessions; + while (ts) { /* For all sessions... */ + int maxlen, w, r; + struct tsession *next = ts->next; /* in case we free ts. */ + + if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) { + int num_totty; + char *ptr; + /* Write to pty from buffer 1. */ + + ptr = remove_iacs(ts, &num_totty); + + w = write(ts->ptyfd, ptr, num_totty); + if (w < 0) { + free_session(ts); + ts = next; + continue; + } + ts->wridx1 += w; + ts->size1 -= w; + if (ts->wridx1 == BUFSIZE) + ts->wridx1 = 0; + } + + if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) { + /* Write to socket from buffer 2. */ + maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2); + w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen); + if (w < 0) { + free_session(ts); + ts = next; + continue; + } + ts->wridx2 += w; + ts->size2 -= w; + if (ts->wridx2 == BUFSIZE) + ts->wridx2 = 0; + } + + if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) { + /* Read from socket to buffer 1. */ + maxlen = MIN(BUFSIZE - ts->rdidx1, + BUFSIZE - ts->size1); + r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen); + if (!r || (r < 0 && errno != EINTR)) { + free_session(ts); + ts = next; + continue; + } + if(!*(ts->buf1 + ts->rdidx1 + r - 1)) { + r--; + if(!r) + continue; + } + ts->rdidx1 += r; + ts->size1 += r; + if (ts->rdidx1 == BUFSIZE) + ts->rdidx1 = 0; + } + + if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) { + /* Read from pty to buffer 2. */ + maxlen = MIN(BUFSIZE - ts->rdidx2, + BUFSIZE - ts->size2); + r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen); + if (!r || (r < 0 && errno != EINTR)) { + free_session(ts); + ts = next; + continue; + } + ts->rdidx2 += r; + ts->size2 += r; + if (ts->rdidx2 == BUFSIZE) + ts->rdidx2 = 0; + } + + if (ts->size1 == 0) { + ts->rdidx1 = 0; + ts->wridx1 = 0; + } + if (ts->size2 == 0) { + ts->rdidx2 = 0; + ts->wridx2 = 0; + } + ts = next; + } + + } while (1); + + return 0; +} |