X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Finetd.c;h=67984accb56f297babb293b43af66cf8cd5cdc0c;hb=9a4100cf53f75356854ce752374babf8135c3f42;hp=8a4c9fbf6e0493ef5c07abc00f6f6541a8c47273;hpb=4e6d5117b839cef41cd3919966f95bf25d24d8d9;p=oweals%2Fbusybox.git diff --git a/networking/inetd.c b/networking/inetd.c index 8a4c9fbf6..67984accb 100644 --- a/networking/inetd.c +++ b/networking/inetd.c @@ -3,6 +3,7 @@ /* $OpenBSD: inetd.c,v 1.79 2001/01/30 08:30:57 deraadt Exp $ */ /* $NetBSD: inetd.c,v 1.11 1996/02/22 11:14:41 mycroft Exp $ */ /* Busybox port by Vladimir Oleynik (C) 2001-2005 */ +/* IPv6 support, many bug fixes by Denys Vlasenko (c) 2008 */ /* * Copyright (c) 1983,1991 The Regents of the University of California. * All rights reserved. @@ -38,21 +39,17 @@ /* Inetd - Internet super-server * - * This program invokes all internet services as needed. - * connection-oriented services are invoked each time a + * This program invokes configured services when a connection + * from a peer is established or a datagram arrives. + * Connection-oriented services are invoked each time a * connection is made, by creating a process. This process * is passed the connection as file descriptor 0 and is - * expected to do a getpeername to find out the source host + * expected to do a getpeername to find out peer's host * and port. - * * Datagram oriented services are invoked when a datagram * arrives; a process is created and passed a pending message - * on file descriptor 0. Datagram servers may either connect - * to their peer, freeing up the original socket for inetd - * to receive further messages on, or "take over the socket", - * processing all arriving datagrams and, eventually, timing - * out. The first type of server is said to be "multi-threaded"; - * the second type of server "single-threaded". + * on file descriptor 0. peer's address can be obtained + * using recvfrom. * * Inetd uses a configuration file which is read at startup * and, possibly, at some later time in response to a hangup signal. @@ -60,28 +57,28 @@ * order shown below. Continuation lines for an entry must begin with * a space or tab. All fields must be present in each entry. * - * service name must be in /etc/services - * socket type stream/dgram/raw/rdm/seqpacket + * service_name must be in /etc/services + * socket_type stream/dgram/raw/rdm/seqpacket * protocol must be in /etc/protocols * (usually "tcp" or "udp") * wait/nowait[.max] single-threaded/multi-threaded, max # * user[.group] or user[:group] user/group to run daemon as - * server program full path name - * server program arguments maximum of MAXARGS (20) + * server_program full path name + * server_program_arguments maximum of MAXARGS (20) * * For RPC services - * service name/version must be in /etc/rpc - * socket type stream/dgram/raw/rdm/seqpacket + * service_name/version must be in /etc/rpc + * socket_type stream/dgram/raw/rdm/seqpacket * rpc/protocol "rpc/tcp" etc * wait/nowait[.max] single-threaded/multi-threaded * user[.group] or user[:group] user to run daemon as - * server program full path name - * server program arguments maximum of MAXARGS (20) + * server_program full path name + * server_program_arguments maximum of MAXARGS (20) * * For non-RPC services, the "service name" can be of the form * hostaddress:servicename, in which case the hostaddress is used * as the host portion of the address to listen on. If hostaddress - * consists of a single `*' character, INADDR_ANY is used. + * consists of a single '*' character, INADDR_ANY is used. * * A line can also consist of just * hostaddress: @@ -102,7 +99,7 @@ * one line for any given RPC service, even if the host-address * specifiers are different. * - * Comment lines are indicated by a `#' in column 1. + * Comment lines are indicated by a '#' in column 1. */ /* inetd rules for passing file descriptors to children @@ -135,43 +132,137 @@ * connection requests until a timeout. */ +/* Despite of above doc saying that dgram services must use "wait", + * "udp nowait" servers are implemented in busyboxed inetd. + * IPv6 addresses are also implemented. However, they may look ugly - + * ":::service..." means "address '::' (IPv6 wildcard addr)":"service"... + * You have to put "tcp6"/"udp6" in protocol field to select IPv6. + */ + /* Here's the scoop concerning the user[:group] feature: * 1) group is not specified: * a) user = root: NO setuid() or setgid() is done - * b) other: setgid(primary group as found in passwd) - * initgroups(name, primary group) + * b) other: initgroups(name, primary group) + * setgid(primary group as found in passwd) * setuid() * 2) group is specified: * a) user = root: setgid(specified group) * NO initgroups() * NO setuid() - * b) other: setgid(specified group) - * initgroups(name, specified group) + * b) other: initgroups(name, specified group) + * setgid(specified group) * setuid() */ +//config:config INETD +//config: bool "inetd (18 kb)" +//config: default y +//config: select FEATURE_SYSLOG +//config: help +//config: Internet superserver daemon +//config: +//config:config FEATURE_INETD_SUPPORT_BUILTIN_ECHO +//config: bool "Support echo service on port 7" +//config: default y +//config: depends on INETD +//config: help +//config: Internal service which echoes data back. +//config: Activated by configuration lines like these: +//config: echo stream tcp nowait root internal +//config: echo dgram udp wait root internal +//config: +//config:config FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +//config: bool "Support discard service on port 8" +//config: default y +//config: depends on INETD +//config: help +//config: Internal service which discards all input. +//config: Activated by configuration lines like these: +//config: discard stream tcp nowait root internal +//config: discard dgram udp wait root internal +//config: +//config:config FEATURE_INETD_SUPPORT_BUILTIN_TIME +//config: bool "Support time service on port 37" +//config: default y +//config: depends on INETD +//config: help +//config: Internal service which returns big-endian 32-bit number +//config: of seconds passed since 1900-01-01. The number wraps around +//config: on overflow. +//config: Activated by configuration lines like these: +//config: time stream tcp nowait root internal +//config: time dgram udp wait root internal +//config: +//config:config FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +//config: bool "Support daytime service on port 13" +//config: default y +//config: depends on INETD +//config: help +//config: Internal service which returns human-readable time. +//config: Activated by configuration lines like these: +//config: daytime stream tcp nowait root internal +//config: daytime dgram udp wait root internal +//config: +//config:config FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +//config: bool "Support chargen service on port 19" +//config: default y +//config: depends on INETD +//config: help +//config: Internal service which generates endless stream +//config: of all ASCII chars beetween space and char 126. +//config: Activated by configuration lines like these: +//config: chargen stream tcp nowait root internal +//config: chargen dgram udp wait root internal +//config: +//config:config FEATURE_INETD_RPC +//config: bool "Support RPC services" +//config: default n # very rarely used, and needs Sun RPC support in libc +//config: depends on INETD +//config: help +//config: Support Sun-RPC based services + +//applet:IF_INETD(APPLET(inetd, BB_DIR_USR_SBIN, BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_INETD) += inetd.o + +//usage:#define inetd_trivial_usage +//usage: "[-fe] [-q N] [-R N] [CONFFILE]" +//usage:#define inetd_full_usage "\n\n" +//usage: "Listen for network connections and launch programs\n" +//usage: "\n -f Run in foreground" +//usage: "\n -e Log to stderr" +//usage: "\n -q N Socket listen queue (default 128)" +//usage: "\n -R N Pause services after N connects/min" +//usage: "\n (default 0 - disabled)" +//usage: "\n Default CONFFILE is /etc/inetd.conf" #include +#include /* setrlimit */ +#include /* un.h may need this */ #include + #include "libbb.h" +#include "common_bufsiz.h" + +#if ENABLE_FEATURE_INETD_RPC +# if defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_RPC__) +# warning "You probably need to build uClibc with UCLIBC_HAS_RPC for NFS support" + /* not #error, since user may be using e.g. libtirpc instead */ +# endif +# include +# include +#endif #if !BB_MMU -/* stream versions of these builtins are forking, +/* stream version of chargen is forking but not execing, * can't do that (easily) on NOMMU */ -#undef ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD -#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD 0 #undef ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN #define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN 0 #endif -#if ENABLE_FEATURE_INETD_RPC -#include -#include -#endif +#define CNT_INTERVAL 60 /* servers in CNT_INTERVAL sec. */ +#define RETRYTIME 60 /* retry after bind or server fail */ -#define _PATH_INETDPID "/var/run/inetd.pid" - -#define CNT_INTERVAL 60 /* servers in CNT_INTERVAL sec. */ -#define RETRYTIME (60*10) /* retry after bind or server fail */ +// TODO: explain, or get rid of setrlimit games #ifndef RLIMIT_NOFILE #define RLIMIT_NOFILE RLIMIT_OFILE @@ -209,20 +300,21 @@ typedef struct servtab_t { #else #define is_rpc_service(sep) 0 #endif - pid_t se_wait; /* 0:"nowait", 1:"wait" */ + pid_t se_wait; /* 0:"nowait", 1:"wait", >1:"wait" */ + /* and waiting for this pid */ socktype_t se_socktype; /* SOCK_STREAM/DGRAM/RDM/... */ family_t se_family; /* AF_UNIX/INET[6] */ - smallint se_proto_no; /* almost "getprotobyname(se_proto)" */ + /* se_proto_no is used by RPC code only... hmm */ + smallint se_proto_no; /* IPPROTO_TCP/UDP, n/a for AF_UNIX */ smallint se_checked; /* looked at during merge */ + unsigned se_max; /* allowed instances per minute */ + unsigned se_count; /* number started since se_time */ + unsigned se_time; /* when we started counting */ char *se_user; /* user name to run as */ char *se_group; /* group name to run as, can be NULL */ #ifdef INETD_BUILTINS_ENABLED const struct builtin *se_builtin; /* if built-in, description */ #endif -// TODO: wrong algorithm!!! - unsigned se_max; /* max # of instances of this service */ - unsigned se_count; /* number started since se_time */ - unsigned se_time; /* start of se_count */ struct servtab_t *se_next; len_and_sockaddr *se_lsa; char *se_program; /* server program */ @@ -231,60 +323,54 @@ typedef struct servtab_t { } servtab_t; #ifdef INETD_BUILTINS_ENABLED - /* Echo received data */ +/* Echo received data */ #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO -static void echo_stream(int, servtab_t *); -static void echo_dg(int, servtab_t *); +static void FAST_FUNC echo_stream(int, servtab_t *); +static void FAST_FUNC echo_dg(int, servtab_t *); #endif - /* Internet /dev/null */ +/* Internet /dev/null */ #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD -static void discard_stream(int, servtab_t *); -static void discard_dg(int, servtab_t *); +static void FAST_FUNC discard_stream(int, servtab_t *); +static void FAST_FUNC discard_dg(int, servtab_t *); #endif - /* Return 32 bit time since 1900 */ +/* Return 32 bit time since 1900 */ #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME -static void machtime_stream(int, servtab_t *); -static void machtime_dg(int, servtab_t *); +static void FAST_FUNC machtime_stream(int, servtab_t *); +static void FAST_FUNC machtime_dg(int, servtab_t *); #endif - /* Return human-readable time */ +/* Return human-readable time */ #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME -static void daytime_stream(int, servtab_t *); -static void daytime_dg(int, servtab_t *); +static void FAST_FUNC daytime_stream(int, servtab_t *); +static void FAST_FUNC daytime_dg(int, servtab_t *); #endif - /* Familiar character generator */ +/* Familiar character generator */ #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN -static void chargen_stream(int, servtab_t *); -static void chargen_dg(int, servtab_t *); +static void FAST_FUNC chargen_stream(int, servtab_t *); +static void FAST_FUNC chargen_dg(int, servtab_t *); #endif struct builtin { - const char *bi_service; /* internally provided service name */ - uint8_t bi_fork; /* 1 if stream fn should run in child */ - /* All builtins are "nowait" */ - /* uint8_t bi_wait; */ /* 1 if should wait for child */ - void (*bi_stream_fn)(int, servtab_t *); - void (*bi_dgram_fn)(int, servtab_t *); + /* NB: not necessarily NUL terminated */ + char bi_service7[7]; /* internally provided service name */ + uint8_t bi_fork; /* 1 if stream fn should run in child */ + void (*bi_stream_fn)(int, servtab_t *) FAST_FUNC; + void (*bi_dgram_fn)(int, servtab_t *) FAST_FUNC; }; static const struct builtin builtins[] = { #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO - /* Echo received data */ { "echo", 1, echo_stream, echo_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD - /* Internet /dev/null */ { "discard", 1, discard_stream, discard_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN - /* Familiar character generator */ { "chargen", 1, chargen_stream, chargen_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME - /* Return 32 bit time since 1900 */ { "time", 0, machtime_stream, machtime_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME - /* Return human-readable time */ { "daytime", 0, daytime_stream, daytime_dg }, #endif }; @@ -295,59 +381,74 @@ struct globals { struct rlimit rlim_ofile; servtab_t *serv_list; int global_queuelen; + int maxsock; /* max fd# in allsock, -1: unknown */ + /* whenever maxsock grows, prev_maxsock is set to new maxsock, + * but if maxsock is set to -1, prev_maxsock is not changed */ int prev_maxsock; - int maxsock; unsigned max_concurrency; smallint alarm_armed; uid_t real_uid; /* user ID who ran us */ - unsigned config_lineno; const char *config_filename; - FILE *fconfig; + parser_t *parser; char *default_local_hostname; #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN - char *endring; - char *ringpos; + char *end_ring; + char *ring_pos; char ring[128]; #endif fd_set allsock; /* Used in next_line(), and as scratch read buffer */ char line[256]; /* _at least_ 256, see LINE_SIZE */ -}; -#define G (*(struct globals*)&bb_common_bufsiz1) +} FIX_ALIASING; +#define G (*(struct globals*)bb_common_bufsiz1) enum { LINE_SIZE = COMMON_BUFSIZE - offsetof(struct globals, line) }; -struct BUG_G_too_big { - char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1]; -}; #define rlim_ofile_cur (G.rlim_ofile_cur ) #define rlim_ofile (G.rlim_ofile ) #define serv_list (G.serv_list ) #define global_queuelen (G.global_queuelen) -#define prev_maxsock (G.prev_maxsock ) #define maxsock (G.maxsock ) +#define prev_maxsock (G.prev_maxsock ) #define max_concurrency (G.max_concurrency) #define alarm_armed (G.alarm_armed ) #define real_uid (G.real_uid ) -#define config_lineno (G.config_lineno ) #define config_filename (G.config_filename) -#define fconfig (G.fconfig ) +#define parser (G.parser ) #define default_local_hostname (G.default_local_hostname) #define first_ps_byte (G.first_ps_byte ) #define last_ps_byte (G.last_ps_byte ) -#define endring (G.endring ) -#define ringpos (G.ringpos ) +#define end_ring (G.end_ring ) +#define ring_pos (G.ring_pos ) #define ring (G.ring ) #define allsock (G.allsock ) #define line (G.line ) #define INIT_G() do { \ + setup_common_bufsiz(); \ + BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \ rlim_ofile_cur = OPEN_MAX; \ global_queuelen = 128; \ config_filename = "/etc/inetd.conf"; \ } while (0) +#if 1 +# define dbg(...) ((void)0) +#else +# define dbg(...) \ +do { \ + int dbg_fd = open("inetd_debug.log", O_WRONLY | O_CREAT | O_APPEND, 0666); \ + if (dbg_fd >= 0) { \ + fdprintf(dbg_fd, "%d: ", getpid()); \ + fdprintf(dbg_fd, __VA_ARGS__); \ + close(dbg_fd); \ + } \ +} while (0) +#endif + static void maybe_close(int fd) { - if (fd >= 0) + if (fd >= 0) { close(fd); + dbg("closed fd:%d\n", fd); + } } // TODO: move to libbb? @@ -357,6 +458,8 @@ static len_and_sockaddr *xzalloc_lsa(int family) int sz; sz = sizeof(struct sockaddr_in); + if (family == AF_UNIX) + sz = sizeof(struct sockaddr_un); #if ENABLE_FEATURE_IPV6 if (family == AF_INET6) sz = sizeof(struct sockaddr_in6); @@ -364,10 +467,9 @@ static len_and_sockaddr *xzalloc_lsa(int family) lsa = xzalloc(LSA_LEN_SIZE + sz); lsa->len = sz; lsa->u.sa.sa_family = family; - return lsa; + return lsa; } - static void rearm_alarm(void) { if (!alarm_armed) { @@ -382,12 +484,12 @@ static void block_CHLD_HUP_ALRM(sigset_t *m) sigaddset(m, SIGCHLD); sigaddset(m, SIGHUP); sigaddset(m, SIGALRM); - sigprocmask(SIG_BLOCK, m, NULL); + sigprocmask(SIG_BLOCK, m, m); /* old sigmask is stored in m */ } -static void unblock_sigs(sigset_t *m) +static void restore_sigmask(sigset_t *m) { - sigprocmask(SIG_UNBLOCK, m, NULL); + sigprocmask(SIG_SETMASK, m, NULL); } #if ENABLE_FEATURE_INETD_RPC @@ -450,7 +552,9 @@ static void remove_fd_from_set(int fd) { if (fd >= 0) { FD_CLR(fd, &allsock); + dbg("stopped listening on fd:%d\n", fd); maxsock = -1; + dbg("maxsock:%d\n", maxsock); } } @@ -458,9 +562,11 @@ static void add_fd_to_set(int fd) { if (fd >= 0) { FD_SET(fd, &allsock); + dbg("started listening on fd:%d\n", fd); if (maxsock >= 0 && fd > maxsock) { prev_maxsock = maxsock = fd; - if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN) + dbg("maxsock:%d\n", maxsock); + if ((rlim_t)fd > rlim_ofile_cur - FD_MARGIN) bump_nofile(); } } @@ -469,11 +575,16 @@ static void add_fd_to_set(int fd) static void recalculate_maxsock(void) { int fd = 0; + + /* We may have no services, in this case maxsock should still be >= 0 + * (code elsewhere is not happy with maxsock == -1) */ + maxsock = 0; while (fd <= prev_maxsock) { if (FD_ISSET(fd, &allsock)) maxsock = fd; fd++; } + dbg("recalculated maxsock:%d\n", maxsock); prev_maxsock = maxsock; if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN) bump_nofile(); @@ -496,7 +607,7 @@ static void prepare_socket_fd(servtab_t *sep) /* zero out the port for all RPC services; let bind() * find one. */ - set_nport(sep->se_lsa, 0); + set_nport(&sep->se_lsa->u.sa, 0); /* for RPC services, attempt to use a reserved port * if they are going to be running as root. */ @@ -531,8 +642,13 @@ static void prepare_socket_fd(servtab_t *sep) rearm_alarm(); return; } - if (sep->se_socktype == SOCK_STREAM) + + if (sep->se_socktype == SOCK_STREAM) { listen(fd, global_queuelen); + dbg("new sep->se_fd:%d (stream)\n", fd); + } else { + dbg("new sep->se_fd:%d (!stream)\n", fd); + } add_fd_to_set(fd); sep->se_fd = fd; @@ -542,61 +658,20 @@ static int reopen_config_file(void) { free(default_local_hostname); default_local_hostname = xstrdup("*"); - if (fconfig != NULL) - fclose(fconfig); - config_lineno = 0; - fconfig = fopen_or_warn(config_filename, "r"); - return (fconfig != NULL); + if (parser != NULL) + config_close(parser); + parser = config_open(config_filename); + return (parser != NULL); } static void close_config_file(void) { - if (fconfig) { - fclose(fconfig); - fconfig = NULL; + if (parser) { + config_close(parser); + parser = NULL; } } -static char *next_line(void) -{ - if (fgets(line, LINE_SIZE, fconfig) == NULL) - return NULL; - config_lineno++; - *strchrnul(line, '\n') = '\0'; - return line; -} - -static char *next_word(char **cpp) -{ - char *start; - char *cp = *cpp; - - if (cp == NULL) - return NULL; - again: - while (*cp == ' ' || *cp == '\t') - cp++; - if (*cp == '\0') { - int c = getc(fconfig); - ungetc(c, fconfig); - if (c == ' ' || c == '\t') { - cp = next_line(); - if (cp) - goto again; - } - *cpp = NULL; - return NULL; - } - start = cp; - while (*cp && *cp != ' ' && *cp != '\t') - cp++; - if (*cp != '\0') - *cp++ = '\0'; - - *cpp = cp; - return start; -} - static void free_servtab_strings(servtab_t *cp) { int i; @@ -645,63 +720,58 @@ static servtab_t *dup_servtab(servtab_t *sep) static NOINLINE servtab_t *parse_one_line(void) { int argc; - char *p, *cp, *arg; + char *token[6+MAXARGV]; + char *p, *arg; char *hostdelim; servtab_t *sep; servtab_t *nsep; new: sep = new_servtab(); more: - while ((cp = next_line()) && *cp == '#') - continue; /* skip comment lines */ - if (cp == NULL) { + argc = config_read(parser, token, 6+MAXARGV, 1, "# \t", PARSE_NORMAL); + if (!argc) { free(sep); return NULL; } - arg = next_word(&cp); - if (arg == NULL) { - /* A blank line. */ - goto more; - } - /* [host:]service socktype proto wait user[:group] prog [args] */ /* Check for "host:...." line */ + arg = token[0]; hostdelim = strrchr(arg, ':'); if (hostdelim) { *hostdelim = '\0'; sep->se_local_hostname = xstrdup(arg); arg = hostdelim + 1; - if (*arg == '\0') { - arg = next_word(&cp); - if (arg == NULL) { - /* Line has just "host:", change the - * default host for the following lines. */ - free(default_local_hostname); - default_local_hostname = sep->se_local_hostname; - goto more; - } + if (*arg == '\0' && argc == 1) { + /* Line has just "host:", change the + * default host for the following lines. */ + free(default_local_hostname); + default_local_hostname = sep->se_local_hostname; + /*sep->se_local_hostname = NULL; - redundant */ + /* (we'll overwrite this field anyway) */ + goto more; } } else sep->se_local_hostname = xstrdup(default_local_hostname); /* service socktype proto wait user[:group] prog [args] */ sep->se_service = xstrdup(arg); + /* socktype proto wait user[:group] prog [args] */ - arg = next_word(&cp); - if (arg == NULL) { + if (argc < 6) { parse_err: bb_error_msg("parse error on line %u, line is ignored", - config_lineno); - free_servtab_strings(sep); + parser->lineno); /* Just "goto more" can make sep to carry over e.g. * "rpc"-ness (by having se_rpcver_lo != 0). * We will be more paranoid: */ + free_servtab_strings(sep); free(sep); goto new; } + { - static int8_t SOCK_xxx[] ALIGN1 = { + static const int8_t SOCK_xxx[] ALIGN1 = { -1, SOCK_STREAM, SOCK_DGRAM, SOCK_RDM, SOCK_SEQPACKET, SOCK_RAW @@ -709,13 +779,11 @@ static NOINLINE servtab_t *parse_one_line(void) sep->se_socktype = SOCK_xxx[1 + index_in_strings( "stream""\0" "dgram""\0" "rdm""\0" "seqpacket""\0" "raw""\0" - , arg)]; + , token[1])]; } /* {unix,[rpc/]{tcp,udp}[6]} wait user[:group] prog [args] */ - sep->se_proto = arg = xstrdup(next_word(&cp)); - if (arg == NULL) - goto parse_err; + sep->se_proto = arg = xstrdup(token[2]); if (strcmp(arg, "unix") == 0) { sep->se_family = AF_UNIX; } else { @@ -731,7 +799,7 @@ static NOINLINE servtab_t *parse_one_line(void) goto parse_err; #endif } - if (strncmp(arg, "rpc/", 4) == 0) { + if (is_prefixed_with(arg, "rpc/")) { #if ENABLE_FEATURE_INETD_RPC unsigned n; arg += 4; @@ -751,7 +819,7 @@ static NOINLINE servtab_t *parse_one_line(void) if (*p == '-') { p++; n = bb_strtou(p, &p, 10); - if (n > INT_MAX || n < sep->se_rpcver_lo) + if (n > INT_MAX || (int)n < sep->se_rpcver_lo) goto bad_ver_spec; sep->se_rpcver_hi = n; } @@ -764,9 +832,9 @@ static NOINLINE servtab_t *parse_one_line(void) } /* we don't really need getprotobyname()! */ if (strcmp(arg, "tcp") == 0) - sep->se_proto_no = 6; + sep->se_proto_no = IPPROTO_TCP; /* = 6 */ if (strcmp(arg, "udp") == 0) - sep->se_proto_no = 17; + sep->se_proto_no = IPPROTO_UDP; /* = 17 */ if (six) *six = '6'; if (!sep->se_proto_no) /* not tcp/udp?? */ @@ -774,9 +842,7 @@ static NOINLINE servtab_t *parse_one_line(void) } /* [no]wait[.max] user[:group] prog [args] */ - arg = next_word(&cp); - if (arg == NULL) - goto parse_err; + arg = token[3]; sep->se_max = max_concurrency; p = strchr(arg, '.'); if (p) { @@ -785,12 +851,14 @@ static NOINLINE servtab_t *parse_one_line(void) if (errno) goto parse_err; } - sep->se_wait = (strcmp(arg, "wait") == 0); + sep->se_wait = (arg[0] != 'n' || arg[1] != 'o'); + if (!sep->se_wait) /* "no" seen */ + arg += 2; + if (strcmp(arg, "wait") != 0) + goto parse_err; /* user[:group] prog [args] */ - sep->se_user = xstrdup(next_word(&cp)); - if (sep->se_user == NULL) - goto parse_err; + sep->se_user = xstrdup(token[4]); arg = strchr(sep->se_user, '.'); if (arg == NULL) arg = strchr(sep->se_user, ':'); @@ -800,52 +868,66 @@ static NOINLINE servtab_t *parse_one_line(void) } /* prog [args] */ - sep->se_program = xstrdup(next_word(&cp)); - if (sep->se_program == NULL) - goto parse_err; + sep->se_program = xstrdup(token[5]); #ifdef INETD_BUILTINS_ENABLED - /* sep->se_builtin = NULL; - done by new_servtab() */ if (strcmp(sep->se_program, "internal") == 0 + && strlen(sep->se_service) <= 7 && (sep->se_socktype == SOCK_STREAM || sep->se_socktype == SOCK_DGRAM) ) { - int i; + unsigned i; for (i = 0; i < ARRAY_SIZE(builtins); i++) - if (strcmp(builtins[i].bi_service, sep->se_service) == 0) + if (strncmp(builtins[i].bi_service7, sep->se_service, 7) == 0) goto found_bi; bb_error_msg("unknown internal service %s", sep->se_service); goto parse_err; found_bi: sep->se_builtin = &builtins[i]; - sep->se_wait = 0; /* = builtins[i].bi_wait; - always 0 */ + /* stream builtins must be "nowait", dgram must be "wait" */ + if (sep->se_wait != (sep->se_socktype == SOCK_DGRAM)) + goto parse_err; } #endif argc = 0; - while ((arg = next_word(&cp)) != NULL && argc < MAXARGV) { + while (argc < MAXARGV && (arg = token[6+argc]) != NULL) sep->se_argv[argc++] = xstrdup(arg); + /* Some inetd.conf files have no argv's, not even argv[0]. + * Fix them up. + * (Technically, programs can be execed with argv[0] = NULL, + * but many programs do not like that at all) */ + if (argc == 0) + sep->se_argv[0] = xstrdup(sep->se_program); + + /* catch mixups. " stream udp ..." == wtf */ + if (sep->se_socktype == SOCK_STREAM) { + if (sep->se_proto_no == IPPROTO_UDP) + goto parse_err; + } + if (sep->se_socktype == SOCK_DGRAM) { + if (sep->se_proto_no == IPPROTO_TCP) + goto parse_err; } - /* while (argc <= MAXARGV) */ - /* sep->se_argv[argc++] = NULL; - done by new_servtab() */ - - /* - * Now that we've processed the entire line, check if the hostname - * specifier was a comma separated list of hostnames. If so - * we'll make new entries for each address. - */ + + //bb_error_msg( + // "ENTRY[%s][%s][%s][%d][%d][%d][%d][%d][%s][%s][%s]", + // sep->se_local_hostname, sep->se_service, sep->se_proto, sep->se_wait, sep->se_proto_no, + // sep->se_max, sep->se_count, sep->se_time, sep->se_user, sep->se_group, sep->se_program); + + /* check if the hostname specifier is a comma separated list + * of hostnames. we'll make new entries for each address. */ while ((hostdelim = strrchr(sep->se_local_hostname, ',')) != NULL) { nsep = dup_servtab(sep); - /* - * NUL terminate the hostname field of the existing entry, - * and make a dup for the new entry. - */ + /* NUL terminate the hostname field of the existing entry, + * and make a dup for the new entry. */ *hostdelim++ = '\0'; nsep->se_local_hostname = xstrdup(hostdelim); nsep->se_next = sep->se_next; sep->se_next = nsep; } - /* Was doing it here: */ + /* was doing it here: */ /* DNS resolution, create copies for each IP address */ + /* IPv6-ization destroyed it :( */ return sep; } @@ -864,7 +946,7 @@ static servtab_t *insert_in_servlist(servtab_t *cp) block_CHLD_HUP_ALRM(&omask); sep->se_next = serv_list; serv_list = sep; - unblock_sigs(&omask); + restore_sigmask(&omask); return sep; } @@ -879,16 +961,17 @@ static int same_serv_addr_proto(servtab_t *old, servtab_t *new) return 1; } -static void reread_config_file(int sig ATTRIBUTE_UNUSED) +static void reread_config_file(int sig UNUSED_PARAM) { servtab_t *sep, *cp, **sepp; len_and_sockaddr *lsa; sigset_t omask; unsigned n; uint16_t port; + int save_errno = errno; if (!reopen_config_file()) - return; + goto ret; for (sep = serv_list; sep; sep = sep->se_next) sep->se_checked = 0; @@ -935,7 +1018,7 @@ static void reread_config_file(int sig ATTRIBUTE_UNUSED) for (i = 0; i < MAXARGV; i++) SWAP(char*, sep->se_argv[i], cp->se_argv[i]); #undef SWAP - unblock_sigs(&omask); + restore_sigmask(&omask); free_servtab_strings(cp); } after_check: @@ -947,15 +1030,9 @@ static void reread_config_file(int sig ATTRIBUTE_UNUSED) switch (sep->se_family) { struct sockaddr_un *sun; case AF_UNIX: - /* we have poor infrastructure for AF_UNIX... */ - n = strlen(sep->se_service); - if (n > sizeof(sun->sun_path) - 1) - n = sizeof(sun->sun_path) - 1; - lsa = xzalloc(LSA_LEN_SIZE + sizeof(struct sockaddr_un)); - lsa->len = sizeof(struct sockaddr_un); + lsa = xzalloc_lsa(AF_UNIX); sun = (struct sockaddr_un*)&lsa->u.sa; - sun->sun_family = AF_UNIX; - strncpy(sun->sun_path, sep->se_service, n); + safe_strncpy(sun->sun_path, sep->se_service, sizeof(sun->sun_path)); break; default: /* case AF_INET, case AF_INET6 */ @@ -995,7 +1072,7 @@ static void reread_config_file(int sig ATTRIBUTE_UNUSED) } if (LONE_CHAR(sep->se_local_hostname, '*')) { lsa = xzalloc_lsa(sep->se_family); - set_nport(lsa, port); + set_nport(&lsa->u.sa, port); } else { lsa = host_and_af2sockaddr(sep->se_local_hostname, ntohs(port), sep->se_family); @@ -1035,7 +1112,7 @@ static void reread_config_file(int sig ATTRIBUTE_UNUSED) * new config file doesnt have them. */ block_CHLD_HUP_ALRM(&omask); sepp = &serv_list; - while ((sep = *sepp)) { + while ((sep = *sepp) != NULL) { if (sep->se_checked) { sepp = &sep->se_next; continue; @@ -1052,10 +1129,12 @@ static void reread_config_file(int sig ATTRIBUTE_UNUSED) free_servtab_strings(sep); free(sep); } - unblock_sigs(&omask); + restore_sigmask(&omask); + ret: + errno = save_errno; } -static void reap_child(int sig ATTRIBUTE_UNUSED) +static void reap_child(int sig UNUSED_PARAM) { pid_t pid; int status; @@ -1066,24 +1145,27 @@ static void reap_child(int sig ATTRIBUTE_UNUSED) pid = wait_any_nohang(&status); if (pid <= 0) break; - for (sep = serv_list; sep; sep = sep->se_next) - if (sep->se_wait == pid) { - /* One of our "wait" services */ - if (WIFEXITED(status) && WEXITSTATUS(status)) - bb_error_msg("%s: exit status 0x%x", - sep->se_program, WEXITSTATUS(status)); - else if (WIFSIGNALED(status)) - bb_error_msg("%s: exit signal 0x%x", - sep->se_program, WTERMSIG(status)); - sep->se_wait = 1; - add_fd_to_set(sep->se_fd); - } + for (sep = serv_list; sep; sep = sep->se_next) { + if (sep->se_wait != pid) + continue; + /* One of our "wait" services */ + if (WIFEXITED(status) && WEXITSTATUS(status)) + bb_error_msg("%s: exit status %u", + sep->se_program, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + bb_error_msg("%s: exit signal %u", + sep->se_program, WTERMSIG(status)); + sep->se_wait = 1; + add_fd_to_set(sep->se_fd); + break; + } } errno = save_errno; } -static void retry_network_setup(int sig ATTRIBUTE_UNUSED) +static void retry_network_setup(int sig UNUSED_PARAM) { + int save_errno = errno; servtab_t *sep; alarm_armed = 0; @@ -1096,9 +1178,10 @@ static void retry_network_setup(int sig ATTRIBUTE_UNUSED) #endif } } + errno = save_errno; } -static void clean_up_and_exit(int sig ATTRIBUTE_UNUSED) +static void clean_up_and_exit(int sig UNUSED_PARAM) { servtab_t *sep; @@ -1121,12 +1204,12 @@ static void clean_up_and_exit(int sig ATTRIBUTE_UNUSED) if (ENABLE_FEATURE_CLEAN_UP) close(sep->se_fd); } - remove_pidfile(_PATH_INETDPID); - exit(0); + remove_pidfile(CONFIG_PID_FILE_PATH "/inetd.pid"); + exit(EXIT_SUCCESS); } int inetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; -int inetd_main(int argc, char **argv) +int inetd_main(int argc UNUSED_PARAM, char **argv) { struct sigaction sa, saved_pipe_handler; servtab_t *sep, *sep2; @@ -1142,8 +1225,8 @@ int inetd_main(int argc, char **argv) if (real_uid != 0) /* run by non-root user */ config_filename = NULL; - opt_complementary = "R+:q+"; /* -q N, -R N */ - opt = getopt32(argv, "R:feq:", &max_concurrency, &global_queuelen); + /* -q N, -R N */ + opt = getopt32(argv, "R:+feq:+", &max_concurrency, &global_queuelen); argv += optind; //argc -= optind; if (argv[0]) @@ -1155,7 +1238,12 @@ int inetd_main(int argc, char **argv) else bb_sanitize_stdio(); if (!(opt & 4)) { - openlog(applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + /* LOG_NDELAY: connect to syslog daemon NOW. + * Otherwise, we may open syslog socket + * in vforked child, making opened fds and syslog() + * internal state inconsistent. + * This was observed to leak file descriptors. */ + openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON); logmode = LOGMODE_SYSLOG; } @@ -1165,7 +1253,7 @@ int inetd_main(int argc, char **argv) setgroups(1, &gid); } - write_pidfile(_PATH_INETDPID); + write_pidfile(CONFIG_PID_FILE_PATH "/inetd.pid"); /* never fails under Linux (except if you pass it bad arguments) */ getrlimit(RLIMIT_NOFILE, &rlim_ofile); @@ -1178,16 +1266,21 @@ int inetd_main(int argc, char **argv) sigaddset(&sa.sa_mask, SIGALRM); sigaddset(&sa.sa_mask, SIGCHLD); sigaddset(&sa.sa_mask, SIGHUP); +//FIXME: explain why no SA_RESTART +//FIXME: retry_network_setup is unsafe to run in signal handler (many reasons)! sa.sa_handler = retry_network_setup; - sigaction(SIGALRM, &sa, NULL); + sigaction_set(SIGALRM, &sa); +//FIXME: reread_config_file is unsafe to run in signal handler(many reasons)! sa.sa_handler = reread_config_file; - sigaction(SIGHUP, &sa, NULL); + sigaction_set(SIGHUP, &sa); +//FIXME: reap_child is unsafe to run in signal handler (uses stdio)! sa.sa_handler = reap_child; - sigaction(SIGCHLD, &sa, NULL); + sigaction_set(SIGCHLD, &sa); +//FIXME: clean_up_and_exit is unsafe to run in signal handler (uses stdio)! sa.sa_handler = clean_up_and_exit; - sigaction(SIGTERM, &sa, NULL); + sigaction_set(SIGTERM, &sa); sa.sa_handler = clean_up_and_exit; - sigaction(SIGINT, &sa, NULL); + sigaction_set(SIGINT, &sa); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, &saved_pipe_handler); @@ -1195,7 +1288,7 @@ int inetd_main(int argc, char **argv) for (;;) { int ready_fd_cnt; - int ctrl, accepted_fd; + int ctrl, accepted_fd, new_udp_fd; fd_set readable; if (maxsock < 0) @@ -1203,7 +1296,8 @@ int inetd_main(int argc, char **argv) readable = allsock; /* struct copy */ /* if there are no fds to wait on, we will block - * until signal wakes us up */ + * until signal wakes us up (maxsock == 0, but readable + * never contains fds 0 and 1...) */ ready_fd_cnt = select(maxsock + 1, &readable, NULL, NULL, NULL); if (ready_fd_cnt < 0) { if (errno != EINTR) { @@ -1212,20 +1306,57 @@ int inetd_main(int argc, char **argv) } continue; } + dbg("ready_fd_cnt:%d\n", ready_fd_cnt); for (sep = serv_list; ready_fd_cnt && sep; sep = sep->se_next) { if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable)) continue; + dbg("ready fd:%d\n", sep->se_fd); ready_fd_cnt--; ctrl = sep->se_fd; accepted_fd = -1; - if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { - ctrl = accepted_fd = accept(sep->se_fd, NULL, NULL); - if (ctrl < 0) { - if (errno != EINTR) - bb_perror_msg("accept (for %s)", sep->se_service); - continue; + new_udp_fd = -1; + if (!sep->se_wait) { + if (sep->se_socktype == SOCK_STREAM) { + ctrl = accepted_fd = accept(sep->se_fd, NULL, NULL); + dbg("accepted_fd:%d\n", accepted_fd); + if (ctrl < 0) { + if (errno != EINTR) + bb_perror_msg("accept (for %s)", sep->se_service); + continue; + } + } + /* "nowait" udp */ + if (sep->se_socktype == SOCK_DGRAM + && sep->se_family != AF_UNIX + ) { +/* How udp "nowait" works: + * child peeks at (received and buffered by kernel) UDP packet, + * performs connect() on the socket so that it is linked only + * to this peer. But this also affects parent, because descriptors + * are shared after fork() a-la dup(). When parent performs + * select(), it will see this descriptor connected to the peer (!) + * and still readable, will act on it and mess things up + * (can create many copies of same child, etc). + * Parent must create and use new socket instead. */ + new_udp_fd = socket(sep->se_family, SOCK_DGRAM, 0); + dbg("new_udp_fd:%d\n", new_udp_fd); + if (new_udp_fd < 0) { /* error: eat packet, forget about it */ + udp_err: + recv(sep->se_fd, line, LINE_SIZE, MSG_DONTWAIT); + continue; + } + setsockopt_reuseaddr(new_udp_fd); + /* TODO: better do bind after fork in parent, + * so that we don't have two wildcard bound sockets + * even for a brief moment? */ + if (bind(new_udp_fd, &sep->se_lsa->u.sa, sep->se_lsa->len) < 0) { + dbg("bind(new_udp_fd) failed\n"); + close(new_udp_fd); + goto udp_err; + } + dbg("bind(new_udp_fd) succeeded\n"); } } @@ -1252,15 +1383,16 @@ int inetd_main(int argc, char **argv) sep->se_fd = -1; sep->se_count = 0; rearm_alarm(); /* will revive it in RETRYTIME sec */ - unblock_sigs(&omask); + restore_sigmask(&omask); + maybe_close(new_udp_fd); maybe_close(accepted_fd); continue; /* -> check next fd in fd set */ } sep->se_count = 0; } } - /* on NOMMU, streamed echo, chargen and discard - * builtins wouldn't work, but they are + /* on NOMMU, streamed chargen + * builtin wouldn't work, but it is * not allowed on NOMMU (ifdefed out) */ #ifdef INETD_BUILTINS_ENABLED if (BB_MMU && sep->se_builtin) @@ -1270,25 +1402,32 @@ int inetd_main(int argc, char **argv) pid = vfork(); if (pid < 0) { /* fork error */ - bb_perror_msg("fork"); + bb_perror_msg("vfork"+1); sleep(1); - unblock_sigs(&omask); + restore_sigmask(&omask); + maybe_close(new_udp_fd); maybe_close(accepted_fd); continue; /* -> check next fd in fd set */ } if (pid == 0) pid--; /* -1: "we did fork and we are child" */ } - /* if pid == 0 here, we never forked */ + /* if pid == 0 here, we didn't fork */ if (pid > 0) { /* parent */ if (sep->se_wait) { + /* wait: we passed socket to child, + * will wait for child to terminate */ sep->se_wait = pid; remove_fd_from_set(sep->se_fd); - /* we passed listening socket to child, - * will wait for child to terminate */ } - unblock_sigs(&omask); + if (new_udp_fd >= 0) { + /* udp nowait: child connected the socket, + * we created and will use new, unconnected one */ + xmove_fd(new_udp_fd, sep->se_fd); + dbg("moved new_udp_fd:%d to sep->se_fd:%d\n", new_udp_fd, sep->se_fd); + } + restore_sigmask(&omask); maybe_close(accepted_fd); continue; /* -> check next fd in fd set */ } @@ -1298,28 +1437,50 @@ int inetd_main(int argc, char **argv) if (sep->se_builtin) { if (pid) { /* "pid" is -1: we did fork */ close(sep->se_fd); /* listening socket */ - logmode = 0; /* make xwrite etc silent */ + dbg("closed sep->se_fd:%d\n", sep->se_fd); + logmode = LOGMODE_NONE; /* make xwrite etc silent */ } - unblock_sigs(&omask); + restore_sigmask(&omask); if (sep->se_socktype == SOCK_STREAM) sep->se_builtin->bi_stream_fn(ctrl, sep); else sep->se_builtin->bi_dgram_fn(ctrl, sep); if (pid) /* we did fork */ - _exit(0); + _exit(EXIT_FAILURE); maybe_close(accepted_fd); continue; /* -> check next fd in fd set */ } #endif - /* child. prepare env and exec program */ + /* child */ setsid(); + /* "nowait" udp */ + if (new_udp_fd >= 0) { + len_and_sockaddr *lsa; + int r; + + close(new_udp_fd); + dbg("closed new_udp_fd:%d\n", new_udp_fd); + lsa = xzalloc_lsa(sep->se_family); + /* peek at the packet and remember peer addr */ + r = recvfrom(ctrl, NULL, 0, MSG_PEEK|MSG_DONTWAIT, + &lsa->u.sa, &lsa->len); + if (r < 0) + goto do_exit1; + /* make this socket "connected" to peer addr: + * only packets from this peer will be recv'ed, + * and bare write()/send() will work on it */ + connect(ctrl, &lsa->u.sa, lsa->len); + dbg("connected ctrl:%d to remote peer\n", ctrl); + free(lsa); + } + /* prepare env and exec program */ pwd = getpwnam(sep->se_user); if (pwd == NULL) { - bb_error_msg("%s: no such user", sep->se_user); + bb_error_msg("%s: no such %s", sep->se_user, "user"); goto do_exit1; } if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) { - bb_error_msg("%s: no such group", sep->se_group); + bb_error_msg("%s: no such %s", sep->se_group, "group"); goto do_exit1; } if (real_uid != 0 && real_uid != pwd->pw_uid) { @@ -1327,12 +1488,11 @@ int inetd_main(int argc, char **argv) bb_error_msg("non-root must run services as himself"); goto do_exit1; } - if (pwd->pw_uid) { + if (pwd->pw_uid != 0) { if (sep->se_group) pwd->pw_gid = grp->gr_gid; - xsetgid(pwd->pw_gid); - initgroups(pwd->pw_name, pwd->pw_gid); - xsetuid(pwd->pw_uid); + /* initgroups, setgid, setuid: */ + change_identity(pwd); } else if (sep->se_group) { xsetgid(grp->gr_gid); setgroups(1, &grp->gr_gid); @@ -1340,49 +1500,73 @@ int inetd_main(int argc, char **argv) if (rlim_ofile.rlim_cur != rlim_ofile_cur) if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) bb_perror_msg("setrlimit"); - closelog(); - xmove_fd(ctrl, 0); - dup2(0, 1); - dup2(0, 2); - /* NB: among others, this loop closes listening socket + + /* closelog(); - WRONG. we are after vfork, + * this may confuse syslog() internal state. + * Let's hope libc sets syslog fd to CLOEXEC... + */ + xmove_fd(ctrl, STDIN_FILENO); + xdup2(STDIN_FILENO, STDOUT_FILENO); + dbg("moved ctrl:%d to fd 0,1[,2]\n", ctrl); + /* manpages of inetd I managed to find either say + * that stderr is also redirected to the network, + * or do not talk about redirection at all (!) */ + if (!sep->se_wait) /* only for usual "tcp nowait" */ + xdup2(STDIN_FILENO, STDERR_FILENO); + /* NB: among others, this loop closes listening sockets * for nowait stream children */ for (sep2 = serv_list; sep2; sep2 = sep2->se_next) - maybe_close(sep2->se_fd); - sigaction(SIGPIPE, &saved_pipe_handler, NULL); - unblock_sigs(&omask); + if (sep2->se_fd != ctrl) + maybe_close(sep2->se_fd); + sigaction_set(SIGPIPE, &saved_pipe_handler); + restore_sigmask(&omask); + dbg("execing:'%s'\n", sep->se_program); BB_EXECVP(sep->se_program, sep->se_argv); - bb_perror_msg("exec %s", sep->se_program); + bb_perror_msg("can't execute '%s'", sep->se_program); do_exit1: /* eat packet in udp case */ if (sep->se_socktype != SOCK_STREAM) recv(0, line, LINE_SIZE, MSG_DONTWAIT); - _exit(1); + _exit(EXIT_FAILURE); } /* for (sep = servtab...) */ } /* for (;;) */ } +#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO \ + || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +# if !BB_MMU +static const char *const cat_args[] = { "cat", NULL }; +# endif +#endif + /* * Internet services provided internally by inetd: */ #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO /* Echo service -- echo data back. */ /* ARGSUSED */ -static void echo_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC echo_stream(int s, servtab_t *sep UNUSED_PARAM) { -#if BB_MMU +# if BB_MMU while (1) { ssize_t sz = safe_read(s, line, LINE_SIZE); if (sz <= 0) break; xwrite(s, line, sz); } -#else - static const char *const args[] = { "cat", NULL }; - BB_EXECVP("cat", (char**)args); - _exit(1); -#endif +# else + /* We are after vfork here! */ + /* move network socket to stdin/stdout */ + xmove_fd(s, STDIN_FILENO); + xdup2(STDIN_FILENO, STDOUT_FILENO); + /* no error messages please... */ + close(STDERR_FILENO); + xopen(bb_dev_null, O_WRONLY); + BB_EXECVP("cat", (char**)cat_args); + /* on failure we return to main, which does exit(EXIT_FAILURE) */ +# endif } -static void echo_dg(int s, servtab_t *sep) +static void FAST_FUNC echo_dg(int s, servtab_t *sep) { enum { BUFSIZE = 12*1024 }; /* for jumbo sized packets! :) */ char *buf = xmalloc(BUFSIZE); /* too big for stack */ @@ -1400,15 +1584,28 @@ static void echo_dg(int s, servtab_t *sep) #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD -/* Discard service -- ignore data. MMU arches only. */ +/* Discard service -- ignore data. */ /* ARGSUSED */ -static void discard_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC discard_stream(int s, servtab_t *sep UNUSED_PARAM) { +# if BB_MMU while (safe_read(s, line, LINE_SIZE) > 0) continue; +# else + /* We are after vfork here! */ + /* move network socket to stdin */ + xmove_fd(s, STDIN_FILENO); + /* discard output */ + close(STDOUT_FILENO); + xopen(bb_dev_null, O_WRONLY); + /* no error messages please... */ + xdup2(STDOUT_FILENO, STDERR_FILENO); + BB_EXECVP("cat", (char**)cat_args); + /* on failure we return to main, which does exit(EXIT_FAILURE) */ +# endif } /* ARGSUSED */ -static void discard_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC discard_dg(int s, servtab_t *sep UNUSED_PARAM) { /* dgram builtins are non-forking - DONT BLOCK! */ recv(s, line, LINE_SIZE, MSG_DONTWAIT); @@ -1418,25 +1615,24 @@ static void discard_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN #define LINESIZ 72 -static void initring(void) +static void init_ring(void) { int i; - endring = ring; - for (i = 0; i <= 128; ++i) - if (isprint(i)) - *endring++ = i; + end_ring = ring; + for (i = ' '; i < 127; i++) + *end_ring++ = i; } /* Character generator. MMU arches only. */ /* ARGSUSED */ -static void chargen_stream(int s, servtab_t *sep) +static void FAST_FUNC chargen_stream(int s, servtab_t *sep UNUSED_PARAM) { char *rs; int len; char text[LINESIZ + 2]; - if (!endring) { - initring(); + if (!end_ring) { + init_ring(); rs = ring; } @@ -1444,20 +1640,20 @@ static void chargen_stream(int s, servtab_t *sep) text[LINESIZ + 1] = '\n'; rs = ring; for (;;) { - len = endring - rs; + len = end_ring - rs; if (len >= LINESIZ) memmove(text, rs, LINESIZ); else { memmove(text, rs, len); memmove(text + len, ring, LINESIZ - len); } - if (++rs == endring) + if (++rs == end_ring) rs = ring; xwrite(s, text, sizeof(text)); } } /* ARGSUSED */ -static void chargen_dg(int s, servtab_t *sep) +static void FAST_FUNC chargen_dg(int s, servtab_t *sep) { int len; char text[LINESIZ + 2]; @@ -1469,20 +1665,20 @@ static void chargen_dg(int s, servtab_t *sep) if (recvfrom(s, text, sizeof(text), MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0) return; - if (!endring) { - initring(); - ringpos = ring; + if (!end_ring) { + init_ring(); + ring_pos = ring; } - len = endring - ringpos; + len = end_ring - ring_pos; if (len >= LINESIZ) - memmove(text, ringpos, LINESIZ); + memmove(text, ring_pos, LINESIZ); else { - memmove(text, ringpos, len); + memmove(text, ring_pos, len); memmove(text + len, ring, LINESIZ - len); } - if (++ringpos == endring) - ringpos = ring; + if (++ring_pos == end_ring) + ring_pos = ring; text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; sendto(s, text, sizeof(text), 0, &lsa->u.sa, lsa->len); @@ -1498,22 +1694,22 @@ static void chargen_dg(int s, servtab_t *sep) * we must add 2208988800 seconds to this figure to make up for * some seventy years Bell Labs was asleep. */ -static unsigned machtime(void) +static uint32_t machtime(void) { struct timeval tv; gettimeofday(&tv, NULL); - return htonl((uint32_t)(tv.tv_sec + 2208988800)); + return htonl((uint32_t)(tv.tv_sec + 2208988800U)); } /* ARGSUSED */ -static void machtime_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC machtime_stream(int s, servtab_t *sep UNUSED_PARAM) { uint32_t result; result = machtime(); full_write(s, &result, sizeof(result)); } -static void machtime_dg(int s, servtab_t *sep) +static void FAST_FUNC machtime_dg(int s, servtab_t *sep) { uint32_t result; len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len); @@ -1531,14 +1727,14 @@ static void machtime_dg(int s, servtab_t *sep) #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME /* Return human-readable time of day */ /* ARGSUSED */ -static void daytime_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC daytime_stream(int s, servtab_t *sep UNUSED_PARAM) { time_t t; - t = time(NULL); + time(&t); fdprintf(s, "%.24s\r\n", ctime(&t)); } -static void daytime_dg(int s, servtab_t *sep) +static void FAST_FUNC daytime_dg(int s, servtab_t *sep) { time_t t; len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);