X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=networking%2Finetd.c;h=584c5e5e40dd3643a9e231e7c9290de7035bdd35;hb=HEAD;hp=9c17c920c06e90fe4c08d94aa9fa82cb8875c0f9;hpb=1bec1b980e3cf5ad604fb0c2038a3ab83d9ab5f5;p=platform%2Fupstream%2Fbusybox.git diff --git a/networking/inetd.c b/networking/inetd.c index 9c17c92..584c5e5 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,27 +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 - * protocol must be in /etc/protocols + * 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: @@ -101,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 @@ -120,7 +118,7 @@ * 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) + * 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. * @@ -134,51 +132,64 @@ * connection requests until a timeout. */ -/* Here's the scoop concerning the user[.:]group feature: - * - * 1) set-group-option off. - * +/* 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) set-group-option on. - * + * 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() */ -#include "libbb.h" +//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)" + #include +#include /* setrlimit */ +#include /* un.h may need this */ #include -//#define ENABLE_FEATURE_INETD_RPC 1 -//#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO 1 -//#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD 1 -//#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME 1 -//#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME 1 -//#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN 1 -//#define ENABLE_FEATURE_IPV6 1 +#include "libbb.h" #if ENABLE_FEATURE_INETD_RPC -#include -#include +# if defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_RPC__) +# error "You need to build uClibc with UCLIBC_HAS_RPC for NFS support" +# endif +# include +# include #endif -extern char **environ; - +#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 _PATH_INETDPID "/var/run/inetd.pid" +#define CNT_INTERVAL 60 /* servers in CNT_INTERVAL sec. */ +#define RETRYTIME 60 /* retry after bind or server fail */ -#define CNT_INTVL 60 /* servers in CNT_INTVL 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 @@ -191,175 +202,131 @@ extern char **environ; /* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */ #define FD_MARGIN 8 -/* Check unsupporting builtin */ -#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ - ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD || \ - ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME || \ - ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME || \ - ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN -# define INETD_FEATURE_ENABLED -#endif - -#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ - ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD || \ - ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN -# define INETD_SETPROCTITLE +#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 int8_t socktype_t; -typedef int8_t family_t; -struct BUG_too_small { - char BUG_socktype_t_too_small[(0 - | SOCK_STREAM - | SOCK_DGRAM - | SOCK_RDM - | SOCK_SEQPACKET - | SOCK_RAW) <= 127 ? 1 : -1]; - char BUG_family_t_too_small[(0 - | AF_INET - | AF_INET6 - | AF_UNIX) <= 127 ? 1 : -1]; -}; - typedef struct servtab_t { /* The most frequently referenced one: */ int se_fd; /* open descriptor */ /* NB: 'biggest fields last' saves on code size (~250 bytes) */ - char *se_hostaddr; /* host address to listen on */ - char *se_service; /* name of service */ - char *se_proto; /* protocol used */ + /* [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_rpcversl; /* rpc program lowest version */ - int se_rpcversh; /* rpc program highest version */ -#define isrpcservice(sep) ((sep)->se_rpcversl != 0) + 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 isrpcservice(sep) 0 +#define is_rpc_service(sep) 0 #endif - pid_t se_wait; /* single threaded server */ - socktype_t se_socktype; /* type of socket to use */ - family_t se_family; /* address family */ + 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 */ -#ifdef INETD_FEATURE_ENABLED - const struct builtin *se_bi; /* if built-in, description */ + 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 - int se_ctrladdr_size; - int se_max; /* max # of instances of this service */ - int se_count; /* number started since se_time */ struct servtab_t *se_next; - struct timeval se_time; /* start of se_count */ - char *se_server; /* server program */ + len_and_sockaddr *se_lsa; + char *se_program; /* server program */ #define MAXARGV 20 char *se_argv[MAXARGV + 1]; /* program arguments */ - union { - struct sockaddr se_un_ctrladdr; - struct sockaddr_in se_un_ctrladdr_in; -#if ENABLE_FEATURE_IPV6 - struct sockaddr_in6 se_un_ctrladdr_in6; -#endif - struct sockaddr_un se_un_ctrladdr_un; - } se_un; /* bound address */ -#define se_ctrladdr se_un.se_un_ctrladdr -#define se_ctrladdr_in se_un.se_un_ctrladdr_in -#define se_ctrladdr_in6 se_un.se_un_ctrladdr_in6 -#define se_ctrladdr_un se_un.se_un_ctrladdr_un } servtab_t; -#ifdef INETD_FEATURE_ENABLED -struct builtin { - const char *bi_service; /* internally provided service name */ - socktype_t bi_socktype; /* type of socket supported */ - uint8_t bi_fork; /* 1 if should fork before call */ -// Commented since it is always 0 -// uint8_t bi_wait; /* 1 if should wait for child */ - void (*bi_fn) (int, servtab_t *); -}; - - /* Echo received data */ +#ifdef INETD_BUILTINS_ENABLED +/* 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 { + /* 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", SOCK_STREAM, 1, echo_stream,}, - {"echo", SOCK_DGRAM, 0, echo_dg,}, + { "echo", 1, echo_stream, echo_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD - /* Internet /dev/null */ - {"discard", SOCK_STREAM, 1, discard_stream,}, - {"discard", SOCK_DGRAM, 0, discard_dg,}, + { "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 - /* Return 32 bit time since 1900 */ - {"time", SOCK_STREAM, 0, machtime_stream,}, - {"time", SOCK_DGRAM, 0, machtime_dg,}, + { "time", 0, machtime_stream, machtime_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME - /* Return human-readable time */ - {"daytime", SOCK_STREAM, 0, daytime_stream,}, - {"daytime", SOCK_DGRAM, 0, daytime_dg,}, + { "daytime", 0, daytime_stream, daytime_dg }, #endif -#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN - /* Familiar character generator */ - {"chargen", SOCK_STREAM, 1, chargen_stream,}, - {"chargen", SOCK_DGRAM, 0, chargen_dg,}, -#endif - { /* zero filled */ } }; -#endif /* INETD_FEATURE_ENABLED */ +#endif /* INETD_BUILTINS_ENABLED */ struct globals { rlim_t rlim_ofile_cur; struct rlimit rlim_ofile; - servtab_t *servtab; + servtab_t *serv_list; int global_queuelen; - int nsock; - int maxsock; - int toomany; - int timingout; - struct servent *sp; - uid_t uid; + 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; - FILE *fconfig; - char *defhost; -#ifdef INETD_SETPROCTITLE - char **Argv; - char *LastArg; -#endif + 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 only in nextline() */ - char line[80]; /* at least 80, see LINE_SIZE */ -}; + /* 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) }; struct BUG_G_too_big { @@ -367,21 +334,20 @@ struct BUG_G_too_big { }; #define rlim_ofile_cur (G.rlim_ofile_cur ) #define rlim_ofile (G.rlim_ofile ) -#define servtab (G.servtab ) +#define serv_list (G.serv_list ) #define global_queuelen (G.global_queuelen) -#define nsock (G.nsock ) #define maxsock (G.maxsock ) -#define toomany (G.toomany ) -#define timingout (G.timingout ) -#define sp (G.sp ) -#define uid (G.uid ) +#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 fconfig (G.fconfig ) -#define defhost (G.defhost ) -#define Argv (G.Argv ) -#define LastArg (G.LastArg ) -#define endring (G.endring ) -#define ringpos (G.ringpos ) +#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 ) @@ -391,35 +357,67 @@ struct BUG_G_too_big { config_filename = "/etc/inetd.conf"; \ } while (0) -/* xstrdup(NULL) returns NULL, but this one - * will return newly-allocated "" if called with NULL arg - * TODO: audit whether this makes any real difference - */ -static char *xxstrdup(char *cp) +#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) { - return xstrdup(cp ? cp : ""); + if (fd >= 0) { + close(fd); + dbg("closed fd:%d\n", fd); + } } -static int setconfig(void) +// TODO: move to libbb? +static len_and_sockaddr *xzalloc_lsa(int family) { - free(defhost); - defhost = xstrdup("*"); - if (fconfig != NULL) { - fseek(fconfig, 0L, SEEK_SET); - return 1; - } - fconfig = fopen(config_filename, "r"); - return (fconfig != NULL); + 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 endconfig(void) +static void rearm_alarm(void) { - if (fconfig) { - (void) fclose(fconfig); - fconfig = NULL; + if (!alarm_armed) { + alarm_armed = 1; + alarm(RETRYTIME); } - free(defhost); - defhost = 0; +} + +static void block_CHLD_HUP_ALRM(sigset_t *m) +{ + sigemptyset(m); + sigaddset(m, SIGCHLD); + sigaddset(m, SIGHUP); + sigaddset(m, SIGALRM); + sigprocmask(SIG_BLOCK, m, m); /* old sigmask is stored in m */ +} + +static void restore_sigmask(sigset_t *m) +{ + sigprocmask(SIG_SETMASK, m, NULL); } #if ENABLE_FEATURE_INETD_RPC @@ -427,27 +425,20 @@ static void register_rpc(servtab_t *sep) { int n; struct sockaddr_in ir_sin; - struct protoent *pp; socklen_t size; - pp = getprotobyname(sep->se_proto + 4); - if (pp == NULL) { - bb_perror_msg("%s: getproto", sep->se_proto); - return; - } - size = sizeof ir_sin; + size = sizeof(ir_sin); if (getsockname(sep->se_fd, (struct sockaddr *) &ir_sin, &size) < 0) { - bb_perror_msg("%s/%s: getsockname", - sep->se_service, sep->se_proto); + bb_perror_msg("getsockname"); return; } - for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) { - (void) pmap_unset(sep->se_rpcprog, n); - if (!pmap_set(sep->se_rpcprog, n, pp->p_proto, 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, pp->p_proto, ntohs(ir_sin.sin_port)); + 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)); } } @@ -455,262 +446,258 @@ static void unregister_rpc(servtab_t *sep) { int n; - for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) { + for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) { if (!pmap_unset(sep->se_rpcprog, n)) - bb_error_msg("pmap_unset(%u, %u)", sep->se_rpcprog, n); + bb_perror_msg("pmap_unset(%u,%u)", sep->se_rpcprog, n); } } #endif /* FEATURE_INETD_RPC */ -static void freeconfig(servtab_t *cp) -{ - int i; - - free(cp->se_hostaddr); - free(cp->se_service); - free(cp->se_proto); - free(cp->se_user); - free(cp->se_group); - free(cp->se_server); - for (i = 0; i < MAXARGV; i++) - free(cp->se_argv[i]); -} - -static int bump_nofile(void) +static void bump_nofile(void) { -#define FD_CHUNK 32 - + enum { FD_CHUNK = 32 }; struct rlimit rl; - if (getrlimit(RLIMIT_NOFILE, &rl) < 0) { - bb_perror_msg("getrlimit"); - return -1; - } + /* 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("bump_nofile: cannot extend file limit, max = %d", + bb_error_msg("can't extend file limit, max = %d", (int) rl.rlim_cur); - return -1; + return; } if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { bb_perror_msg("setrlimit"); - return -1; + return; } rlim_ofile_cur = rl.rlim_cur; - return 0; } -static void setup(servtab_t *sep) +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; + int r, fd; - sep->se_fd = socket(sep->se_family, sep->se_socktype, 0); - if (sep->se_fd < 0) { - bb_perror_msg("%s/%s: socket", sep->se_service, sep->se_proto); + fd = socket(sep->se_family, sep->se_socktype, 0); + if (fd < 0) { + bb_perror_msg("socket"); return; } - setsockopt_reuseaddr(sep->se_fd); + setsockopt_reuseaddr(fd); #if ENABLE_FEATURE_INETD_RPC - if (isrpcservice(sep)) { + if (is_rpc_service(sep)) { struct passwd *pwd; - /* - * for RPC services, attempt to use a reserved port - * if they are going to be running as root. - * - * Also, zero out the port for all RPC services; let bind() - * find one. - */ - sep->se_ctrladdr_in.sin_port = 0; - if (sep->se_user && (pwd = getpwnam(sep->se_user)) && - pwd->pw_uid == 0 && uid == 0) - r = bindresvport(sep->se_fd, &sep->se_ctrladdr_in); - else { - r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size); - if (r == 0) { - socklen_t len = sep->se_ctrladdr_size; - int saveerrno = errno; - - /* update se_ctrladdr_in.sin_port */ - r = getsockname(sep->se_fd, &sep->se_ctrladdr, &len); - if (r <= 0) - errno = saveerrno; - } + /* 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 - r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size); - if (r < 0) { - bb_perror_msg("%s/%s (%d): bind", - sep->se_service, sep->se_proto, sep->se_ctrladdr.sa_family); - close(sep->se_fd); - sep->se_fd = -1; - if (!timingout) { - timingout = 1; - alarm(RETRYTIME); + { + 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(sep->se_fd, global_queuelen); - - FD_SET(sep->se_fd, &allsock); - nsock++; - if (sep->se_fd > maxsock) { - maxsock = sep->se_fd; - if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN) - bump_nofile(); + + 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 char *nextline(void) +static int reopen_config_file(void) { - char *cp; - FILE *fd = fconfig; - - if (fgets(line, LINE_SIZE, fd) == NULL) - return NULL; - cp = strchr(line, '\n'); - if (cp) - *cp = '\0'; - return line; + free(default_local_hostname); + default_local_hostname = xstrdup("*"); + if (parser != NULL) + config_close(parser); + parser = config_open(config_filename); + return (parser != NULL); } -static char *skip(char **cpp) /* int report; */ +static void close_config_file(void) { - char *cp = *cpp; - char *start; - -/* erp: */ - if (*cpp == NULL) { - /* if (report) */ - /* bb_error_msg("syntax error in inetd config file"); */ - return NULL; + if (parser) { + config_close(parser); + parser = NULL; } +} - again: - while (*cp == ' ' || *cp == '\t') - cp++; - if (*cp == '\0') { - int c; - - c = getc(fconfig); - ungetc(c, fconfig); - if (c == ' ' || c == '\t') { - cp = nextline(); - if (cp) - goto again; - } - *cpp = NULL; - /* goto erp; */ - return NULL; - } - start = cp; - while (*cp && *cp != ' ' && *cp != '\t') - cp++; - if (*cp != '\0') - *cp++ = '\0'; - /* if ((*cpp = cp) == NULL) */ - /* goto erp; */ - - *cpp = cp; - return start; +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) { - return xmalloc(sizeof(servtab_t)); + servtab_t *newtab = xzalloc(sizeof(servtab_t)); + newtab->se_fd = -1; /* paranoia */ + return newtab; } -static servtab_t *dupconfig(servtab_t *sep) +static servtab_t *dup_servtab(servtab_t *sep) { servtab_t *newtab; int argc; newtab = new_servtab(); - memset(newtab, 0, sizeof(servtab_t)); - newtab->se_service = xstrdup(sep->se_service); - newtab->se_socktype = sep->se_socktype; - newtab->se_family = sep->se_family; - newtab->se_proto = xstrdup(sep->se_proto); -#if ENABLE_FEATURE_INETD_RPC - newtab->se_rpcprog = sep->se_rpcprog; - newtab->se_rpcversl = sep->se_rpcversl; - newtab->se_rpcversh = sep->se_rpcversh; -#endif - newtab->se_wait = sep->se_wait; - newtab->se_user = xstrdup(sep->se_user); - newtab->se_group = xstrdup(sep->se_group); -#ifdef INETD_FEATURE_ENABLED - newtab->se_bi = sep->se_bi; -#endif - newtab->se_server = xstrdup(sep->se_server); - + *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(sep->se_argv[argc]); - newtab->se_max = sep->se_max; + 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; } -static servtab_t *getconfigent(void) +/* gcc generates much more code if this is inlined */ +static servtab_t *parse_one_line(void) { - servtab_t *sep; int argc; - char *cp, *arg; + char *token[6+MAXARGV]; + char *p, *arg; char *hostdelim; + servtab_t *sep; servtab_t *nsep; - servtab_t *psep; - + new: sep = new_servtab(); - - /* memset(sep, 0, sizeof *sep); */ more: - /* freeconfig(sep); */ - - while ((cp = nextline()) && *cp == '#') /* skip comment line */; - if (cp == NULL) { - /* free(sep); */ + argc = config_read(parser, token, 6+MAXARGV, 1, "# \t", PARSE_NORMAL); + if (!argc) { + free(sep); return NULL; } - memset((char *) sep, 0, sizeof *sep); - arg = skip(&cp); - if (arg == NULL) { - /* A blank line. */ - goto more; - } - - /* Check for a host name. */ + /* [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_hostaddr = xstrdup(arg); + sep->se_local_hostname = xstrdup(arg); arg = hostdelim + 1; - /* - * If the line is of the form `host:', then just change the - * default host for the following lines. - */ - if (*arg == '\0') { - arg = skip(&cp); - if (cp == NULL) { - free(defhost); - defhost = sep->se_hostaddr; - 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; + goto more; } } else - sep->se_hostaddr = xxstrdup(defhost); - - sep->se_service = xxstrdup(arg); - arg = skip(&cp); + 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); + free_servtab_strings(sep); + /* 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(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 @@ -718,68 +705,86 @@ static servtab_t *getconfigent(void) sep->se_socktype = SOCK_xxx[1 + index_in_strings( "stream""\0" "dgram""\0" "rdm""\0" "seqpacket""\0" "raw""\0" - , arg)]; + , token[1])]; } - sep->se_proto = xxstrdup(skip(&cp)); - - if (strcmp(sep->se_proto, "unix") == 0) { + /* {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; - if (sep->se_proto[strlen(sep->se_proto) - 1] == '6') + six = last_char_is(arg, '6'); + if (six) { #if ENABLE_FEATURE_IPV6 + *six = '\0'; sep->se_family = AF_INET6; #else - bb_error_msg("%s: IPV6 not supported", sep->se_proto); + bb_error_msg("%s: no support for IPv6", sep->se_proto); + goto parse_err; #endif - if (strncmp(sep->se_proto, "rpc/", 4) == 0) { + } + if (strncmp(arg, "rpc/", 4) == 0) { #if ENABLE_FEATURE_INETD_RPC - char *p, *ccp; - long l; - + unsigned n; + arg += 4; p = strchr(sep->se_service, '/'); - if (p == 0) { - bb_error_msg("%s: no rpc version", sep->se_service); - goto more; + if (p == NULL) { + bb_error_msg("no rpc version: '%s'", sep->se_service); + goto parse_err; } *p++ = '\0'; - l = strtol(p, &ccp, 0); - if (ccp == p || l < 0 || l > INT_MAX) { - badafterall: - bb_error_msg("%s/%s: bad rpc version", sep->se_service, p); - goto more; + n = bb_strtou(p, &p, 10); + if (n > INT_MAX) { + bad_ver_spec: + bb_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; } - sep->se_rpcversl = sep->se_rpcversh = l; - if (*ccp == '-') { - p = ccp + 1; - l = strtol(p, &ccp, 0); - if (ccp == p || l < 0 || l > INT_MAX || l < sep->se_rpcversl || *ccp) - goto badafterall; - sep->se_rpcversh = l; - } else if (*ccp != '\0') - goto badafterall; + if (*p != '\0') + goto bad_ver_spec; #else - bb_error_msg("%s: rpc services not supported", sep->se_service); + bb_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; } - arg = skip(&cp); - if (arg == NULL) - goto more; - { - char *s = strchr(arg, '.'); - if (s) { - *s++ = '\0'; - sep->se_max = xatoi(s); - } else - sep->se_max = toomany; + /* [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 = strcmp(arg, "wait") == 0; - /* if ((arg = skip(&cp, 1)) == NULL) */ - /* goto more; */ - sep->se_user = xxstrdup(skip(&cp)); + 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, ':'); @@ -788,899 +793,769 @@ static servtab_t *getconfigent(void) sep->se_group = xstrdup(arg); } - arg = skip(&cp); - sep->se_server = xxstrdup(arg); - if (strcmp(sep->se_server, "internal") == 0) { -#ifdef INETD_FEATURE_ENABLED - const struct builtin *bi; - - for (bi = builtins; bi->bi_service; bi++) - if (bi->bi_socktype == sep->se_socktype - && strcmp(bi->bi_service, sep->se_service) == 0) - break; - if (bi->bi_service == 0) { - bb_error_msg("internal service %s unknown", sep->se_service); - goto more; - } - sep->se_bi = bi; - sep->se_wait = 0; /* = bi->bi_wait; - always 0 */ -#else - bb_perror_msg("internal service %s unknown", sep->se_service); - goto more; -#endif + /* 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; } -#ifdef INETD_FEATURE_ENABLED - else - sep->se_bi = NULL; #endif argc = 0; - for (; cp; arg = skip(&cp)) { - if (argc < MAXARGV) - sep->se_argv[argc++] = xxstrdup(arg); + while ((arg = token[6+argc]) != NULL && argc < MAXARGV) + 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; - - /* - * 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. - */ - while ((hostdelim = strrchr(sep->se_hostaddr, ',')) != NULL) { - nsep = dupconfig(sep); - - /* - * NULL terminate the hostname field of the existing entry, - * and make a dup for the new entry. - */ - *hostdelim++ = '\0'; - nsep->se_hostaddr = xstrdup(hostdelim); +// bb_info_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; } - nsep = sep; - while (nsep != NULL) { - nsep->se_checked = 1; - if (nsep->se_family == AF_INET) { - if (LONE_CHAR(nsep->se_hostaddr, '*')) - nsep->se_ctrladdr_in.sin_addr.s_addr = INADDR_ANY; - else if (!inet_aton(nsep->se_hostaddr, &nsep->se_ctrladdr_in.sin_addr)) { - int i; - struct hostent *hp; - - hp = gethostbyname(nsep->se_hostaddr); - if (hp == NULL) { - bb_error_msg("%s: unknown host", nsep->se_hostaddr); - nsep->se_checked = 0; - goto skip; - } - if (hp->h_addrtype != AF_INET) { - bb_error_msg("%s: address isn't an Internet " - "address", nsep->se_hostaddr); - nsep->se_checked = 0; - goto skip; - } - i = 1; - memmove(&nsep->se_ctrladdr_in.sin_addr, - hp->h_addr_list[0], sizeof(struct in_addr)); - while (hp->h_addr_list[i] != NULL) { - psep = dupconfig(nsep); - psep->se_hostaddr = xxstrdup(nsep->se_hostaddr); - psep->se_checked = 1; - memmove(&psep->se_ctrladdr_in.sin_addr, - hp->h_addr_list[i], sizeof(struct in_addr)); - psep->se_ctrladdr_size = sizeof(psep->se_ctrladdr_in); - i++; - /* Prepend to list, don't want to look up */ - /* its hostname again. */ - psep->se_next = sep; - sep = psep; - } - } - } -/* XXX BUG?: is this skip: label supposed to remain? */ - skip: - nsep = nsep->se_next; - } - - /* - * Finally, free any entries which failed the gethostbyname - * check. - */ - psep = NULL; - nsep = sep; - while (nsep != NULL) { - servtab_t *tsep; - - if (nsep->se_checked == 0) { - tsep = nsep; - if (psep == NULL) { - sep = nsep->se_next; - nsep = sep; - } else { - nsep = nsep->se_next; - psep->se_next = nsep; - } - freeconfig(tsep); - } else { - nsep->se_checked = 0; - psep = nsep; - nsep = nsep->se_next; - } - } + /* was doing it here: */ + /* DNS resolution, create copies for each IP address */ + /* IPv6-ization destroyed it :( */ return sep; } -#define Block_Using_Signals(m) do { \ - sigemptyset(&m); \ - sigaddset(&m, SIGCHLD); \ - sigaddset(&m, SIGHUP); \ - sigaddset(&m, SIGALRM); \ - sigprocmask(SIG_BLOCK, &m, NULL); \ -} while (0) - -static servtab_t *enter(servtab_t *cp) +static servtab_t *insert_in_servlist(servtab_t *cp) { servtab_t *sep; sigset_t omask; sep = new_servtab(); - *sep = *cp; + *sep = *cp; /* struct copy */ sep->se_fd = -1; #if ENABLE_FEATURE_INETD_RPC sep->se_rpcprog = -1; #endif - Block_Using_Signals(omask); - sep->se_next = servtab; - servtab = sep; - sigprocmask(SIG_UNBLOCK, &omask, NULL); + block_CHLD_HUP_ALRM(&omask); + sep->se_next = serv_list; + serv_list = sep; + restore_sigmask(&omask); return sep; } -static int matchconf(servtab_t *old, servtab_t *new) +static int same_serv_addr_proto(servtab_t *old, servtab_t *new) { - if (strcmp(old->se_service, new->se_service) != 0) + if (strcmp(old->se_local_hostname, new->se_local_hostname) != 0) return 0; - - if (strcmp(old->se_hostaddr, new->se_hostaddr) != 0) + if (strcmp(old->se_service, new->se_service) != 0) return 0; - if (strcmp(old->se_proto, new->se_proto) != 0) return 0; - - /* - * If the new servtab is bound to a specific address, check that the - * old servtab is bound to the same entry. If the new service is not - * bound to a specific address then the check of se_hostaddr above - * is sufficient. - */ - - if (old->se_family == AF_INET && new->se_family == AF_INET && - memcmp(&old->se_ctrladdr_in.sin_addr, - &new->se_ctrladdr_in.sin_addr, - sizeof(new->se_ctrladdr_in.sin_addr)) != 0) - return 0; - -#if ENABLE_FEATURE_IPV6 - if (old->se_family == AF_INET6 && new->se_family == AF_INET6 && - memcmp(&old->se_ctrladdr_in6.sin6_addr, - &new->se_ctrladdr_in6.sin6_addr, - sizeof(new->se_ctrladdr_in6.sin6_addr)) != 0) - return 0; -#endif return 1; } -static void config(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; - size_t n; - char protoname[10]; + unsigned n; + uint16_t port; + int save_errno = errno; - if (!setconfig()) { - bb_simple_perror_msg(config_filename); - return; - } - for (sep = servtab; sep; sep = sep->se_next) + if (!reopen_config_file()) + goto ret; + for (sep = serv_list; sep; sep = sep->se_next) sep->se_checked = 0; - cp = getconfigent(); - while (cp != NULL) { - for (sep = servtab; sep; sep = sep->se_next) - if (matchconf(sep, cp)) - break; - if (sep != 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; -#define SWAP(type, a, b) do {type c=(type)a; a=(type)b; b=(type)c;} while (0) - - Block_Using_Signals(omask); - /* - * sep->se_wait may be holding the pid of a daemon - * that we're waiting for. If so, don't overwrite - * it unless the config file explicitly says don't - * wait. - */ - if ( -#ifdef INETD_FEATURE_ENABLED - cp->se_bi == 0 && -#endif - (sep->se_wait == 1 || cp->se_wait == 0)) - sep->se_wait = cp->se_wait; - SWAP(int, cp->se_max, sep->se_max); - SWAP(char *, sep->se_user, cp->se_user); - SWAP(char *, sep->se_group, cp->se_group); - SWAP(char *, sep->se_server, cp->se_server); - for (i = 0; i < MAXARGV; i++) - SWAP(char *, sep->se_argv[i], cp->se_argv[i]); -#undef SWAP - + block_CHLD_HUP_ALRM(&omask); #if ENABLE_FEATURE_INETD_RPC - if (isrpcservice(sep)) + if (is_rpc_service(sep)) unregister_rpc(sep); - sep->se_rpcversl = cp->se_rpcversl; - sep->se_rpcversh = cp->se_rpcversh; + sep->se_rpcver_lo = cp->se_rpcver_lo; + sep->se_rpcver_hi = cp->se_rpcver_hi; #endif - sigprocmask(SIG_UNBLOCK, &omask, NULL); - freeconfig(cp); - } else { - sep = enter(cp); + 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: - if (sep->se_fd != -1) - break; - (void) unlink(sep->se_service); - n = strlen(sep->se_service); - if (n > sizeof sep->se_ctrladdr_un.sun_path - 1) - n = sizeof sep->se_ctrladdr_un.sun_path - 1; - safe_strncpy(sep->se_ctrladdr_un.sun_path, sep->se_service, n + 1); - sep->se_ctrladdr_un.sun_family = AF_UNIX; - sep->se_ctrladdr_size = n + sizeof sep->se_ctrladdr_un.sun_family; - setup(sep); + 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; - case AF_INET: - sep->se_ctrladdr_in.sin_family = AF_INET; - /* se_ctrladdr_in was set in getconfigent */ - sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in; + default: /* case AF_INET, case AF_INET6 */ + n = bb_strtou(sep->se_service, NULL, 10); #if ENABLE_FEATURE_INETD_RPC - if (isrpcservice(sep)) { - struct rpcent *rp; - // FIXME: atoi_or_else(str, 0) would be handy here - sep->se_rpcprog = atoi(sep->se_service); - if (sep->se_rpcprog == 0) { - rp = getrpcbyname(sep->se_service); - if (rp == 0) { + 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 serv_unknown; + goto next_cp; } sep->se_rpcprog = rp->r_number; } if (sep->se_fd == -1) - setup(sep); + prepare_socket_fd(sep); if (sep->se_fd != -1) register_rpc(sep); - } else -#endif - { - uint16_t port = htons(atoi(sep->se_service)); - // FIXME: atoi_or_else(str, 0) would be handy here - if (!port) { - /*XXX*/ strncpy(protoname, sep->se_proto, sizeof(protoname)); - if (isdigit(protoname[strlen(protoname) - 1])) - protoname[strlen(protoname) - 1] = '\0'; - sp = getservbyname(sep->se_service, protoname); - if (sp == 0) { - bb_error_msg("%s/%s: unknown service", - sep->se_service, sep->se_proto); - goto serv_unknown; - } - port = sp->s_port; - } - if (port != sep->se_ctrladdr_in.sin_port) { - sep->se_ctrladdr_in.sin_port = port; - if (sep->se_fd != -1) { - FD_CLR(sep->se_fd, &allsock); - nsock--; - (void) close(sep->se_fd); - } - sep->se_fd = -1; - } - if (sep->se_fd == -1) - setup(sep); + goto next_cp; } - break; -#if ENABLE_FEATURE_IPV6 - case AF_INET6: - sep->se_ctrladdr_in6.sin6_family = AF_INET6; - /* se_ctrladdr_in was set in getconfigent */ - sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in6; - -#if ENABLE_FEATURE_INETD_RPC - if (isrpcservice(sep)) { - struct rpcent *rp; - - sep->se_rpcprog = atoi(sep->se_service); - if (sep->se_rpcprog == 0) { - rp = getrpcbyname(sep->se_service); - if (rp == 0) { - bb_error_msg("%s: unknown rpc service", sep->se_service); - goto serv_unknown; - } - sep->se_rpcprog = rp->r_number; - } - if (sep->se_fd == -1) - setup(sep); - if (sep->se_fd != -1) - register_rpc(sep); - } else #endif - { - uint16_t port = htons(atoi(sep->se_service)); - - if (!port) { - /*XXX*/ strncpy(protoname, sep->se_proto, sizeof(protoname)); - if (isdigit(protoname[strlen(protoname) - 1])) - protoname[strlen(protoname) - 1] = '\0'; - sp = getservbyname(sep->se_service, protoname); - if (sp == 0) { - bb_error_msg("%s/%s: unknown service", - sep->se_service, sep->se_proto); - goto serv_unknown; - } - port = sp->s_port; + /* 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; } - if (port != sep->se_ctrladdr_in6.sin6_port) { - sep->se_ctrladdr_in6.sin6_port = port; - if (sep->se_fd != -1) { - FD_CLR(sep->se_fd, &allsock); - nsock--; - (void) close(sep->se_fd); - } - sep->se_fd = -1; + 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; } - if (sep->se_fd == -1) - setup(sep); } break; -#endif /* FEATURE_IPV6 */ - } - serv_unknown: - if (cp->se_next != NULL) { - servtab_t *tmp = cp; - - cp = cp->se_next; - free(tmp); + } /* 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(cp); - cp = getconfigent(); + free(lsa); } - } - endconfig(); - /* - * Purge anything not looked at above. - */ - Block_Using_Signals(omask); - sepp = &servtab; - while ((sep = *sepp)) { + 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; - if (sep->se_fd != -1) { - FD_CLR(sep->se_fd, &allsock); - nsock--; - (void) close(sep->se_fd); - } + remove_fd_from_set(sep->se_fd); + maybe_close(sep->se_fd); #if ENABLE_FEATURE_INETD_RPC - if (isrpcservice(sep)) + if (is_rpc_service(sep)) unregister_rpc(sep); #endif if (sep->se_family == AF_UNIX) - (void) unlink(sep->se_service); - freeconfig(sep); + unlink(sep->se_service); + free_servtab_strings(sep); free(sep); } - sigprocmask(SIG_UNBLOCK, &omask, NULL); + restore_sigmask(&omask); + ret: + errno = save_errno; } - -static void reapchild(int sig ATTRIBUTE_UNUSED) +static void reap_child(int sig UNUSED_PARAM) { pid_t pid; - int save_errno = errno, status; + int status; servtab_t *sep; + int save_errno = errno; for (;;) { - pid = wait3(&status, WNOHANG, NULL); + pid = wait_any_nohang(&status); if (pid <= 0) break; - for (sep = servtab; sep; sep = sep->se_next) - if (sep->se_wait == pid) { - if (WIFEXITED(status) && WEXITSTATUS(status)) - bb_error_msg("%s: exit status 0x%x", - sep->se_server, WEXITSTATUS(status)); - else if (WIFSIGNALED(status)) - bb_error_msg("%s: exit signal 0x%x", - sep->se_server, WTERMSIG(status)); - sep->se_wait = 1; - FD_SET(sep->se_fd, &allsock); - nsock++; - } + 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(int sig ATTRIBUTE_UNUSED) +static void retry_network_setup(int sig UNUSED_PARAM) { + int save_errno = errno; servtab_t *sep; - timingout = 0; - for (sep = servtab; sep; sep = sep->se_next) { + alarm_armed = 0; + for (sep = serv_list; sep; sep = sep->se_next) { if (sep->se_fd == -1) { - switch (sep->se_family) { - case AF_UNIX: - case AF_INET: -#if ENABLE_FEATURE_IPV6 - case AF_INET6: -#endif - setup(sep); + prepare_socket_fd(sep); #if ENABLE_FEATURE_INETD_RPC - if (sep->se_fd != -1 && isrpcservice(sep)) - register_rpc(sep); + if (sep->se_fd != -1 && is_rpc_service(sep)) + register_rpc(sep); #endif - break; - } } } + errno = save_errno; } -static void goaway(int sig ATTRIBUTE_UNUSED) +static void clean_up_and_exit(int sig UNUSED_PARAM) { servtab_t *sep; /* XXX signal race walking sep list */ - for (sep = servtab; sep; sep = sep->se_next) { + for (sep = serv_list; sep; sep = sep->se_next) { if (sep->se_fd == -1) continue; switch (sep->se_family) { case AF_UNIX: - (void) unlink(sep->se_service); + unlink(sep->se_service); break; - case AF_INET: -#if ENABLE_FEATURE_IPV6 - case AF_INET6: -#endif + default: /* case AF_INET, AF_INET6 */ #if ENABLE_FEATURE_INETD_RPC - if (sep->se_wait == 1 && isrpcservice(sep)) + if (sep->se_wait == 1 && is_rpc_service(sep)) unregister_rpc(sep); /* XXX signal race */ #endif break; } - (void) close(sep->se_fd); + 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); } - -#ifdef INETD_SETPROCTITLE - -static void -inetd_setproctitle(char *a, int s) -{ - socklen_t size; - char *cp; - struct sockaddr_in prt_sin; - char buf[80]; - - cp = Argv[0]; - size = sizeof(prt_sin); - (void) snprintf(buf, sizeof buf, "-%s", a); - if (getpeername(s, (struct sockaddr *) &prt_sin, &size) == 0) { - char *sa = inet_ntoa(prt_sin.sin_addr); - - buf[sizeof(buf) - 1 - strlen(sa) - 3] = '\0'; - strcat(buf, " ["); - strcat(buf, sa); - strcat(buf, "]"); - } - strncpy(cp, buf, LastArg - cp); - cp += strlen(cp); - while (cp < LastArg) - *cp++ = ' '; -} -#endif - - 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) { - servtab_t *sep; + struct sigaction sa, saved_pipe_handler; + servtab_t *sep, *sep2; struct passwd *pwd; - struct group *grp = NULL; - int tmpint; - struct sigaction sa, sapipe; + struct group *grp = grp; /* for compiler */ int opt; pid_t pid; - char buf[50]; - char *stoomany; - sigset_t omask, wait_mask; -#ifdef INETD_SETPROCTITLE - char **envp; -#endif + sigset_t omask; INIT_G(); -#ifdef INETD_SETPROCTITLE - envp = environ; - Argv = argv; - if (envp == 0 || *envp == 0) - envp = argv; - while (*envp) - envp++; - LastArg = envp[-1] + strlen(envp[-1]); -#endif - - uid = getuid(); - if (uid != 0) + real_uid = getuid(); + if (real_uid != 0) /* run by non-root user */ config_filename = NULL; - opt = getopt32(argv, "R:f", &stoomany); - if (opt & 1) - toomany = xatoi_u(stoomany); + opt_complementary = "R+:q+"; /* -q N, -R N */ + opt = getopt32(argv, "R:feq:", &max_concurrency, &global_queuelen); argv += optind; - argc -= optind; - if (argc) + //argc -= optind; + if (argv[0]) config_filename = argv[0]; if (config_filename == NULL) - bb_error_msg_and_die("non-root must specify a config file"); - + bb_error_msg_and_die("non-root must specify config file"); if (!(opt & 2)) bb_daemonize_or_rexec(0, argv - optind); else bb_sanitize_stdio(); - openlog(applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON); - logmode = LOGMODE_SYSLOG; + 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 (uid == 0) { - /* If run by hand, ensure groups vector gets trashed */ + if (real_uid == 0) { + /* run by root, ensure groups vector gets trashed */ gid_t gid = getgid(); setgroups(1, &gid); } - write_pidfile(_PATH_INETDPID); + write_pidfile(CONFIG_PID_FILE_PATH "/inetd.pid"); - if (getrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) { - bb_perror_msg("getrlimit"); - } else { - rlim_ofile_cur = rlim_ofile.rlim_cur; - if (rlim_ofile_cur == RLIM_INFINITY) /* ! */ - rlim_ofile_cur = OPEN_MAX; - } + /* 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((char *) &sa, 0, sizeof(sa)); - sigemptyset(&sa.sa_mask); + 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); - sa.sa_handler = retry; - sigaction(SIGALRM, &sa, NULL); - config(SIGHUP); - sa.sa_handler = config; - sigaction(SIGHUP, &sa, NULL); - sa.sa_handler = reapchild; - sigaction(SIGCHLD, &sa, NULL); - sa.sa_handler = goaway; - sigaction(SIGTERM, &sa, NULL); - sa.sa_handler = goaway; - sigaction(SIGINT, &sa, NULL); +//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, &sapipe); - memset(&wait_mask, 0, sizeof(wait_mask)); - { - /* space for daemons to overwrite environment for ps */ -#define DUMMYSIZE 100 - char dummy[DUMMYSIZE]; + sigaction(SIGPIPE, &sa, &saved_pipe_handler); - (void) memset(dummy, 'x', DUMMYSIZE - 1); - dummy[DUMMYSIZE - 1] = '\0'; - - (void) setenv("inetd_dummy", dummy, 1); - } + reread_config_file(SIGHUP); /* load config from file */ for (;;) { - int n, ctrl = -1; + int ready_fd_cnt; + int ctrl, accepted_fd, new_udp_fd; fd_set readable; - if (nsock == 0) { - Block_Using_Signals(omask); - while (nsock == 0) - sigsuspend(&wait_mask); - sigprocmask(SIG_UNBLOCK, &omask, NULL); - } + if (maxsock < 0) + recalculate_maxsock(); - readable = allsock; - n = select(maxsock + 1, &readable, NULL, NULL, NULL); - if (n <= 0) { - if (n < 0 && errno != EINTR) { + 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_perror_msg("select"); sleep(1); } continue; } + dbg("ready_fd_cnt:%d\n", ready_fd_cnt); - for (sep = servtab; n && sep; sep = sep->se_next) { + for (sep = serv_list; ready_fd_cnt && sep; sep = sep->se_next) { if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable)) continue; - n--; - if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { - ctrl = accept(sep->se_fd, NULL, NULL); - if (ctrl < 0) { - if (errno == EINTR) + 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; - bb_perror_msg("accept (for %s)", sep->se_service); - continue; + } } - if (sep->se_family == AF_INET && sep->se_socktype == SOCK_STREAM) { - struct sockaddr_in peer; - socklen_t plen = sizeof(peer); - - if (getpeername(ctrl, (struct sockaddr *) &peer, &plen) < 0) { - bb_error_msg("cannot getpeername"); - close(ctrl); + /* "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; } - if (ntohs(peer.sin_port) == 20) { - /* XXX ftp bounce */ - close(ctrl); - 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"); } - } else - ctrl = sep->se_fd; + } - Block_Using_Signals(omask); + block_CHLD_HUP_ALRM(&omask); pid = 0; -#ifdef INETD_FEATURE_ENABLED - if (sep->se_bi == 0 || sep->se_bi->bi_fork) +#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_count++ == 0) - (void) gettimeofday(&sep->se_time, NULL); - else if (toomany > 0 && sep->se_count >= sep->se_max) { - struct timeval now; - - (void) gettimeofday(&now, NULL); - if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL) { - sep->se_time = now; - sep->se_count = 1; - } else { - if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) - close(ctrl); - if (sep->se_family == AF_INET && - ntohs(sep->se_ctrladdr_in.sin_port) >= IPPORT_RESERVED) { - /* - * Cannot close it -- there are - * thieves on the system. - * Simply ignore the connection. - */ - --sep->se_count; - continue; + 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 */ } - bb_error_msg("%s/%s server failing (looping), service terminated", - sep->se_service, sep->se_proto); - if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) - close(ctrl); - FD_CLR(sep->se_fd, &allsock); - (void) close(sep->se_fd); - sep->se_fd = -1; sep->se_count = 0; - nsock--; - sigprocmask(SIG_UNBLOCK, &omask, NULL); - if (!timingout) { - timingout = 1; - alarm(RETRYTIME); - } - continue; } } - pid = fork(); + /* 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_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) { - bb_perror_msg("fork"); - if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) - close(ctrl); - sigprocmask(SIG_UNBLOCK, &omask, NULL); - sleep(1); - continue; + /* 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 */ } - if (pid && sep->se_wait) { - sep->se_wait = pid; - FD_CLR(sep->se_fd, &allsock); - nsock--; + + /* 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 */ } - sigprocmask(SIG_UNBLOCK, &omask, NULL); - if (pid == 0) { -#ifdef INETD_FEATURE_ENABLED - if (sep->se_bi) { - (*sep->se_bi->bi_fn)(ctrl, sep); - } else #endif - { - pwd = getpwnam(sep->se_user); - if (pwd == NULL) { - bb_error_msg("getpwnam: %s: no such user", sep->se_user); - goto do_exit1; - } - if (setsid() < 0) - bb_perror_msg("%s: setsid", sep->se_service); - if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) { - bb_error_msg("getgrnam: %s: no such group", sep->se_group); - goto do_exit1; - } - if (uid != 0) { - /* a user running private inetd */ - if (uid != pwd->pw_uid) - _exit(1); - } else if (pwd->pw_uid) { - if (sep->se_group) - pwd->pw_gid = grp->gr_gid; - xsetgid((gid_t) pwd->pw_gid); - initgroups(pwd->pw_name, pwd->pw_gid); - xsetuid((uid_t) pwd->pw_uid); - } else if (sep->se_group) { - xsetgid(grp->gr_gid); - setgroups(1, &grp->gr_gid); - } - dup2(ctrl, 0); - if (ctrl) close(ctrl); - dup2(0, 1); - dup2(0, 2); - if (rlim_ofile.rlim_cur != rlim_ofile_cur) - if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) - bb_perror_msg("setrlimit"); - closelog(); - for (tmpint = rlim_ofile_cur - 1; --tmpint > 2;) - (void) close(tmpint); - sigaction(SIGPIPE, &sapipe, NULL); - execv(sep->se_server, sep->se_argv); - bb_perror_msg("execv %s", sep->se_server); - do_exit1: - if (sep->se_socktype != SOCK_STREAM) - recv(0, buf, sizeof(buf), 0); - _exit(1); - } + /* 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 (!sep->se_wait && sep->se_socktype == SOCK_STREAM) - close(ctrl); + if (real_uid != 0 && real_uid != pwd->pw_uid) { + /* a user running private inetd */ + bb_error_msg("non-root must run services as himself"); + goto do_exit1; + } + if (pwd->pw_uid != 0) { + 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_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 !BB_MMU +static const char *const cat_args[] = { "cat", NULL }; +#endif + /* * Internet services provided internally by inetd: */ -#define BUFSIZE 4096 - -#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ - ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN || \ - ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME -static int dg_badinput(struct sockaddr_in *dg_sin) -{ - if (ntohs(dg_sin->sin_port) < IPPORT_RESERVED) - return 1; - if (dg_sin->sin_addr.s_addr == htonl(INADDR_BROADCAST)) - return 1; - /* XXX compare against broadcast addresses in SIOCGIFCONF list? */ - return 0; -} -#endif - #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO -/* Echo service -- echo data back */ +/* Echo service -- echo data back. */ /* ARGSUSED */ -static void -echo_stream(int s, servtab_t *sep) +static void FAST_FUNC echo_stream(int s, servtab_t *sep UNUSED_PARAM) { - char buffer[BUFSIZE]; - int i; - - inetd_setproctitle(sep->se_service, s); +#if BB_MMU while (1) { - i = read(s, buffer, sizeof(buffer)); - if (i <= 0) break; - /* FIXME: this isnt correct - safe_write()? */ - if (write(s, buffer, i) <= 0) break; + ssize_t sz = safe_read(s, line, LINE_SIZE); + if (sz <= 0) + break; + xwrite(s, line, sz); } - exit(0); +#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 } - -/* Echo service -- echo data back */ -/* ARGSUSED */ -static void -echo_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC echo_dg(int s, servtab_t *sep) { - char buffer[BUFSIZE]; - int i; - socklen_t size; - /* struct sockaddr_storage ss; */ - struct sockaddr sa; - - size = sizeof(sa); - i = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size); - if (i < 0) - return; - if (dg_badinput((struct sockaddr_in *) &sa)) - return; - (void) sendto(s, buffer, i, 0, &sa, sizeof(sa)); + 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 */ +/* Discard service -- ignore data. */ /* ARGSUSED */ -static void -discard_stream(int s, servtab_t *sep) +static void FAST_FUNC discard_stream(int s, servtab_t *sep UNUSED_PARAM) { - char buffer[BUFSIZE]; - - inetd_setproctitle(sep->se_service, s); - while (1) { - if (safe_read(s, buffer, sizeof(buffer)) <= 0) - exit(0); - } +#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 } - -/* Discard service -- ignore data */ /* 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) { - char buffer[BUFSIZE]; - - (void) read(s, buffer, sizeof(buffer)); + /* 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 -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 */ +/* 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]; - inetd_setproctitle(sep->se_service, s); - - if (!endring) { - initring(); + if (!end_ring) { + init_ring(); rs = ring; } @@ -1688,55 +1563,48 @@ 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; - if (write(s, text, sizeof(text)) != sizeof(text)) - break; + xwrite(s, text, sizeof(text)); } - exit(0); } - -/* Character generator */ /* ARGSUSED */ -static void -chargen_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC chargen_dg(int s, servtab_t *sep) { - /* struct sockaddr_storage ss; */ - struct sockaddr sa; int len; char text[LINESIZ + 2]; - socklen_t size; + len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len); - if (!endring) { - initring(); - ringpos = ring; - } - - size = sizeof(sa); - if (recvfrom(s, text, sizeof(text), 0, &sa, &size) < 0) - return; - if (dg_badinput((struct sockaddr_in *) &sa)) + /* 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; - len = endring - ringpos; + if (!end_ring) { + init_ring(); + ring_pos = ring; + } + + 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'; - (void) sendto(s, text, sizeof(text), 0, &sa, sizeof(sa)); + sendto(s, text, sizeof(text), 0, &lsa->u.sa, lsa->len); } #endif /* FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN */ @@ -1749,48 +1617,32 @@ chargen_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) * 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; - if (gettimeofday(&tv, NULL) < 0) { - fprintf(stderr, "Unable to get time of day\n"); - return 0L; - } - return htonl((unsigned) tv.tv_sec + 2208988800UL); + gettimeofday(&tv, NULL); + return htonl((uint32_t)(tv.tv_sec + 2208988800)); } - /* 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) { - unsigned result; + uint32_t result; result = machtime(); - (void) write(s, (char *) &result, sizeof(result)); + full_write(s, &result, sizeof(result)); } - -/* ARGSUSED */ -static void -machtime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC machtime_dg(int s, servtab_t *sep) { - unsigned result; - /* struct sockaddr_storage ss; */ - struct sockaddr sa; - struct sockaddr_in *dg_sin; - socklen_t size; + uint32_t result; + len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len); - size = sizeof(sa); - if (recvfrom(s, (char *) &result, sizeof(result), 0, &sa, &size) < 0) - return; - /* if (dg_badinput((struct sockaddr *)&ss)) */ - dg_sin = (struct sockaddr_in *) &sa; - if (dg_sin->sin_addr.s_addr == htonl(INADDR_BROADCAST) || - ntohs(dg_sin->sin_port) < IPPORT_RESERVED / 2) + lsa->len = sep->se_lsa->len; + if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0) return; + result = machtime(); - (void) sendto(s, (char *) &result, sizeof(result), 0, &sa, sizeof(sa)); + sendto(s, &result, sizeof(result), 0, &lsa->u.sa, lsa->len); } #endif /* FEATURE_INETD_SUPPORT_BUILTIN_TIME */ @@ -1798,37 +1650,24 @@ machtime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) #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) { - char buffer[32]; time_t t; t = time(NULL); - -// fdprintf instead? - (void) sprintf(buffer, "%.24s\r\n", ctime(&t)); - (void) write(s, buffer, strlen(buffer)); + fdprintf(s, "%.24s\r\n", ctime(&t)); } - -/* Return human-readable time of day */ -/* ARGSUSED */ -void -daytime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +static void FAST_FUNC daytime_dg(int s, servtab_t *sep) { - char buffer[256]; time_t t; - /* struct sockaddr_storage ss; */ - struct sockaddr sa; - socklen_t size; + len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len); - t = time(NULL); - - size = sizeof(sa); - if (recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size) < 0) - return; - if (dg_badinput((struct sockaddr_in *) &sa)) + lsa->len = sep->se_lsa->len; + if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0) return; - (void) sprintf(buffer, "%.24s\r\n", ctime(&t)); - (void) sendto(s, buffer, strlen(buffer), 0, &sa, sizeof(sa)); + + 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 */