diff options
-rw-r--r-- | include/libbb.h | 2 | ||||
-rw-r--r-- | include/usage.h | 22 | ||||
-rw-r--r-- | libbb/xfuncs.c | 24 | ||||
-rw-r--r-- | networking/Kbuild | 3 | ||||
-rw-r--r-- | networking/inetd.c | 15 | ||||
-rw-r--r-- | networking/isrv.c | 337 | ||||
-rw-r--r-- | networking/isrv_identd.c | 144 |
7 files changed, 523 insertions, 24 deletions
diff --git a/include/libbb.h b/include/libbb.h index 4060498b8..c191dc2a0 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -278,6 +278,8 @@ extern int wait4pid(int pid); extern void xsetgid(gid_t gid); extern void xsetuid(uid_t uid); extern void xdaemon(int nochdir, int noclose); +/* More clever/thorough xdaemon */ +extern void bb_sanitize_stdio(int daemonize); extern void xchdir(const char *path); extern void xsetenv(const char *key, const char *value); extern int xopen(const char *pathname, int flags); diff --git a/include/usage.h b/include/usage.h index 0275df3f0..2b51fad72 100644 --- a/include/usage.h +++ b/include/usage.h @@ -826,22 +826,16 @@ "\\( and \\) or null; if \\( and \\) are not used, they return the number\n" \ "of characters matched or 0." -#if 0 /* bloaty */ #define fakeidentd_trivial_usage \ - "[-b ip] [STRING]" + "[-fiw] [-b ADDR] [STRING]" #define fakeidentd_full_usage \ - "Return a set string to auth requests" \ - "\n\nOptions:\n" \ - " -b Bind to ip address\n" \ - " STRING The ident answer string (default is nobody)" -#else /* inetd-only */ -#define fakeidentd_trivial_usage \ - "[username]" -#define fakeidentd_full_usage \ - "Return a (faked) ident response.\n" \ - "This applet is meant to run from inetd.\n" \ - "Optional argument is the username to return (default is 'nobody')." -#endif + "Provide fake ident (auth) service" \ + "\n\nOptions:" \ + "\n -f Run in foreground" \ + "\n -i Inetd mode" \ + "\n -w Inetd 'wait' mode" \ + "\n -b ADDR Bind to specified address" \ + "\n STRING Ident answer string (default is 'nobody')" #define false_trivial_usage \ "" diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c index 207537929..6a6bdced3 100644 --- a/libbb/xfuncs.c +++ b/libbb/xfuncs.c @@ -509,6 +509,30 @@ void xdaemon(int nochdir, int noclose) } #endif +void bb_sanitize_stdio(int daemonize) +{ + int fd; + /* Mega-paranoid */ + fd = xopen(bb_dev_null, O_RDWR); + while (fd < 2) + fd = dup(fd); /* have 0,1,2 open at least to /dev/null */ + if (daemonize) { + pid_t pid = fork(); + if (pid < 0) /* wtf? */ + bb_perror_msg_and_die("fork"); + if (pid) /* parent */ + exit(0); + /* child */ + setsid(); + /* if daemonizing, make sure we detach from stdio */ + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + } + while (fd > 2) + close(fd--); /* close everything after fd#2 */ +} + // Die with an error message if we can't open a new socket. int xsocket(int domain, int type, int protocol) { diff --git a/networking/Kbuild b/networking/Kbuild index 4c29e45a8..bb024c9b7 100644 --- a/networking/Kbuild +++ b/networking/Kbuild @@ -9,7 +9,8 @@ lib-$(CONFIG_ARP) += arp.o interface.o lib-$(CONFIG_ARPING) += arping.o lib-$(CONFIG_DNSD) += dnsd.o lib-$(CONFIG_ETHER_WAKE) += ether-wake.o -lib-$(CONFIG_FAKEIDENTD) += fakeidentd.o +#lib-$(CONFIG_FAKEIDENTD) += fakeidentd.o +lib-$(CONFIG_FAKEIDENTD) += isrv_identd.o isrv.o lib-$(CONFIG_FTPGET) += ftpgetput.o lib-$(CONFIG_FTPPUT) += ftpgetput.o lib-$(CONFIG_HOSTNAME) += hostname.o diff --git a/networking/inetd.c b/networking/inetd.c index 93c16bf60..f9f3b51b6 100644 --- a/networking/inetd.c +++ b/networking/inetd.c @@ -1289,31 +1289,28 @@ inetd_main(int argc, char *argv[]) if (CONFIG == NULL) bb_error_msg_and_die("non-root must specify a config file"); - if (!(opt & 2)) { #ifdef BB_NOMMU + if (!(opt & 2)) { /* reexec for vfork() do continue parent */ vfork_daemon_rexec(0, 0, argc, argv, "-f"); + } + bb_sanitize_stdio(0); #else - xdaemon(0, 0); + bb_sanitize_stdio(!(opt & 2)); #endif - } else { - setsid(); - } logmode = LOGMODE_SYSLOG; if (uid == 0) { - gid_t gid = getgid(); - /* If run by hand, ensure groups vector gets trashed */ + gid_t gid = getgid(); setgroups(1, &gid); } { FILE *fp = fopen(_PATH_INETDPID, "w"); - if (fp != NULL) { fprintf(fp, "%u\n", getpid()); - (void) fclose(fp); + fclose(fp); } } diff --git a/networking/isrv.c b/networking/isrv.c new file mode 100644 index 000000000..02ca1d787 --- /dev/null +++ b/networking/isrv.c @@ -0,0 +1,337 @@ +/* vi: set sw=4 ts=4: */ +/* + * Generic non-forking server infrastructure. + * Intended to make writing telnetd-type servers easier. + * + * Copyright (C) 2007 Denis Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "isrv.h" + +#define DEBUG 0 + +#if DEBUG +#define DPRINTF(args...) bb_error_msg(args) +#else +#define DPRINTF(args...) ((void)0) +#endif + +/* Helpers */ + +#if 0 /*def _POSIX_MONOTONIC_CLOCK*/ +static time_t monotonic_time(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + time(&ts.tv_sec); + return ts.tv_sec; +} +#else +#define monotonic_time() (time(NULL)) +#endif + +/* Opaque structure */ + +struct isrv_state_t { + short *fd2peer; /* one per registered fd */ + void **param_tbl; /* one per registered peer */ + /* one per registered peer; doesn't exist if !timeout */ + time_t *timeo_tbl; + int (*new_peer)(isrv_state_t *state, int fd); + time_t curtime; + int timeout; + int fd_count; + int peer_count; + int wr_count; + fd_set rd; + fd_set wr; +}; +#define FD2PEER (state->fd2peer) +#define PARAM_TBL (state->param_tbl) +#define TIMEO_TBL (state->timeo_tbl) +#define CURTIME (state->curtime) +#define TIMEOUT (state->timeout) +#define FD_COUNT (state->fd_count) +#define PEER_COUNT (state->peer_count) +#define WR_COUNT (state->wr_count) + +/* callback */ +void isrv_want_rd(isrv_state_t *state, int fd) +{ + FD_SET(fd, &state->rd); +} + +/* callback */ +void isrv_want_wr(isrv_state_t *state, int fd) +{ + if (!FD_ISSET(fd, &state->wr)) { + WR_COUNT++; + FD_SET(fd, &state->wr); + } +} + +/* callback */ +void isrv_dont_want_rd(isrv_state_t *state, int fd) +{ + FD_CLR(fd, &state->rd); +} + +/* callback */ +void isrv_dont_want_wr(isrv_state_t *state, int fd) +{ + if (FD_ISSET(fd, &state->wr)) { + WR_COUNT--; + FD_CLR(fd, &state->wr); + } +} + +/* callback */ +int isrv_register_fd(isrv_state_t *state, int peer, int fd) +{ + int n; + + DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd); + + if (FD_COUNT >= FD_SETSIZE) return -1; + if (FD_COUNT <= fd) { + n = FD_COUNT; + FD_COUNT = fd + 1; + + DPRINTF("register_fd: FD_COUNT %d", FD_COUNT); + + FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0])); + while (n < fd) FD2PEER[n++] = -1; + } + + DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer); + + FD2PEER[fd] = peer; + return 0; +} + +/* callback */ +void isrv_close_fd(isrv_state_t *state, int fd) +{ + DPRINTF("close_fd(%d)", fd); + + close(fd); + isrv_dont_want_rd(state, fd); + if (WR_COUNT) isrv_dont_want_wr(state, fd); + + FD2PEER[fd] = -1; + if (fd == FD_COUNT-1) { + do fd--; while (fd >= 0 && FD2PEER[fd] == -1); + FD_COUNT = fd + 1; + + DPRINTF("close_fd: FD_COUNT %d", FD_COUNT); + + FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0])); + } +} + +/* callback */ +int isrv_register_peer(isrv_state_t *state, void *param) +{ + int n; + + if (PEER_COUNT >= FD_SETSIZE) return -1; + n = PEER_COUNT++; + + DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT); + + PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0])); + PARAM_TBL[n] = param; + if (TIMEOUT) { + TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0])); + TIMEO_TBL[n] = CURTIME; + } + return n; +} + +static void remove_peer(isrv_state_t *state, int peer) +{ + int movesize; + int fd; + + DPRINTF("remove_peer(%d)", peer); + + fd = FD_COUNT - 1; + while (fd >= 0) { + if (FD2PEER[fd] == peer) { + isrv_close_fd(state, fd); + fd--; + continue; + } + if (FD2PEER[fd] > peer) + FD2PEER[fd]--; + fd--; + } + + PEER_COUNT--; + DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT); + + movesize = (PEER_COUNT - peer) * sizeof(void*); + if (movesize > 0) { + memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize); + if (TIMEOUT) + memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize); + } + PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0])); + if (TIMEOUT) + TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0])); +} + +static void handle_accept(isrv_state_t *state, int fd) +{ + int n, newfd; + + fcntl(fd, F_SETFL, (int)(PARAM_TBL[0]) | O_NONBLOCK); + newfd = accept(fd, NULL, 0); + fcntl(fd, F_SETFL, (int)(PARAM_TBL[0])); + if (newfd < 0) { + if (errno == EAGAIN) return; + /* Most probably someone gave us wrong fd type + * (for example, non-socket) */ + bb_perror_msg_and_die("accept"); + } + + DPRINTF("new_peer(%d)", newfd); + n = state->new_peer(state, newfd); + if (n) + remove_peer(state, n); /* unsuccesful peer start */ +} + +void BUG_sizeof_fd_set_is_strange(void); +static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **)) +{ + enum { LONG_CNT = sizeof(fd_set) / sizeof(long) }; + int fds_pos; + int fd, peer; + int fd_cnt = FD_COUNT; + + if (LONG_CNT * sizeof(long) != sizeof(fd_set)) + BUG_sizeof_fd_set_is_strange(); + + fds_pos = 0; + while (1) { + /* Find next nonzero bit */ + while (fds_pos < LONG_CNT) { + if (((long*)fds)[fds_pos] == 0) { + fds_pos++; + continue; + } + /* Found non-zero word */ + fd = fds_pos * sizeof(long)*8; /* word# -> bit# */ + while (1) { + if (FD_ISSET(fd, fds)) { + FD_CLR(fd, fds); + goto found_fd; + } + fd++; + } + } + break; /* all words are zero */ + found_fd: + if (fd >= fd_cnt) /* paranoia */ + break; + DPRINTF("handle_fd_set: fd %d is active", fd); + peer = FD2PEER[fd]; + if (peer == 0) { + handle_accept(state, fd); + continue; + } + DPRINTF("h(fd:%d)", fd); + if (h(fd, &PARAM_TBL[peer])) { + /* this peer is gone */ + remove_peer(state, peer); + } else if (TIMEOUT) { + TIMEO_TBL[peer] = monotonic_time(); + } + } +} + +static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **)) +{ + int n, peer; + peer = PEER_COUNT-1; + /* peer 0 is not checked */ + while (peer > 0) { + DPRINTF("peer %d: time diff %d", peer, (int)(CURTIME - TIMEO_TBL[peer])); + + if ((CURTIME - TIMEO_TBL[peer]) > TIMEOUT) { + DPRINTF("peer %d: do_timeout()", peer); + n = do_timeout(&PARAM_TBL[peer]); + if (n) + remove_peer(state, peer); + } + peer--; + } +} + +/* Driver */ +void isrv_run( + int listen_fd, + int (*new_peer)(isrv_state_t *state, int fd), + int (*do_rd)(int fd, void **), + int (*do_wr)(int fd, void **), + int (*do_timeout)(void **), + int timeout, + int exit_if_no_clients) +{ + isrv_state_t *state = xzalloc(sizeof(*state)); + state->new_peer = new_peer; + state->timeout = timeout; + + /* register "peer" #0 - it will accept new connections */ + isrv_register_peer(state, NULL); + isrv_register_fd(state, /*peer:*/ 0, listen_fd); + isrv_want_rd(state, listen_fd); + /* remember flags to make blocking<->nonblocking switch faster */ + PARAM_TBL[0] = (void*) (fcntl(listen_fd, F_GETFL, 0)); + + while (1) { + struct timeval tv; + fd_set rd; + fd_set wr; + fd_set *wrp = NULL; + int n; + + tv.tv_sec = timeout; + tv.tv_usec = 0; + rd = state->rd; + if (WR_COUNT) { + wr = state->wr; + wrp = ≀ + } + + DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...", FD_COUNT, timeout); + n = select(FD_COUNT, &rd, wrp, NULL, timeout ? &tv : NULL); + DPRINTF("run: ...select:%d", n); + + if (n < 0) { + if (errno != EINTR) + bb_perror_msg("select"); + continue; + } + + if (exit_if_no_clients && n == 0 && PEER_COUNT <= 1) + break; + + if (timeout) { + time_t t = monotonic_time(); + if (t != CURTIME) { + CURTIME = t; + handle_timeout(state, do_timeout); + } + } + if (n > 0) { + handle_fd_set(state, &rd, do_rd); + if (wrp) + handle_fd_set(state, wrp, do_wr); + } + } + DPRINTF("run: bailout"); +} diff --git a/networking/isrv_identd.c b/networking/isrv_identd.c new file mode 100644 index 000000000..b9481f8d3 --- /dev/null +++ b/networking/isrv_identd.c @@ -0,0 +1,144 @@ +/* vi: set sw=4 ts=4: */ +/* + * Fake identd server. + * + * Copyright (C) 2007 Denis Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include <syslog.h> +#include "busybox.h" +#include "isrv.h" + +enum { TIMEOUT = 20 }; + +/* Why use alarm(TIMEOUT-1)? + * isrv's internal select() will run with timeout=TIMEOUT. + * If nothing happens during TIMEOUT-1 seconds (no accept/read), + * then ALL sessions timed out by now. Instead of closing them one-by-one + * (isrv calls do_timeout for each 'stale' session), + * SIGALRM triggered by alarm(TIMEOUT-1) will kill us, terminating them all. + */ + +typedef struct identd_buf_t { + int pos; + int fd_flag; + char buf[64 - 2*sizeof(int)]; +} identd_buf_t; + +static const char *bogouser = "nobody"; + +static int new_peer(isrv_state_t *state, int fd) +{ + int peer; + identd_buf_t *buf = xzalloc(sizeof(*buf)); + + alarm(TIMEOUT - 1); + + peer = isrv_register_peer(state, buf); + if (peer < 0) + return 0; /* failure */ + if (isrv_register_fd(state, peer, fd) < 0) + return peer; /* failure, unregister peer */ + + buf->fd_flag = fcntl(fd, F_GETFL, 0) | O_NONBLOCK; + isrv_want_rd(state, fd); + return 0; +} + +static int do_rd(int fd, void **paramp) +{ + identd_buf_t *buf = *paramp; + char *cur, *p; + int sz; + + alarm(TIMEOUT - 1); + + cur = buf->buf + buf->pos; + + fcntl(fd, F_SETFL, buf->fd_flag | O_NONBLOCK); + sz = safe_read(fd, cur, sizeof(buf->buf) - buf->pos); + + if (sz < 0) { + if (errno != EAGAIN) + goto term; /* terminate this session if !EAGAIN */ + goto ok; + } + + buf->pos += sz; + buf->buf[buf->pos] = '\0'; + p = strpbrk(cur, "\r\n"); + if (p) + *p = '\0'; + if (p || !sz || buf->pos == sizeof(buf->buf)) { + /* fd is still in nonblocking mode - we never block here */ + fdprintf(fd, "%s : USERID : UNIX : %s\r\n", buf->buf, bogouser); + goto term; + } + ok: + fcntl(fd, F_SETFL, buf->fd_flag & ~O_NONBLOCK); + return 0; + term: + fcntl(fd, F_SETFL, buf->fd_flag & ~O_NONBLOCK); + free(buf); + return 1; +} + +static int do_timeout(void **paramp) +{ + return 1; /* terminate session */ +} + +static void inetd_mode(void) +{ + identd_buf_t *buf = xzalloc(sizeof(*buf)); + /* We do NOT want nonblocking I/O here! */ + buf->fd_flag = fcntl(0, F_GETFL, 0); + while (do_rd(0, (void*)&buf) == 0) /* repeat */; +} + +int fakeidentd_main(int argc, char **argv) +{ + enum { + OPT_foreground = 0x1, + OPT_inetd = 0x2, + OPT_inetdwait = 0x4, + OPT_nodeamon = 0x7, + OPT_bindaddr = 0x8, + }; + + const char *bind_address = NULL; + unsigned opt; + int fd; + + opt = getopt32(argc, argv, "fiwb:", &bind_address); + if (optind < argc) + bogouser = argv[optind]; + + /* Daemonize if no -f or -i or -w */ + bb_sanitize_stdio(!(opt & OPT_nodeamon)); + if (!(opt & OPT_nodeamon)) { + openlog(applet_name, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + if (opt & OPT_inetd) { + inetd_mode(); + return 0; + } + + /* Ignore closed connections when writing */ + signal(SIGPIPE, SIG_IGN); + + if (opt & OPT_inetdwait) { + fd = 0; + } else { + fd = create_and_bind_stream_or_die(bind_address, + bb_lookup_port("identd", "tcp", 113)); + xlisten(fd, 5); + } + + isrv_run(fd, new_peer, do_rd, NULL, do_timeout, TIMEOUT, 1); + return 0; +} |