busybox/networking/inetd.c
James Byrne 6937487be7 libbb: reduce the overhead of single parameter bb_error_msg() calls
Back in 2007, commit 0c97c9d437 ("'simple' error message functions by
Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower
overhead call to bb_perror_msg() when only a string was being printed
with no parameters. This saves space for some CPU architectures because
it avoids the overhead of a call to a variadic function. However there
has never been a simple version of bb_error_msg(), and since 2007 many
new calls to bb_perror_msg() have been added that only take a single
parameter and so could have been using bb_simple_perror_message().

This changeset introduces 'simple' versions of bb_info_msg(),
bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and
bb_herror_msg_and_die(), and replaces all calls that only take a
single parameter, or use something like ("%s", arg), with calls to the
corresponding 'simple' version.

Since it is likely that single parameter calls to the variadic functions
may be accidentally reintroduced in the future a new debugging config
option WARN_SIMPLE_MSG has been introduced. This uses some macro magic
which will cause any such calls to generate a warning, but this is
turned off by default to avoid use of the unpleasant macros in normal
circumstances.

This is a large changeset due to the number of calls that have been
replaced. The only files that contain changes other than simple
substitution of function calls are libbb.h, libbb/herror_msg.c,
libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c,
networking/udhcp/common.h and util-linux/mdev.c additonal macros have
been added for logging so that single parameter and multiple parameter
logging variants exist.

The amount of space saved varies considerably by architecture, and was
found to be as follows (for 'defconfig' using GCC 7.4):

Arm:     -92 bytes
MIPS:    -52 bytes
PPC:   -1836 bytes
x86_64: -938 bytes

Note that for the MIPS architecture only an exception had to be made
disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h)
because it made these files larger on MIPS.

Signed-off-by: James Byrne <james.byrne@origamienergy.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2019-07-02 11:35:03 +02:00

1754 lines
52 KiB
C

