telnetd: at Alexander Kriegisch <Alexander@kriegisch.name> insistence

add an option to close sessions as soon as child exits.
Maybe it should be a CONFIG option. OTOH, maybe it should be always on,
as it mimics, say, getty's behaviour.

function                                             old     new   delta
handle_sigchld                                         -      49     +49
telnetd_main                                        1312    1355     +43
.rodata                                           123429  123466     +37
packed_usage                                       22770   22806     +36
make_new_session                                     525     532      +7
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 4/0 up/down: 172/0)             Total: 172 bytes
   text    data     bss     dec     hex filename
 676285    2538   12104  690927   a8aef busybox_old
 676421    2538   12104  691063   a8b77 busybox_unstripped
This commit is contained in:
Denis Vlasenko 2007-10-15 22:09:15 +00:00
parent 10916c5c6b
commit 2450c450ab
2 changed files with 66 additions and 35 deletions

View File

@ -3517,6 +3517,8 @@ USE_FEATURE_RUN_PARTS_FANCY("\n -l Prints names of all matching files even when
"\n\nOptions:" \ "\n\nOptions:" \
"\n -l LOGIN Exec LOGIN on connect" \ "\n -l LOGIN Exec LOGIN on connect" \
"\n -f issue_file Display issue_file instead of /etc/issue" \ "\n -f issue_file Display issue_file instead of /etc/issue" \
"\n -K Close connection as soon as login exits" \
"\n (normally wait until all programs close slave pty)" \
USE_FEATURE_TELNETD_STANDALONE( \ USE_FEATURE_TELNETD_STANDALONE( \
"\n -p PORT Port to listen to" \ "\n -p PORT Port to listen to" \
"\n -b ADDR Address to bind to" \ "\n -b ADDR Address to bind to" \

View File

@ -37,7 +37,7 @@
struct tsession { struct tsession {
struct tsession *next; struct tsession *next;
int sockfd_read, sockfd_write, ptyfd; int sockfd_read, sockfd_write, ptyfd;
/*int shell_pid;*/ int shell_pid;
/* two circular buffers */ /* two circular buffers */
/*char *buf1, *buf2;*/ /*char *buf1, *buf2;*/
@ -265,7 +265,7 @@ make_new_session(
} }
if (pid > 0) { if (pid > 0) {
/* Parent */ /* Parent */
/*ts->shell_pid = pid;*/ ts->shell_pid = pid;
return ts; return ts;
} }
@ -305,7 +305,8 @@ make_new_session(
login_argv[0] = loginpath; login_argv[0] = loginpath;
login_argv[1] = NULL; login_argv[1] = NULL;
execvp(loginpath, (char **)login_argv); 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);*/ _exit(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
} }
@ -357,6 +358,24 @@ void free_session(struct tsession *ts);
#endif #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) MAIN_EXTERNALLY_VISIBLE;
int telnetd_main(int argc, char **argv) int telnetd_main(int argc, char **argv)
@ -379,29 +398,35 @@ int telnetd_main(int argc, char **argv)
}; };
#endif #endif
enum { enum {
OPT_INETD = (1 << 2) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */ OPT_WATCHCHILD = (1 << 2), /* -K */
OPT_PORT = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */ OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
OPT_FOREGROUND = (1 << 5) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */ OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
OPT_FOREGROUND = (1 << 6) * 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 */ * 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 &issuefile, &loginpath
USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)); 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/wthout -F, -i */
bb_daemonize_or_rexec(0 /*DAEMON_CHDIR_ROOT*/, argv);
}
}
/* Redirect log to syslog early, if needed */ /* Redirect log to syslog early, if needed */
if (IS_INETD || !(opt & OPT_FOREGROUND)) { if (IS_INETD || !(opt & OPT_FOREGROUND)) {
openlog(applet_name, 0, LOG_USER); openlog(applet_name, 0, LOG_USER);
logmode = LOGMODE_SYSLOG; logmode = LOGMODE_SYSLOG;
} }
//if (opt & 1) // -f
//if (opt & 2) // -l
USE_FEATURE_TELNETD_STANDALONE( USE_FEATURE_TELNETD_STANDALONE(
if (opt & OPT_PORT) // -p if (opt & OPT_PORT)
portnbr = xatou16(opt_portnbr); 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. /* Used to check access(loginpath, X_OK) here. Pointless.
@ -413,12 +438,8 @@ int telnetd_main(int argc, char **argv)
if (!sessions) /* pty opening or vfork problem, exit */ if (!sessions) /* pty opening or vfork problem, exit */
return 1; /* make_new_session prints error message */ return 1; /* make_new_session prints error message */
} else { } else {
//vda: inform that we start in standalone mode?
master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr); master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
xlisten(master_fd, 1); xlisten(master_fd, 1);
if (!(opt & OPT_FOREGROUND))
//vda: NOMMU?
bb_daemonize(DAEMON_CHDIR_ROOT);
} }
#else #else
sessions = make_new_session(); sessions = make_new_session();
@ -429,6 +450,9 @@ int telnetd_main(int argc, char **argv)
/* We don't want to die if just one session is broken */ /* We don't want to die if just one session is broken */
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
if (opt & OPT_WATCHCHILD)
signal(SIGCHLD, handle_sigchld);
/* /*
This is how the buffers are used. The arrows indicate the movement This is how the buffers are used. The arrows indicate the movement
of data. of data.
@ -450,14 +474,6 @@ int telnetd_main(int argc, char **argv)
again: again:
FD_ZERO(&rdfdset); FD_ZERO(&rdfdset);
FD_ZERO(&wrfdset); 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 /* Select on the master socket, all telnet sockets and their
* ptys if there is room in their session buffers. * ptys if there is room in their session buffers.
@ -465,15 +481,28 @@ int telnetd_main(int argc, char **argv)
* before each select. Can be a problem with 500+ connections. */ * before each select. Can be a problem with 500+ connections. */
ts = sessions; ts = sessions;
while (ts) { while (ts) {
if (ts->size1 > 0) /* can write to pty */ struct tsession *next = ts->next; /* in case we free ts. */
FD_SET(ts->ptyfd, &wrfdset); if (ts->shell_pid == -1) {
if (ts->size1 < BUFSIZE) /* can read from socket */ free_session(ts);
FD_SET(ts->sockfd_read, &rdfdset); } else {
if (ts->size2 > 0) /* can write to socket */ if (ts->size1 > 0) /* can write to pty */
FD_SET(ts->sockfd_write, &wrfdset); FD_SET(ts->ptyfd, &wrfdset);
if (ts->size2 < BUFSIZE) /* can read from pty */ if (ts->size1 < BUFSIZE) /* can read from socket */
FD_SET(ts->ptyfd, &rdfdset); FD_SET(ts->sockfd_read, &rdfdset);
ts = ts->next; 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;
} }
count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL); count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);