From a4bcbd0e0416bb4965488ec1f16039aa302f8b91 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 9 Jun 2009 23:01:24 +0200 Subject: telnetd: properly close fds in child Signed-off-by: Denys Vlasenko --- networking/telnetd.c | 157 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 63 deletions(-) (limited to 'networking/telnetd.c') diff --git a/networking/telnetd.c b/networking/telnetd.c index b7162ad36..6a8190b14 100644 --- a/networking/telnetd.c +++ b/networking/telnetd.c @@ -11,14 +11,14 @@ * * 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. + * 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 2001 - * Set process group corrections, initial busybox port + * Set process group corrections, initial busybox port */ #define DEBUG 0 @@ -40,10 +40,10 @@ struct tsession { /* two circular buffers */ /*char *buf1, *buf2;*/ -/*#define TS_BUF1 ts->buf1*/ -/*#define TS_BUF2 TS_BUF2*/ -#define TS_BUF1 ((unsigned char*)(ts + 1)) -#define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE) +/*#define TS_BUF1(ts) ts->buf1*/ +/*#define TS_BUF2(ts) TS_BUF2(ts)*/ +#define TS_BUF1(ts) ((unsigned char*)(ts + 1)) +#define TS_BUF2(ts) (((unsigned char*)(ts + 1)) + BUFSIZE) int rdidx1, wridx1, size1; int rdidx2, wridx2, size2; }; @@ -54,10 +54,17 @@ enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 }; /* Globals */ -static int maxfd; -static struct tsession *sessions; -static const char *loginpath = "/bin/login"; -static const char *issuefile = "/etc/issue.net"; +struct globals { + struct tsession *sessions; + const char *loginpath; + const char *issuefile; + int maxfd; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define INIT_G() do { \ + G.loginpath = "/bin/login"; \ + G.issuefile = "/etc/issue.net"; \ +} while (0) /* @@ -81,7 +88,7 @@ static const char *issuefile = "/etc/issue.net"; static unsigned char * remove_iacs(struct tsession *ts, int *pnum_totty) { - unsigned char *ptr0 = TS_BUF1 + ts->wridx1; + unsigned char *ptr0 = TS_BUF1(ts) + ts->wridx1; unsigned char *ptr = ptr0; unsigned char *totty = ptr; unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); @@ -199,9 +206,17 @@ static size_t iac_safe_write(int fd, const char *buf, size_t count) return total + rc; } +/* 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 */ +}; + static struct tsession * make_new_session( - IF_FEATURE_TELNETD_STANDALONE(int sock) + IF_FEATURE_TELNETD_STANDALONE(int master_fd, int sock) IF_NOT_FEATURE_TELNETD_STANDALONE(void) ) { const char *login_argv[2]; @@ -215,8 +230,8 @@ make_new_session( /* Got a new connection, set up a tty. */ fd = xgetpty(tty_name); - if (fd > maxfd) - maxfd = fd; + if (fd > G.maxfd) + G.maxfd = fd; ts->ptyfd = fd; ndelay_on(fd); #if ENABLE_FEATURE_TELNETD_STANDALONE @@ -229,8 +244,8 @@ make_new_session( ndelay_on(sock); } ts->sockfd_write = sock; - if (sock > maxfd) - maxfd = sock; + if (sock > G.maxfd) + G.maxfd = sock; #else /* SO_KEEPALIVE by popular demand */ setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1)); @@ -254,7 +269,7 @@ make_new_session( }; /* This confuses iac_safe_write(), it will try to duplicate * each IAC... */ - //memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send)); + //memcpy(TS_BUF2(ts), iacs_to_send, sizeof(iacs_to_send)); //ts->rdidx2 = sizeof(iacs_to_send); //ts->size2 = sizeof(iacs_to_send); /* So just stuff it into TCP stream! (no error check...) */ @@ -288,13 +303,35 @@ make_new_session( /* Restore default signal handling ASAP */ bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL); +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (!(option_mask32 & OPT_INETD)) { + struct tsession *tp = G.sessions; + while (tp) { + close(tp->ptyfd); + close(tp->sockfd_read); + /* sockfd_write == sockfd_read for standalone telnetd */ + /*close(tp->sockfd_write);*/ + tp = tp->next; + } + } +#endif + /* Make new session and process group */ setsid(); + close(fd); +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (master_fd >= 0) + close(master_fd); + close(sock); +#endif /* Open the child's side of the tty. */ /* NB: setsid() disconnects from any previous ctty's. Therefore * we must open child's side of the tty AFTER setsid! */ - close(0); +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (sock != 0) /* if we did not close 0 already */ +#endif + close(0); xopen(tty_name, O_RDWR); /* becomes our ctty */ xdup2(0, 1); xdup2(0, 2); @@ -316,40 +353,32 @@ make_new_session( * issue files, and they may block writing to fd 1, * (parent is supposed to read it, but parent waits * for vforked child to exec!) */ - print_login_issue(issuefile, tty_name); + print_login_issue(G.issuefile, tty_name); /* Exec shell / login / whatever */ - login_argv[0] = loginpath; + login_argv[0] = G.loginpath; login_argv[1] = NULL; /* exec busybox applet (if PREFER_APPLETS=y), if that fails, * exec external program */ - BB_EXECVP(loginpath, (char **)login_argv); + BB_EXECVP(G.loginpath, (char **)login_argv); /* _exit is safer with vfork, and we shouldn't send message * to remote clients anyway */ - _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/ + _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", G.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 free_session(struct tsession *ts) { - struct tsession *t = sessions; + struct tsession *t = G.sessions; if (option_mask32 & OPT_INETD) exit(EXIT_SUCCESS); /* Unlink this telnet session from the session list */ if (t == ts) - sessions = ts->next; + G.sessions = ts->next; else { while (t->next != ts) t = t->next; @@ -371,17 +400,17 @@ free_session(struct tsession *ts) free(ts); /* Scan all sessions and find new maxfd */ - maxfd = 0; - ts = sessions; + G.maxfd = 0; + ts = G.sessions; while (ts) { - if (maxfd < ts->ptyfd) - maxfd = ts->ptyfd; - if (maxfd < ts->sockfd_read) - maxfd = ts->sockfd_read; + if (G.maxfd < ts->ptyfd) + G.maxfd = ts->ptyfd; + if (G.maxfd < ts->sockfd_read) + G.maxfd = ts->sockfd_read; #if 0 /* Again, sockfd_write == sockfd_read here */ - if (maxfd < ts->sockfd_write) - maxfd = ts->sockfd_write; + if (G.maxfd < ts->sockfd_write) + G.maxfd = ts->sockfd_write; #endif ts = ts->next; } @@ -404,7 +433,7 @@ static void handle_sigchld(int sig UNUSED_PARAM) pid = wait_any_nohang(NULL); if (pid <= 0) break; - ts = sessions; + ts = G.sessions; while (ts) { if (ts->shell_pid == pid) { ts->shell_pid = -1; @@ -435,10 +464,12 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) portnbr = 23, }; #endif + INIT_G(); + /* 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:Ki" IF_FEATURE_TELNETD_STANDALONE("p:b:F"), - &issuefile, &loginpath + &G.issuefile, &G.loginpath IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)); if (!IS_INETD /*&& !re_execed*/) { /* inform that we start in standalone mode? @@ -460,21 +491,21 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) portnbr = xatou16(opt_portnbr); ); - /* Used to check access(loginpath, X_OK) here. Pointless. + /* Used to check access(G.loginpath, X_OK) here. Pointless. * exec will do this for us for free later. */ #if ENABLE_FEATURE_TELNETD_STANDALONE if (IS_INETD) { - sessions = make_new_session(0); - if (!sessions) /* pty opening or vfork problem, exit */ + G.sessions = make_new_session(-1, 0); + if (!G.sessions) /* pty opening or vfork problem, exit */ return 1; /* make_new_session prints error message */ } else { master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr); xlisten(master_fd, 1); } #else - sessions = make_new_session(); - if (!sessions) /* pty opening or vfork problem, exit */ + G.sessions = make_new_session(); + if (!G.sessions) /* pty opening or vfork problem, exit */ return 1; /* make_new_session prints error message */ #endif @@ -512,7 +543,7 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) * ptys if there is room in their session buffers. * NB: scalability problem: we recalculate entire bitmap * before each select. Can be a problem with 500+ connections. */ - ts = sessions; + ts = G.sessions; while (ts) { struct tsession *next = ts->next; /* in case we free ts. */ if (ts->shell_pid == -1) { @@ -535,11 +566,11 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) /* This is needed because free_session() does not * take master_fd into account when it finds new * maxfd among remaining fd's */ - if (master_fd > maxfd) - maxfd = master_fd; + if (master_fd > G.maxfd) + G.maxfd = master_fd; } - count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL); + count = select(G.maxfd + 1, &rdfdset, &wrfdset, NULL, NULL); if (count < 0) goto again; /* EINTR or ENOMEM */ @@ -553,10 +584,10 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) if (fd < 0) goto again; /* Create a new session and link it into our active list */ - new_ts = make_new_session(fd); + new_ts = make_new_session(master_fd, fd); if (new_ts) { - new_ts->next = sessions; - sessions = new_ts; + new_ts->next = G.sessions; + G.sessions = new_ts; } else { close(fd); } @@ -564,7 +595,7 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) #endif /* Then check for data tunneling. */ - ts = sessions; + ts = G.sessions; while (ts) { /* For all sessions... */ struct tsession *next = ts->next; /* in case we free ts. */ @@ -588,7 +619,7 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) { /* Write to socket from buffer 2. */ count = MIN(BUFSIZE - ts->wridx2, ts->size2); - count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2 + ts->wridx2), count); + count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count); if (count < 0) { if (errno == EAGAIN) goto skip2; @@ -618,14 +649,14 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) { /* Read from socket to buffer 1. */ count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1); - count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count); + count = safe_read(ts->sockfd_read, TS_BUF1(ts) + ts->rdidx1, count); if (count <= 0) { if (count < 0 && errno == EAGAIN) goto skip3; goto kill_session; } /* Ignore trailing NUL if it is there */ - if (!TS_BUF1[ts->rdidx1 + count - 1]) { + if (!TS_BUF1(ts)[ts->rdidx1 + count - 1]) { --count; } ts->size1 += count; @@ -637,7 +668,7 @@ int telnetd_main(int argc UNUSED_PARAM, char **argv) if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) { /* Read from pty to buffer 2. */ count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2); - count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count); + count = safe_read(ts->ptyfd, TS_BUF2(ts) + ts->rdidx2, count); if (count <= 0) { if (count < 0 && errno == EAGAIN) goto skip4; -- cgit v1.2.3