/* vi: set sw=4 ts=4: */
/* $Slackware: inetd.c 1.79s 2001/02/06 13:18:00 volkerdi Exp $ */
/* $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 <dzo@simtreas.ru> */
/* IPv6 support, many bug fixes by Denys Vlasenko (c) 2008 */
/*
* Copyright (c) 1983,1991 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/* Inetd - Internet super-server
*
* 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 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. 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.
* The configuration file is "free format" with fields given in the
* 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
* 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)
*
* For RPC services
* 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)
*
* 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.
*
* A line can also consist of just
* hostaddress:
* where hostaddress is as in the preceding paragraph. Such a line must
* have no further fields; the specified hostaddress is remembered and
* used for all further lines that have no hostaddress specified,
* until the next such line (or EOF). (This is why * is provided to
* allow explicit specification of INADDR_ANY.) A line
* *:
* is implicitly in effect at the beginning of the file.
*
* The hostaddress specifier may (and often will) contain dots;
* the service name must not.
*
* For RPC services, host-address specifiers are accepted and will
* work to some extent; however, because of limitations in the
* portmapper interface, it will not work to try to give more than
* one line for any given RPC service, even if the host-address
* specifiers are different.
*
* Comment lines are indicated by a '#' in column 1.
*/
/* inetd rules for passing file descriptors to children
* (http://www.freebsd.org/cgi/man.cgi?query=inetd):
*
* The wait/nowait entry specifies whether the server that is invoked by
* inetd will take over the socket associated with the service access point,
* and thus whether inetd should wait for the server to exit before listen-
* ing for new service requests. Datagram servers must use "wait", as
* they are always invoked with the original datagram socket bound to the
* specified service address. These servers must read at least one datagram
* from the socket before exiting. If a datagram server connects to its
* peer, freeing the socket so inetd can receive further messages on the
* socket, it is said to be a "multi-threaded" server; it should read one
* datagram from the socket and create a new socket connected to the peer.
* It should fork, and the parent should then exit to allow inetd to check
* for new service requests to spawn new servers. Datagram servers which
* process all incoming datagrams on a socket and eventually time out are
* said to be "single-threaded". The comsat(8), biff(1) and talkd(8)
* utilities are both examples of the latter type of datagram server. The
* tftpd(8) utility is an example of a multi-threaded datagram server.
*
* Servers using stream sockets generally are multi-threaded and use the
* "nowait" entry. Connection requests for these services are accepted by
* inetd, and the server is given only the newly-accepted socket connected
* to a client of the service. Most stream-based services operate in this
* manner. Stream-based servers that use "wait" are started with the lis-
* tening service socket, and must accept at least one connection request
* before exiting. Such a server would normally accept and process incoming
* 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: 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: 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 <syslog.h>
#include <sys/resource.h> /* setrlimit */
#include <sys/socket.h> /* un.h may need this */
#include <sys/un.h>
#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.
* This might work:
* CONFIG_EXTRA_CFLAGS="-I/usr/include/tirpc"
* CONFIG_EXTRA_LDLIBS="tirpc"
*/
# endif
# include <rpc/rpc.h>
# include <rpc/pmap_clnt.h>
#endif
#if !BB_MMU
/* stream version of chargen is forking but not execing,
* can't do that (easily) on NOMMU */
#undef ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN 0
#endif
#define CNT_INTERVAL 60 /* servers in CNT_INTERVAL sec. */
#define RETRYTIME 60 /* retry after bind or server fail */
// TODO: explain, or get rid of setrlimit games
#ifndef RLIMIT_NOFILE
#define RLIMIT_NOFILE RLIMIT_OFILE
#endif
#ifndef OPEN_MAX
#define OPEN_MAX 64
#endif
/* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */
#define FD_MARGIN 8
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD \
|| ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO \
|| ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN \
|| ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME \
|| ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
# define INETD_BUILTINS_ENABLED
#endif
typedef struct servtab_t {
/* The most frequently referenced one: */
int se_fd; /* open descriptor */
/* NB: 'biggest fields last' saves on code size (~250 bytes) */
/* [addr:]service socktype proto wait user[:group] prog [args] */
char *se_local_hostname; /* addr to listen on */
char *se_service; /* "80" or "www" or "mount/2[-3]" */
/* socktype is in se_socktype */ /* "stream" "dgram" "raw" "rdm" "seqpacket" */
char *se_proto; /* "unix" or "[rpc/]tcp[6]" */
#if ENABLE_FEATURE_INETD_RPC
int se_rpcprog; /* rpc program number */
int se_rpcver_lo; /* rpc program lowest version */
int se_rpcver_hi; /* rpc program highest version */
#define is_rpc_service(sep) ((sep)->se_rpcver_lo != 0)
#else
#define is_rpc_service(sep) 0
#endif
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] */
/* 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
struct servtab_t *se_next;
len_and_sockaddr *se_lsa;
char *se_program; /* server program */
#define MAXARGV 20
char *se_argv[MAXARGV + 1]; /* program arguments */
} servtab_t;
#ifdef INETD_BUILTINS_ENABLED
/* Echo received data */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO
static void FAST_FUNC echo_stream(int, servtab_t *);
static void FAST_FUNC echo_dg(int, servtab_t *);
#endif
/* Internet /dev/null */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
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 */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
static void FAST_FUNC machtime_stream(int, servtab_t *);
static void FAST_FUNC machtime_dg(int, servtab_t *);
#endif
/* Return human-readable time */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
static void FAST_FUNC daytime_stream(int, servtab_t *);
static void FAST_FUNC daytime_dg(int, servtab_t *);
#endif
/* Familiar character generator */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
static void FAST_FUNC chargen_stream(int, servtab_t *);
static void FAST_FUNC chargen_dg(int, servtab_t *);
#endif
struct builtin {
/* 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", 1, echo_stream, echo_dg },
#endif
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
{ "discard", 1, discard_stream, discard_dg },
#endif
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
{ "chargen", 1, chargen_stream, chargen_dg },
#endif
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
{ "time", 0, machtime_stream, machtime_dg },
#endif
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
{ "daytime", 0, daytime_stream, daytime_dg },
#endif
};
#endif /* INETD_BUILTINS_ENABLED */
struct globals {
rlim_t rlim_ofile_cur;
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;
unsigned max_concurrency;
smallint alarm_armed;
uid_t real_uid; /* user ID who ran us */
const char *config_filename;
parser_t *parser;
char *default_local_hostname;
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
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 */
} FIX_ALIASING;
#define G (*(struct globals*)bb_common_bufsiz1)
enum { LINE_SIZE = COMMON_BUFSIZE - offsetof(struct globals, line) };
#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 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_filename (G.config_filename)
#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 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) {
close(fd);
dbg("closed fd:%d\n", fd);
}
}
// TODO: move to libbb?
static len_and_sockaddr *xzalloc_lsa(int family)
{
len_and_sockaddr *lsa;
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);
#endif
lsa = xzalloc(LSA_LEN_SIZE + sz);
lsa->len = sz;
lsa->u.sa.sa_family = family;
return lsa;
}
static void rearm_alarm(void)
{
if (!alarm_armed) {
alarm_armed = 1;
alarm(RETRYTIME);
}
}
static void block_CHLD_HUP_ALRM(sigset_t *m)
{
sigemptyset(m);
sigaddset(m, SIGCHLD);
sigaddset(m, SIGHUP);
sigaddset(m, SIGALRM);
sigprocmask2(SIG_BLOCK, m); /* old sigmask is stored in m */
}
static void restore_sigmask(sigset_t *m)
{
sigprocmask(SIG_SETMASK, m, NULL);
}
#if ENABLE_FEATURE_INETD_RPC
static void register_rpc(servtab_t *sep)
{
int n;
struct sockaddr_in ir_sin;
if (bb_getsockname(sep->se_fd, (struct sockaddr *) &ir_sin, sizeof(ir_sin)) < 0) {
//TODO: verify that such failure is even possible in Linux kernel
bb_simple_perror_msg("getsockname");
return;
}
for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) {
pmap_unset(sep->se_rpcprog, n);
if (!pmap_set(sep->se_rpcprog, n, sep->se_proto_no, ntohs(ir_sin.sin_port)))
bb_perror_msg("%s %s: pmap_set(%u,%u,%u,%u)",
sep->se_service, sep->se_proto,
sep->se_rpcprog, n, sep->se_proto_no, ntohs(ir_sin.sin_port));
}
}
static void unregister_rpc(servtab_t *sep)
{
int n;
for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) {
if (!pmap_unset(sep->se_rpcprog, n))
bb_perror_msg("pmap_unset(%u,%u)", sep->se_rpcprog, n);
}
}
#endif /* FEATURE_INETD_RPC */
static void bump_nofile(void)
{
enum { FD_CHUNK = 32 };
struct rlimit rl;
/* Never fails under Linux (except if you pass it bad arguments) */
getrlimit(RLIMIT_NOFILE, &rl);
rl.rlim_cur = MIN(rl.rlim_max, rl.rlim_cur + FD_CHUNK);
rl.rlim_cur = MIN(FD_SETSIZE, rl.rlim_cur + FD_CHUNK);
if (rl.rlim_cur <= rlim_ofile_cur) {
bb_error_msg("can't extend file limit, max = %d",
(int) rl.rlim_cur);
return;
}
if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
bb_simple_perror_msg("setrlimit");
return;
}
rlim_ofile_cur = rl.rlim_cur;
}
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);
}
}
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;
dbg("maxsock:%d\n", maxsock);
if ((rlim_t)fd > rlim_ofile_cur - FD_MARGIN)
bump_nofile();
}
}
}
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();
}
static void prepare_socket_fd(servtab_t *sep)
{
int r, fd;
fd = socket(sep->se_family, sep->se_socktype, 0);
if (fd < 0) {
bb_simple_perror_msg("socket");
return;
}
setsockopt_reuseaddr(fd);
#if ENABLE_FEATURE_INETD_RPC
if (is_rpc_service(sep)) {
struct passwd *pwd;
/* zero out the port for all RPC services; let bind()
* find one. */
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. */
if (real_uid == 0 && sep->se_family == AF_INET
&& (pwd = getpwnam(sep->se_user)) != NULL
&& pwd->pw_uid == 0
) {
r = bindresvport(fd, &sep->se_lsa->u.sin);
} else {
r = bind(fd, &sep->se_lsa->u.sa, sep->se_lsa->len);
}
if (r == 0) {
int saveerrno = errno;
/* update lsa with port# */
getsockname(fd, &sep->se_lsa->u.sa, &sep->se_lsa->len);
errno = saveerrno;
}
} else
#endif
{
if (sep->se_family == AF_UNIX) {
struct sockaddr_un *sun;
sun = (struct sockaddr_un*)&(sep->se_lsa->u.sa);
unlink(sun->sun_path);
}
r = bind(fd, &sep->se_lsa->u.sa, sep->se_lsa->len);
}
if (r < 0) {
bb_perror_msg("%s/%s: bind",
sep->se_service, sep->se_proto);
close(fd);
rearm_alarm();
return;
}
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;
}
static int reopen_config_file(void)
{
free(default_local_hostname);
default_local_hostname = xstrdup("*");
if (parser != NULL)
config_close(parser);
parser = config_open(config_filename);
return (parser != NULL);
}
static void close_config_file(void)
{
if (parser) {
config_close(parser);
parser = NULL;
}
}
static void free_servtab_strings(servtab_t *cp)
{
int i;
free(cp->se_local_hostname);
free(cp->se_service);
free(cp->se_proto);
free(cp->se_user);
free(cp->se_group);
free(cp->se_lsa); /* not a string in fact */
free(cp->se_program);
for (i = 0; i < MAXARGV; i++)
free(cp->se_argv[i]);
}
static servtab_t *new_servtab(void)
{
servtab_t *newtab = xzalloc(sizeof(servtab_t));
newtab->se_fd = -1; /* paranoia */
return newtab;
}
static servtab_t *dup_servtab(servtab_t *sep)
{
servtab_t *newtab;
int argc;
newtab = new_servtab();
*newtab = *sep; /* struct copy */
/* deep-copying strings */
newtab->se_service = xstrdup(newtab->se_service);
newtab->se_proto = xstrdup(newtab->se_proto);
newtab->se_user = xstrdup(newtab->se_user);
newtab->se_group = xstrdup(newtab->se_group);
newtab->se_program = xstrdup(newtab->se_program);
for (argc = 0; argc <= MAXARGV; argc++)
newtab->se_argv[argc] = xstrdup(newtab->se_argv[argc]);
/* NB: se_fd, se_hostaddr and se_next are always
* overwrittend by callers, so we don't bother resetting them
* to NULL/0/-1 etc */
return newtab;
}
/* gcc generates much more code if this is inlined */
static NOINLINE servtab_t *parse_one_line(void)
{
int argc;
char *token[6+MAXARGV];
char *p, *arg;
char *hostdelim;
servtab_t *sep;
servtab_t *nsep;
new:
sep = new_servtab();
more:
argc = config_read(parser, token, 6+MAXARGV, 1, "# \t", PARSE_NORMAL);
if (!argc) {
free(sep);
return NULL;
}
/* [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' && 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] */
if (argc < 6) {
parse_err:
bb_error_msg("parse error on line %u, line is ignored",
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 const int8_t SOCK_xxx[] ALIGN1 = {
-1,
SOCK_STREAM, SOCK_DGRAM, SOCK_RDM,
SOCK_SEQPACKET, SOCK_RAW
};
sep->se_socktype = SOCK_xxx[1 + index_in_strings(
"stream""\0" "dgram""\0" "rdm""\0"
"seqpacket""\0" "raw""\0"
, token[1])];
}
/* {unix,[rpc/]{tcp,udp}[6]} wait user[:group] prog [args] */
sep->se_proto = arg = xstrdup(token[2]);
if (strcmp(arg, "unix") == 0) {
sep->se_family = AF_UNIX;
} else {
char *six;
sep->se_family = AF_INET;
six = last_char_is(arg, '6');
if (six) {
#if ENABLE_FEATURE_IPV6
*six = '\0';
sep->se_family = AF_INET6;
#else
bb_error_msg("%s: no support for IPv6", sep->se_proto);
goto parse_err;
#endif
}
if (is_prefixed_with(arg, "rpc/")) {
#if ENABLE_FEATURE_INETD_RPC
unsigned n;
arg += 4;
p = strchr(sep->se_service, '/');
if (p == NULL) {
bb_error_msg("no rpc version: '%s'", sep->se_service);
goto parse_err;
}
*p++ = '\0';
n = bb_strtou(p, &p, 10);
if (n > INT_MAX) {
bad_ver_spec:
bb_simple_error_msg("bad rpc version");
goto parse_err;
}
sep->se_rpcver_lo = sep->se_rpcver_hi = n;
if (*p == '-') {
p++;
n = bb_strtou(p, &p, 10);
if (n > INT_MAX || (int)n < sep->se_rpcver_lo)
goto bad_ver_spec;
sep->se_rpcver_hi = n;
}
if (*p != '\0')
goto bad_ver_spec;
#else
bb_simple_error_msg("no support for rpc services");
goto parse_err;
#endif
}
/* we don't really need getprotobyname()! */
if (strcmp(arg, "tcp") == 0)
sep->se_proto_no = IPPROTO_TCP; /* = 6 */
if (strcmp(arg, "udp") == 0)
sep->se_proto_no = IPPROTO_UDP; /* = 17 */
if (six)
*six = '6';
if (!sep->se_proto_no) /* not tcp/udp?? */
goto parse_err;
}
/* [no]wait[.max] user[:group] prog [args] */
arg = token[3];
sep->se_max = max_concurrency;
p = strchr(arg, '.');
if (p) {
*p++ = '\0';
sep->se_max = bb_strtou(p, NULL, 10);
if (errno)
goto parse_err;
}
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(token[4]);
arg = strchr(sep->se_user, '.');
if (arg == NULL)
arg = strchr(sep->se_user, ':');
if (arg) {
*arg++ = '\0';
sep->se_group = xstrdup(arg);
}
/* prog [args] */
sep->se_program = xstrdup(token[5]);
#ifdef INETD_BUILTINS_ENABLED
if (strcmp(sep->se_program, "internal") == 0
&& strlen(sep->se_service) <= 7
&& (sep->se_socktype == SOCK_STREAM
|| sep->se_socktype == SOCK_DGRAM)
) {
unsigned i;
for (i = 0; i < ARRAY_SIZE(builtins); i++)
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];
/* 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 (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. "<service> 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;
}
//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. */
*hostdelim++ = '\0';
nsep->se_local_hostname = xstrdup(hostdelim);
nsep->se_next = sep->se_next;
sep->se_next = nsep;
}
/* was doing it here: */
/* DNS resolution, create copies for each IP address */
/* IPv6-ization destroyed it :( */
return sep;
}
static servtab_t *insert_in_servlist(servtab_t *cp)
{
servtab_t *sep;
sigset_t omask;
sep = new_servtab();
*sep = *cp; /* struct copy */
sep->se_fd = -1;
#if ENABLE_FEATURE_INETD_RPC
sep->se_rpcprog = -1;
#endif
block_CHLD_HUP_ALRM(&omask);
sep->se_next = serv_list;
serv_list = sep;
restore_sigmask(&omask);
return sep;
}
static int same_serv_addr_proto(servtab_t *old, servtab_t *new)
{
if (strcmp(old->se_local_hostname, new->se_local_hostname) != 0)
return 0;
if (strcmp(old->se_service, new->se_service) != 0)
return 0;
if (strcmp(old->se_proto, new->se_proto) != 0)
return 0;
return 1;
}
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())
goto ret;
for (sep = serv_list; sep; sep = sep->se_next)
sep->se_checked = 0;
goto first_line;
while (1) {
if (cp == NULL) {
first_line:
cp = parse_one_line();
if (cp == NULL)
break;
}
for (sep = serv_list; sep; sep = sep->se_next)
if (same_serv_addr_proto(sep, cp))
goto equal_servtab;
/* not an "equal" servtab */
sep = insert_in_servlist(cp);
goto after_check;
equal_servtab:
{
int i;
block_CHLD_HUP_ALRM(&omask);
#if ENABLE_FEATURE_INETD_RPC
if (is_rpc_service(sep))
unregister_rpc(sep);
sep->se_rpcver_lo = cp->se_rpcver_lo;
sep->se_rpcver_hi = cp->se_rpcver_hi;
#endif
if (cp->se_wait == 0) {
/* New config says "nowait". If old one
* was "wait", we currently may be waiting
* for a child (and not accepting connects).
* Stop waiting, start listening again.
* (if it's not true, this op is harmless) */
add_fd_to_set(sep->se_fd);
}
sep->se_wait = cp->se_wait;
sep->se_max = cp->se_max;
/* string fields need more love - we don't want to leak them */
#define SWAP(type, a, b) do { type c = (type)a; a = (type)b; b = (type)c; } while (0)
SWAP(char*, sep->se_user, cp->se_user);
SWAP(char*, sep->se_group, cp->se_group);
SWAP(char*, sep->se_program, cp->se_program);
for (i = 0; i < MAXARGV; i++)
SWAP(char*, sep->se_argv[i], cp->se_argv[i]);
#undef SWAP
restore_sigmask(&omask);
free_servtab_strings(cp);
}
after_check:
/* cp->string_fields are consumed by insert_in_servlist()
* or freed at this point, cp itself is not yet freed. */
sep->se_checked = 1;
/* create new len_and_sockaddr */
switch (sep->se_family) {
struct sockaddr_un *sun;
case AF_UNIX:
lsa = xzalloc_lsa(AF_UNIX);
sun = (struct sockaddr_un*)&lsa->u.sa;
safe_strncpy(sun->sun_path, sep->se_service, sizeof(sun->sun_path));
break;
default: /* case AF_INET, case AF_INET6 */
n = bb_strtou(sep->se_service, NULL, 10);
#if ENABLE_FEATURE_INETD_RPC
if (is_rpc_service(sep)) {
sep->se_rpcprog = n;
if (errno) { /* se_service is not numeric */
struct rpcent *rp = getrpcbyname(sep->se_service);
if (rp == NULL) {
bb_error_msg("%s: unknown rpc service", sep->se_service);
goto next_cp;
}
sep->se_rpcprog = rp->r_number;
}
if (sep->se_fd == -1)
prepare_socket_fd(sep);
if (sep->se_fd != -1)
register_rpc(sep);
goto next_cp;
}
#endif
/* what port to listen on? */
port = htons(n);
if (errno || n > 0xffff) { /* se_service is not numeric */
char protoname[4];
struct servent *sp;
/* can result only in "tcp" or "udp": */
safe_strncpy(protoname, sep->se_proto, 4);
sp = getservbyname(sep->se_service, protoname);
if (sp == NULL) {
bb_error_msg("%s/%s: unknown service",
sep->se_service, sep->se_proto);
goto next_cp;
}
port = sp->s_port;
}
if (LONE_CHAR(sep->se_local_hostname, '*')) {
lsa = xzalloc_lsa(sep->se_family);
set_nport(&lsa->u.sa, port);
} else {
lsa = host_and_af2sockaddr(sep->se_local_hostname,
ntohs(port), sep->se_family);
if (!lsa) {
bb_error_msg("%s/%s: unknown host '%s'",
sep->se_service, sep->se_proto,
sep->se_local_hostname);
goto next_cp;
}
}
break;
} /* end of "switch (sep->se_family)" */
/* did lsa change? Then close/open */
if (sep->se_lsa == NULL
|| lsa->len != sep->se_lsa->len
|| memcmp(&lsa->u.sa, &sep->se_lsa->u.sa, lsa->len) != 0
) {
remove_fd_from_set(sep->se_fd);
maybe_close(sep->se_fd);
free(sep->se_lsa);
sep->se_lsa = lsa;
sep->se_fd = -1;
} else {
free(lsa);
}
if (sep->se_fd == -1)
prepare_socket_fd(sep);
next_cp:
sep = cp->se_next;
free(cp);
cp = sep;
} /* end of "while (1) parse lines" */
close_config_file();
/* Purge anything not looked at above - these are stale entries,
* new config file doesnt have them. */
block_CHLD_HUP_ALRM(&omask);
sepp = &serv_list;
while ((sep = *sepp) != NULL) {
if (sep->se_checked) {
sepp = &sep->se_next;
continue;
}
*sepp = sep->se_next;
remove_fd_from_set(sep->se_fd);
maybe_close(sep->se_fd);
#if ENABLE_FEATURE_INETD_RPC
if (is_rpc_service(sep))
unregister_rpc(sep);
#endif
if (sep->se_family == AF_UNIX)
unlink(sep->se_service);
free_servtab_strings(sep);
free(sep);
}
restore_sigmask(&omask);
ret:
errno = save_errno;
}
static void reap_child(int sig UNUSED_PARAM)
{
pid_t pid;
int status;
servtab_t *sep;
int save_errno = errno;
for (;;) {
pid = wait_any_nohang(&status);
if (pid <= 0)
break;
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 UNUSED_PARAM)
{
int save_errno = errno;
servtab_t *sep;
alarm_armed = 0;
for (sep = serv_list; sep; sep = sep->se_next) {
if (sep->se_fd == -1) {
prepare_socket_fd(sep);
#if ENABLE_FEATURE_INETD_RPC
if (sep->se_fd != -1 && is_rpc_service(sep))
register_rpc(sep);
#endif
}
}
errno = save_errno;
}
static void clean_up_and_exit(int sig UNUSED_PARAM)
{
servtab_t *sep;
/* XXX signal race walking sep list */
for (sep = serv_list; sep; sep = sep->se_next) {
if (sep->se_fd == -1)
continue;
switch (sep->se_family) {
case AF_UNIX:
unlink(sep->se_service);
break;
default: /* case AF_INET, AF_INET6 */
#if ENABLE_FEATURE_INETD_RPC
if (sep->se_wait == 1 && is_rpc_service(sep))
unregister_rpc(sep); /* XXX signal race */
#endif
break;
}
if (ENABLE_FEATURE_CLEAN_UP)
close(sep->se_fd);
}
remove_pidfile_std_path_and_ext("inetd");
exit(EXIT_SUCCESS);
}
int inetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int inetd_main(int argc UNUSED_PARAM, char **argv)
{
struct sigaction sa, saved_pipe_handler;
servtab_t *sep, *sep2;
struct passwd *pwd;
struct group *grp = grp; /* for compiler */
int opt;
pid_t pid;
sigset_t omask;
INIT_G();
real_uid = getuid();
if (real_uid != 0) /* run by non-root user */
config_filename = NULL;
/* -q N, -R N */
opt = getopt32(argv, "R:+feq:+", &max_concurrency, &global_queuelen);
argv += optind;
//argc -= optind;
if (argv[0])
config_filename = argv[0];
if (config_filename == NULL)
bb_simple_error_msg_and_die("non-root must specify config file");
if (!(opt & 2))
bb_daemonize_or_rexec(0, argv - optind);
else
bb_sanitize_stdio();
if (!(opt & 4)) {
/* 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;
}
if (real_uid == 0) {
/* run by root, ensure groups vector gets trashed */
gid_t gid = getgid();
setgroups(1, &gid);
}
write_pidfile_std_path_and_ext("inetd");
/* never fails under Linux (except if you pass it bad arguments) */
getrlimit(RLIMIT_NOFILE, &rlim_ofile);
rlim_ofile_cur = rlim_ofile.rlim_cur;
if (rlim_ofile_cur == RLIM_INFINITY) /* ! */
rlim_ofile_cur = OPEN_MAX;
memset(&sa, 0, sizeof(sa));
/*sigemptyset(&sa.sa_mask); - memset did it */
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_set(SIGALRM, &sa);
//FIXME: reread_config_file is unsafe to run in signal handler(many reasons)!
sa.sa_handler = reread_config_file;
sigaction_set(SIGHUP, &sa);
//FIXME: reap_child is unsafe to run in signal handler (uses stdio)!
sa.sa_handler = reap_child;
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_set(SIGTERM, &sa);
sa.sa_handler = clean_up_and_exit;
sigaction_set(SIGINT, &sa);
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, &saved_pipe_handler);
reread_config_file(SIGHUP); /* load config from file */
for (;;) {
int ready_fd_cnt;
int ctrl, accepted_fd, new_udp_fd;
fd_set readable;
if (maxsock < 0)
recalculate_maxsock();
readable = allsock; /* struct copy */
/* if there are no fds to wait on, we will block
* 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) {
bb_simple_perror_msg("select");
sleep(1);
}
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;
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");
}
}
block_CHLD_HUP_ALRM(&omask);
pid = 0;
#ifdef INETD_BUILTINS_ENABLED
/* do we need to fork? */
if (sep->se_builtin == NULL
|| (sep->se_socktype == SOCK_STREAM
&& sep->se_builtin->bi_fork))
#endif
{
if (sep->se_max != 0) {
if (++sep->se_count == 1)
sep->se_time = monotonic_sec();
else if (sep->se_count >= sep->se_max) {
unsigned now = monotonic_sec();
/* did we accumulate se_max connects too quickly? */
if (now - sep->se_time <= CNT_INTERVAL) {
bb_error_msg("%s/%s: too many connections, pausing",
sep->se_service, sep->se_proto);
remove_fd_from_set(sep->se_fd);
close(sep->se_fd);
sep->se_fd = -1;
sep->se_count = 0;
rearm_alarm(); /* will revive it in RETRYTIME sec */
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 chargen
* builtin wouldn't work, but it is
* not allowed on NOMMU (ifdefed out) */
#ifdef INETD_BUILTINS_ENABLED
if (BB_MMU && sep->se_builtin)
pid = fork();
else
#endif
pid = vfork();
if (pid < 0) { /* fork error */
bb_simple_perror_msg("vfork"+1);
sleep(1);
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 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);
}
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 */
}
/* we are either child or didn't fork at all */
#ifdef INETD_BUILTINS_ENABLED
if (sep->se_builtin) {
if (pid) { /* "pid" is -1: we did fork */
close(sep->se_fd); /* listening socket */
dbg("closed sep->se_fd:%d\n", sep->se_fd);
logmode = LOGMODE_NONE; /* make xwrite etc silent */
}
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(EXIT_FAILURE);
maybe_close(accepted_fd);
continue; /* -> check next fd in fd set */
}
#endif
/* 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 %s", sep->se_user, "user");
goto do_exit1;
}
if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) {
bb_error_msg("%s: no such %s", sep->se_group, "group");
goto do_exit1;
}
if (real_uid != 0 && real_uid != pwd->pw_uid) {
/* a user running private inetd */
bb_simple_error_msg("non-root must run services as himself");
goto do_exit1;
}
if (pwd->pw_uid != real_uid) {
if (sep->se_group)
pwd->pw_gid = grp->gr_gid;
/* initgroups, setgid, setuid: */
change_identity(pwd);
} else if (sep->se_group) {
xsetgid(grp->gr_gid);
setgroups(1, &grp->gr_gid);
}
if (rlim_ofile.rlim_cur != rlim_ofile_cur)
if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0)
bb_simple_perror_msg("setrlimit");
/* 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)
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("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(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 FAST_FUNC echo_stream(int s, servtab_t *sep UNUSED_PARAM)
{
# if BB_MMU
while (1) {
ssize_t sz = safe_read(s, line, LINE_SIZE);
if (sz <= 0)
break;
xwrite(s, line, sz);
}
# 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 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 */
int sz;
len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
lsa->len = sep->se_lsa->len;
/* dgram builtins are non-forking - DONT BLOCK! */
sz = recvfrom(s, buf, BUFSIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len);
if (sz > 0)
sendto(s, buf, sz, 0, &lsa->u.sa, lsa->len);
free(buf);
}
#endif /* FEATURE_INETD_SUPPORT_BUILTIN_ECHO */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
/* Discard service -- ignore data. */
/* ARGSUSED */
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 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);
}
#endif /* FEATURE_INETD_SUPPORT_BUILTIN_DISCARD */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
#define LINESIZ 72
static void init_ring(void)
{
int i;
end_ring = ring;
for (i = ' '; i < 127; i++)
*end_ring++ = i;
}
/* Character generator. MMU arches only. */
/* ARGSUSED */
static void FAST_FUNC chargen_stream(int s, servtab_t *sep UNUSED_PARAM)
{
char *rs;
int len;
char text[LINESIZ + 2];
if (!end_ring) {
init_ring();
rs = ring;
}
text[LINESIZ] = '\r';
text[LINESIZ + 1] = '\n';
rs = ring;
for (;;) {
len = end_ring - rs;
if (len >= LINESIZ)
memmove(text, rs, LINESIZ);
else {
memmove(text, rs, len);
memmove(text + len, ring, LINESIZ - len);
}
if (++rs == end_ring)
rs = ring;
xwrite(s, text, sizeof(text));
}
}
/* ARGSUSED */
static void FAST_FUNC chargen_dg(int s, servtab_t *sep)
{
int len;
char text[LINESIZ + 2];
len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
/* Eat UDP packet which started it all */
/* dgram builtins are non-forking - DONT BLOCK! */
lsa->len = sep->se_lsa->len;
if (recvfrom(s, text, sizeof(text), MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
return;
if (!end_ring) {
init_ring();
ring_pos = ring;
}
len = end_ring - ring_pos;
if (len >= LINESIZ)
memmove(text, ring_pos, LINESIZ);
else {
memmove(text, ring_pos, len);
memmove(text + len, ring, LINESIZ - len);
}
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);
}
#endif /* FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
/*
* Return a machine readable date and time, in the form of the
* number of seconds since midnight, Jan 1, 1900. Since gettimeofday
* returns the number of seconds since midnight, Jan 1, 1970,
* we must add 2208988800 seconds to this figure to make up for
* some seventy years Bell Labs was asleep.
*/
static uint32_t machtime(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return htonl((uint32_t)(tv.tv_sec + 2208988800U));
}
/* ARGSUSED */
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 FAST_FUNC machtime_dg(int s, servtab_t *sep)
{
uint32_t result;
len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
lsa->len = sep->se_lsa->len;
if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
return;
result = machtime();
sendto(s, &result, sizeof(result), 0, &lsa->u.sa, lsa->len);
}
#endif /* FEATURE_INETD_SUPPORT_BUILTIN_TIME */
#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
/* Return human-readable time of day */
/* ARGSUSED */
static void FAST_FUNC daytime_stream(int s, servtab_t *sep UNUSED_PARAM)
{
time_t t;
time(&t);
fdprintf(s, "%.24s\r\n", ctime(&t));
}
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);
lsa->len = sep->se_lsa->len;
if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
return;
t = time(NULL);
sprintf(line, "%.24s\r\n", ctime(&t));
sendto(s, line, strlen(line), 0, &lsa->u.sa, lsa->len);
}
#endif /* FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME */