diff options
-rw-r--r-- | include/usage.h | 2 | ||||
-rw-r--r-- | networking/telnetd.c | 99 |
2 files changed, 66 insertions, 35 deletions
diff --git a/include/usage.h b/include/usage.h index 210e1f246..1e05532d5 100644 --- a/include/usage.h +++ b/include/usage.h @@ -3517,6 +3517,8 @@ USE_FEATURE_RUN_PARTS_FANCY("\n -l Prints names of all matching files even when "\n\nOptions:" \ "\n -l LOGIN Exec LOGIN on connect" \ "\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( \ "\n -p PORT Port to listen to" \ "\n -b ADDR Address to bind to" \ diff --git a/networking/telnetd.c b/networking/telnetd.c index 85c2ebc44..96620d949 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -37,7 +37,7 @@ struct tsession { struct tsession *next; int sockfd_read, sockfd_write, ptyfd; - /*int shell_pid;*/ + int shell_pid; /* two circular buffers */ /*char *buf1, *buf2;*/ @@ -265,7 +265,7 @@ make_new_session( } if (pid > 0) { /* Parent */ - /*ts->shell_pid = pid;*/ + ts->shell_pid = pid; return ts; } @@ -305,7 +305,8 @@ make_new_session( 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);*/ } @@ -357,6 +358,24 @@ void free_session(struct tsession *ts); #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) @@ -379,29 +398,35 @@ int telnetd_main(int argc, char **argv) }; #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 */ + 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 !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/wthout -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. @@ -413,12 +438,8 @@ int telnetd_main(int argc, char **argv) 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(); @@ -429,6 +450,9 @@ int telnetd_main(int argc, char **argv) /* 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. @@ -450,14 +474,6 @@ int telnetd_main(int argc, char **argv) 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. @@ -465,15 +481,28 @@ int telnetd_main(int argc, char **argv) * 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) { + 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; } count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL); |