[git commit] telnetd: do close pty when network read returns EOF; try to cause EOF-on-read
Denys Vlasenko
vda.linux at googlemail.com
Sun Feb 22 06:11:16 UTC 2026
commit: https://git.busybox.net/busybox/commit/?id=20387b703bac7590118c0351dc507b5124bea38d
branch: https://git.busybox.net/busybox/log/?h=master
function old new delta
fabricate_ctrl_D_on_pty - 65 +65
net_to_pty__have_data_to_write 368 429 +61
.rodata 107096 107125 +29
telnetd_main 367 372 +5
net_to_pty__have_buffer_to_read_into 164 162 -2
packed_usage 36066 36040 -26
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 3/2 up/down: 160/-28) Total: 132 bytes
Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
networking/telnetd.c | 102 +++++++++++++++++++++++++++++++++------------------
1 file changed, 66 insertions(+), 36 deletions(-)
diff --git a/networking/telnetd.c b/networking/telnetd.c
index fbc218980..0d2489a2e 100644
--- a/networking/telnetd.c
+++ b/networking/telnetd.c
@@ -127,6 +127,7 @@
//usage: "\n -S Log to syslog (implied by -i or without -F and -w)"
//usage: )
//usage: )
+//usage: "\n -v Verbose"
#include "libbb.h"
#include "common_bufsiz.h"
@@ -172,6 +173,7 @@ struct globals {
ioloop_state_t io;
const char *loginpath;
const char *issuefile;
+ unsigned verbose;
IF_FEATURE_TELNETD_INETD_WAIT(unsigned sec_linger;)
} FIX_ALIASING;
#define G (*(struct globals*)bb_common_bufsiz1)
@@ -181,6 +183,34 @@ struct globals {
G.issuefile = "/etc/issue.net"; \
} while (0)
+#define log1(...) do { if (G.verbose) bb_error_msg(__VA_ARGS__); } while (0)
+
+static NOINLINE void fabricate_ctrl_D_on_pty(int fd)
+{
+ struct termios tio;
+ // Master pty can see slave's termios - abuse that
+ if (tcgetattr(fd, &tio) != 0) {
+ dbg_close("%s: no termios(%d)", __func__, fd);
+ return;
+ }
+ dbg_close("%s: termios(%d) ICANON:%d,VEOF:%02x", __func__,
+ fd, !!(tio.c_lflag & ICANON), (uint8_t)tio.c_cc[VEOF]);
+ if ((tio.c_lflag & ICANON) && tio.c_cc[VEOF] != 0) {
+ // _Should_ look like EOF on input on the slave side...
+ write(fd, &tio.c_cc[VEOF], 1); // usually ^D
+ // One ^D often won't do. If a program was blocked on read already,
+ // ^D makes _that_ read return:
+ // read(0, "some data", 64) = 9
+ // but the program will likely read more
+ // (it did not interpret above as EOF), block, and die:
+ // read(0, 0x123456, 64) = ...<blocked>... -1 EIO
+ // +++ killed by SIGHUP +++
+ // Well, I'm not greedy...
+ write(fd, &tio.c_cc[VEOF], 1); // here is another EOF for you
+ // If this doesn't work, they can see 0x04 bytes. Tough cookies
+ }
+}
+
#define TELNET_CONNECTION \
STRUCT_CONNECTION \
pid_t shell_pid;
@@ -248,7 +278,7 @@ static void ALWAYS_INLINE remove_and_free_to_net(pty_to_net_t *ts)
// Theory of operation
// (AKA "when should I close fds? when should I detach from ioloop?").
-// The fds are named read)fs and write_fd, but for clarity let's call them netfd and ptyfd.
+// The fds are named read_fs and write_fd, but for clarity let's call them netfd and ptyfd.
// net_to_pty::have_data_to_write
// net_to_pty::have_buffer_to_read_into
// if ptyfd < 0: //sibling told us ptyfd is down?
@@ -282,7 +312,6 @@ static int net_to_pty__have_data_to_write(void *this)
if (size == 0)
return 0;
-
buf = BUF2PTY(ts) + ts->wridx;
if (ts->skip_LF_or_NUL) {
/* last char we wrote was '\r' */
@@ -369,6 +398,7 @@ static int net_to_pty__have_data_to_write(void *this)
ws.ws_col = (buf[3] << 8) | buf[4];
ws.ws_row = (buf[5] << 8) | buf[6];
//TODO: sanity check: are they nonzero?
+ log1("pfd:%d window size:%dx%d", ts->write_fd, ws.ws_row, ws.ws_col);
ioctl(ts->write_fd, TIOCSWINSZ, (char *)&ws);
increment = 7;
goto inc;
@@ -481,36 +511,35 @@ static int net_to_pty__have_buffer_to_read_into(void *this)
if (ts->read_fd < 0) { /* we've seen EOF/error on netfd */
dbg_close("EOF on netfd was seen, ts->size:%d", ts->size);
if (ts->size == 0) { /* we wrote everything */
- if (!ts->sibling) { /* if not in use by sibling... */
- /* close ptyfd, but after a pause */
- unsigned rem;
-
- /* pty has a design problem: close(master_ptyfd)
- * is defined as causing hangup on slave pty.
- * Which discards all currently buffered unread data
- * (in our case, all data we just wrote).
- * usleep(1000) is a crude solution.
- * A better one (does not block other conns):
- */
- dbg_close("going to close ptyfd:%d deadline_us:%u",
- ts->write_fd, ts->deadline_us);
- if (!ts->deadline_us) {
- ts->deadline_us = (monotonic_us() + 1000) | 1;
- rem = 1000;
- } else {
- rem = ts->deadline_us - monotonic_us();
- }
- dbg_close("until ptyfd_close:%d", rem);
- if (rem <= 1000) {
- ioloop_decrease_current_timeout(ts->io, rem);
- return 0; /* "do not read" */
- }
- /* rem undeflowed (is "negative"): we waited at least 1000us */
- dbg_close("EOF on netfd, closing ptyfd:%d", ts->write_fd);
- close(ts->write_fd);
+ unsigned rem;
+ /* close ptyfd, but after a 20 ms pause */
+
+ /* pty has a design problem: close(master_ptyfd)
+ * is defined as causing hangup on slave pty.
+ * Which discards all currently buffered unread data
+ * (in our case, all data we just wrote).
+ * usleep(20000) is a crude solution.
+ * A better one (does not block other conns):
+ */
+ dbg_close("going to close ptyfd:%d deadline_us:%u",
+ ts->write_fd, ts->deadline_us);
+ if (!ts->deadline_us) {
+ fabricate_ctrl_D_on_pty(ts->write_fd);
+ ts->deadline_us = (monotonic_us() + 20000) | 1;
+ rem = 20000;
+ } else {
+ rem = ts->deadline_us - monotonic_us();
+ }
+ dbg_close("until ptyfd_close:%d", rem);
+ if (rem <= 20000) {
+ ioloop_decrease_current_timeout(ts->io, rem);
+ return 0; /* "do not read" */
}
- ts->write_fd = -1;
- remove_and_free_to_pty(ts);
+ /* rem undeflowed (is "negative"): we waited at least 1000us */
+ dbg_close("EOF on netfd, closing ptyfd:%d", ts->write_fd);
+ if (ts->sibling)
+ ts->sibling->read_fd = -1;
+ remove_and_free_to_pty(ts); /* closes ts->write_fd (ptyfd) */
return -1; /* "don't use me anymore, I'm gone" */
}
return 0; /* "don't want to read" */
@@ -543,7 +572,7 @@ static int net_to_pty__read(void *this)
if (!ts->sibling)
close(ts->read_fd); /* close netfd if not in use by sibling */
ts->read_fd = -1;
- return 0; /* "didn't read anything */
+ return 0; /* "didn't read anything" */
}
ts->size += count;
@@ -1591,13 +1620,14 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv)
IF_FEATURE_TELNETD_SELFTEST_DEBUG("@")
"f:l:Ki"
IF_FEATURE_TELNETD_STANDALONE("p:b:F")
- IF_FEATURE_TELNETD_INETD_WAIT("Sw:+") /* -w NUM */
+ IF_FEATURE_TELNETD_INETD_WAIT("Sw:+v") /* -w NUM */
"\0"
- /* -w implies -F. -w and -i don't mix */
- IF_FEATURE_TELNETD_INETD_WAIT("wF:i--w:w--i"),
- &G.issuefile, &G.loginpath
+ /* -w implies -F. -w and -i don't mix. -v counter */
+ IF_FEATURE_TELNETD_INETD_WAIT("wF:i--w:w--i:vv")
+ , &G.issuefile, &G.loginpath
IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)
IF_FEATURE_TELNETD_INETD_WAIT(, &G.io.max_timeout)
+ , &G.verbose
);
#if ENABLE_FEATURE_TELNETD_SELFTEST_DEBUG
More information about the busybox-cvs
mailing list