struct tsession {
struct tsession *next;
int sockfd_read, sockfd_write, ptyfd;
- /*int shell_pid;*/
+ int shell_pid;
/* two circular buffers */
/*char *buf1, *buf2;*/
/*#define TS_BUF1 ts->buf1*/
/*#define TS_BUF2 TS_BUF2*/
-#define TS_BUF1 ((char*)(ts + 1))
-#define TS_BUF2 (((char*)(ts + 1)) + BUFSIZE)
+#define TS_BUF1 ((unsigned char*)(ts + 1))
+#define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE)
int rdidx1, wridx1, size1;
int rdidx2, wridx2, size2;
};
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.
- CR-LF ->'s CR mapping is also done here, for convenience
+ CR-LF ->'s CR mapping is also done here, for convenience.
+
+ NB: may fail to remove iacs which wrap around buffer!
*/
-static char *
+static unsigned char *
remove_iacs(struct tsession *ts, int *pnum_totty)
{
- unsigned char *ptr0 = (unsigned char *)TS_BUF1 + ts->wridx1;
+ 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) {
- int c = *ptr;
- *totty++ = *ptr++;
+ char c = *ptr;
+
+ *totty++ = c;
+ ptr++;
/* We now map \r\n ==> \r for pragmatic reasons.
* Many client implementations send \r\n when
* the user hits the CarriageReturn key.
*/
- if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end)
+ if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
ptr++;
} else {
/*
*/
else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
struct winsize ws;
+
if ((ptr+8) >= end)
break; /* incomplete, can't process */
ws.ws_col = (ptr[3] << 8) | ptr[4];
}
}
- 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. */
+ /* the difference between ptr and totty is number of iacs
+ we removed from the stream. Adjust buf1 accordingly. */
+ if ((ptr - totty) == 0) /* 99.999% of cases */
+ return ptr0;
+ ts->wridx1 += ptr - totty;
+ ts->size1 -= ptr - totty;
+ /* move chars meant for the terminal towards the end of the buffer */
return memmove(ptr - num_totty, ptr0, num_totty);
}
static struct tsession *
make_new_session(
- USE_FEATURE_TELNETD_STANDALONE(int sock_r, int sock_w)
+ USE_FEATURE_TELNETD_STANDALONE(int sock)
SKIP_FEATURE_TELNETD_STANDALONE(void)
) {
const char *login_argv[2];
bb_error_msg("can't create pty");
return NULL;
}
- if (fd > maxfd) maxfd = fd;
+ if (fd > maxfd)
+ maxfd = fd;
ts->ptyfd = fd;
ndelay_on(fd);
#if ENABLE_FEATURE_TELNETD_STANDALONE
- if (sock_w > maxfd) maxfd = sock_w;
- ts->sockfd_write = sock_w;
- ndelay_on(sock_w);
- if (sock_r > maxfd) maxfd = sock_r;
- ts->sockfd_read = sock_r;
- ndelay_on(sock_r);
+ ts->sockfd_read = sock;
+ ndelay_on(sock);
+ if (!sock) { /* We are called with fd 0 - we are in inetd mode */
+ sock++; /* so use fd 1 for output */
+ ndelay_on(sock);
+ }
+ ts->sockfd_write = sock;
+ if (sock > maxfd)
+ maxfd = sock;
#else
- ts->sockfd_write = 1;
/* ts->sockfd_read = 0; - done by xzalloc */
+ ts->sockfd_write = 1;
ndelay_on(0);
ndelay_on(1);
#endif
if (pid < 0) {
free(ts);
close(fd);
- /* sock_r and sock_w will be closed by caller */
+ /* sock will be closed by caller */
bb_perror_msg("vfork");
return NULL;
}
if (pid > 0) {
/* Parent */
- /*ts->shell_pid = pid;*/
+ ts->shell_pid = pid;
return ts;
}
login_argv[0] = loginpath;
login_argv[1] = NULL;
execvp(loginpath, (char **)login_argv);
- /* Safer with vfork, and we shouldn't send this to the client anyway */
+ /* Safer with vfork, and we shouldn't send message
+ * to remote clients anyway */
_exit(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
}
+/* Must match getopt32 string */
+enum {
+ OPT_WATCHCHILD = (1 << 2), /* -K */
+ OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
+ OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
+ OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
+};
+
#if ENABLE_FEATURE_TELNETD_STANDALONE
static void
{
struct tsession *t = sessions;
+ if (option_mask32 & OPT_INETD)
+ exit(0);
+
/* Unlink this telnet session from the session list */
if (t == ts)
sessions = ts->next;
t->next = ts->next;
}
+#if 0
/* It was said that "normal" telnetd just closes ptyfd,
* doesn't send SIGKILL. When we close ptyfd,
* kernel sends SIGHUP to processes having slave side opened. */
- /*kill(ts->shell_pid, SIGKILL);
- wait4(ts->shell_pid, NULL, 0, NULL);*/
+ kill(ts->shell_pid, SIGKILL);
+ wait4(ts->shell_pid, NULL, 0, NULL);
+#endif
close(ts->ptyfd);
close(ts->sockfd_read);
- /* error if ts->sockfd_read == ts->sockfd_write. So what? ;) */
- close(ts->sockfd_write);
+ /* We do not need to close(ts->sockfd_write), it's the same
+ * as sockfd_read unless we are in inetd mode. But in inetd mode
+ * we do not reach this */
free(ts);
/* Scan all sessions and find new maxfd */
- ts = sessions;
maxfd = 0;
+ ts = sessions;
while (ts) {
if (maxfd < ts->ptyfd)
maxfd = ts->ptyfd;
if (maxfd < ts->sockfd_read)
maxfd = ts->sockfd_read;
+#if 0
+ /* Again, sockfd_write == sockfd_read here */
if (maxfd < ts->sockfd_write)
maxfd = ts->sockfd_write;
+#endif
ts = ts->next;
}
}
#else /* !FEATURE_TELNETD_STANDALONE */
-/* Never actually called */
-void free_session(struct tsession *ts);
+/* Used in main() only, thus exits. */
+#define free_session(ts) return 0
#endif
+static void handle_sigchld(int sig)
+{
+ pid_t pid;
+ struct tsession *ts;
+
+ pid = waitpid(-1, &sig, WNOHANG);
+ if (pid > 0) {
+ ts = sessions;
+ while (ts) {
+ if (ts->shell_pid == pid) {
+ ts->shell_pid = -1;
+ return;
+ }
+ ts = ts->next;
+ }
+ }
+}
+
int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int telnetd_main(int argc, char **argv)
{
fd_set rdfdset, wrfdset;
unsigned opt;
- int selret, maxlen, w, r;
+ int count;
struct tsession *ts;
#if ENABLE_FEATURE_TELNETD_STANDALONE
#define IS_INETD (opt & OPT_INETD)
portnbr = 23,
};
#endif
- enum {
- OPT_INETD = (1 << 2) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
- OPT_PORT = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
- OPT_FOREGROUND = (1 << 5) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
- };
-
- /* If !STANDALONE, we accept (and ignore) -i, thus people
+ /* Even if !STANDALONE, we accept (and ignore) -i, thus people
* don't need to guess whether it's ok to pass -i to us */
- opt = getopt32(argv, "f:l:i" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
+ opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
&issuefile, &loginpath
USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
+ if (!IS_INETD /*&& !re_execed*/) {
+ /* inform that we start in standalone mode?
+ * May be useful when people forget to give -i */
+ /*bb_error_msg("listening for connections");*/
+ if (!(opt & OPT_FOREGROUND)) {
+ /* DAEMON_CHDIR_ROOT was giving inconsistent
+ * behavior with/without -F, -i */
+ bb_daemonize_or_rexec(0 /*DAEMON_CHDIR_ROOT*/, argv);
+ }
+ }
/* Redirect log to syslog early, if needed */
if (IS_INETD || !(opt & OPT_FOREGROUND)) {
openlog(applet_name, 0, LOG_USER);
logmode = LOGMODE_SYSLOG;
}
- //if (opt & 1) // -f
- //if (opt & 2) // -l
USE_FEATURE_TELNETD_STANDALONE(
- if (opt & OPT_PORT) // -p
+ if (opt & OPT_PORT)
portnbr = xatou16(opt_portnbr);
- //if (opt & 8) // -b
- //if (opt & 0x10) // -F
- //if (opt & 0x20) // -i
);
/* Used to check access(loginpath, X_OK) here. Pointless.
#if ENABLE_FEATURE_TELNETD_STANDALONE
if (IS_INETD) {
- sessions = make_new_session(0, 1);
+ sessions = make_new_session(0);
if (!sessions) /* pty opening or vfork problem, exit */
return 1; /* make_new_session prints error message */
} else {
-//vda: inform that we start in standalone mode?
master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
xlisten(master_fd, 1);
- if (!(opt & OPT_FOREGROUND))
-//vda: NOMMU?
- bb_daemonize(DAEMON_CHDIR_ROOT);
}
#else
sessions = make_new_session();
/* We don't want to die if just one session is broken */
signal(SIGPIPE, SIG_IGN);
+ if (opt & OPT_WATCHCHILD)
+ signal(SIGCHLD, handle_sigchld);
+
/*
This is how the buffers are used. The arrows indicate the movement
of data.
again:
FD_ZERO(&rdfdset);
FD_ZERO(&wrfdset);
- if (!IS_INETD) {
- FD_SET(master_fd, &rdfdset);
- /* This is needed because free_session() does not
- * take into account master_fd when it finds new
- * maxfd among remaining fd's: */
- if (master_fd > maxfd)
- maxfd = master_fd;
- }
/* Select on the master socket, all telnet sockets and their
* ptys if there is room in their session buffers.
* before each select. Can be a problem with 500+ connections. */
ts = sessions;
while (ts) {
- if (ts->size1 > 0) /* can write to pty */
- FD_SET(ts->ptyfd, &wrfdset);
- if (ts->size1 < BUFSIZE) /* can read from socket */
- FD_SET(ts->sockfd_read, &rdfdset);
- if (ts->size2 > 0) /* can write to socket */
- FD_SET(ts->sockfd_write, &wrfdset);
- if (ts->size2 < BUFSIZE) /* can read from pty */
- FD_SET(ts->ptyfd, &rdfdset);
- ts = ts->next;
+ struct tsession *next = ts->next; /* in case we free ts. */
+ if (ts->shell_pid == -1) {
+ /* Child died ad we detected that */
+ free_session(ts);
+ } else {
+ if (ts->size1 > 0) /* can write to pty */
+ FD_SET(ts->ptyfd, &wrfdset);
+ if (ts->size1 < BUFSIZE) /* can read from socket */
+ FD_SET(ts->sockfd_read, &rdfdset);
+ if (ts->size2 > 0) /* can write to socket */
+ FD_SET(ts->sockfd_write, &wrfdset);
+ if (ts->size2 < BUFSIZE) /* can read from pty */
+ FD_SET(ts->ptyfd, &rdfdset);
+ }
+ ts = next;
+ }
+ if (!IS_INETD) {
+ FD_SET(master_fd, &rdfdset);
+ /* This is needed because free_session() does not
+ * take into account master_fd when it finds new
+ * maxfd among remaining fd's */
+ if (master_fd > maxfd)
+ maxfd = master_fd;
}
- selret = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
- if (selret < 0)
+ count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
+ if (count < 0)
goto again; /* EINTR or ENOMEM */
#if ENABLE_FEATURE_TELNETD_STANDALONE
if (fd < 0)
goto again;
/* Create a new session and link it into our active list */
- new_ts = make_new_session(fd, fd);
+ new_ts = make_new_session(fd);
if (new_ts) {
new_ts->next = sessions;
sessions = new_ts;
if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
int num_totty;
- char *ptr;
+ unsigned char *ptr;
/* Write to pty from buffer 1. */
ptr = remove_iacs(ts, &num_totty);
- w = safe_write(ts->ptyfd, ptr, num_totty);
- if (w < 0) {
+ count = safe_write(ts->ptyfd, ptr, num_totty);
+ if (count < 0) {
if (errno == EAGAIN)
goto skip1;
- if (IS_INETD)
- return 0;
- free_session(ts);
- ts = next;
- continue;
+ goto kill_session;
}
- ts->size1 -= w;
- ts->wridx1 += w;
+ ts->size1 -= count;
+ ts->wridx1 += count;
if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
ts->wridx1 = 0;
}
skip1:
if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
/* Write to socket from buffer 2. */
- maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
- w = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, maxlen);
- if (w < 0) {
+ count = MIN(BUFSIZE - ts->wridx2, ts->size2);
+ count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
+ if (count < 0) {
if (errno == EAGAIN)
goto skip2;
- if (IS_INETD)
- return 0;
- free_session(ts);
- ts = next;
- continue;
+ goto kill_session;
}
- ts->size2 -= w;
- ts->wridx2 += w;
+ ts->size2 -= count;
+ ts->wridx2 += count;
if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
ts->wridx2 = 0;
}
skip2:
-#if 0
- /* Not strictly needed, but allows for bigger reads in common case */
+ /* Should not be needed, but... remove_iacs is actually buggy
+ * (it cannot process iacs which wrap around buffer's end)!
+ * Since properly fixing it requires writing bigger code,
+ * we rely instead on this code making it virtually impossible
+ * to have wrapped iac (people don't type at 2k/second).
+ * It also allows for bigger reads in common case. */
if (ts->size1 == 0) {
ts->rdidx1 = 0;
ts->wridx1 = 0;
ts->rdidx2 = 0;
ts->wridx2 = 0;
}
-#endif
+
if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
/* Read from socket to buffer 1. */
- maxlen = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
- r = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, maxlen);
- if (r <= 0) {
- if (r < 0 && errno == EAGAIN)
+ count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
+ count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
+ if (count <= 0) {
+ if (count < 0 && errno == EAGAIN)
goto skip3;
- if (IS_INETD)
- return 0;
- free_session(ts);
- ts = next;
- continue;
+ goto kill_session;
}
/* Ignore trailing NUL if it is there */
- if (!TS_BUF1[ts->rdidx1 + r - 1]) {
- if (!--r)
- goto skip3;
+ if (!TS_BUF1[ts->rdidx1 + count - 1]) {
+ --count;
}
- ts->size1 += r;
- ts->rdidx1 += r;
+ ts->size1 += count;
+ ts->rdidx1 += count;
if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
ts->rdidx1 = 0;
}
skip3:
if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
/* Read from pty to buffer 2. */
- maxlen = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
- r = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, maxlen);
- if (r <= 0) {
- if (r < 0 && errno == EAGAIN)
+ count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
+ count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
+ if (count <= 0) {
+ if (count < 0 && errno == EAGAIN)
goto skip4;
- if (IS_INETD)
- return 0;
- free_session(ts);
- ts = next;
- continue;
+ goto kill_session;
}
- ts->size2 += r;
- ts->rdidx2 += r;
+ ts->size2 += count;
+ ts->rdidx2 += count;
if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
ts->rdidx2 = 0;
}
skip4:
ts = next;
+ continue;
+ kill_session:
+ free_session(ts);
+ ts = next;
}
+
goto again;
}