diff options
Diffstat (limited to 'networking/inetd.c')
-rw-r--r-- | networking/inetd.c | 258 |
1 files changed, 151 insertions, 107 deletions
diff --git a/networking/inetd.c b/networking/inetd.c index 8a4c9fbf6..463c7cfcf 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 <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. @@ -38,21 +39,17 @@ /* Inetd - Internet super-server * - * This program invokes all internet services as needed. - * connection-oriented services are invoked each time a + * This program invokes configured services when a connection + * from a peer is established or a datagram arrives. + * Connection-oriented services are invoked each time a * connection is made, by creating a process. This process * is passed the connection as file descriptor 0 and is - * expected to do a getpeername to find out the source host + * expected to do a getpeername to find out peer's host * and port. - * * Datagram oriented services are invoked when a datagram * arrives; a process is created and passed a pending message - * on file descriptor 0. Datagram servers may either connect - * to their peer, freeing up the original socket for inetd - * to receive further messages on, or "take over the socket", - * processing all arriving datagrams and, eventually, timing - * out. The first type of server is said to be "multi-threaded"; - * the second type of server "single-threaded". + * on file descriptor 0. peer's address can be obtained + * using recvfrom. * * Inetd uses a configuration file which is read at startup * and, possibly, at some later time in response to a hangup signal. @@ -60,28 +57,28 @@ * order shown below. Continuation lines for an entry must begin with * a space or tab. All fields must be present in each entry. * - * service name must be in /etc/services - * socket type stream/dgram/raw/rdm/seqpacket + * service_name must be in /etc/services + * socket_type stream/dgram/raw/rdm/seqpacket * protocol must be in /etc/protocols * (usually "tcp" or "udp") * wait/nowait[.max] single-threaded/multi-threaded, max # * user[.group] or user[:group] user/group to run daemon as - * server program full path name - * server program arguments maximum of MAXARGS (20) + * server_program full path name + * server_program_arguments maximum of MAXARGS (20) * * For RPC services - * service name/version must be in /etc/rpc - * socket type stream/dgram/raw/rdm/seqpacket + * service_name/version must be in /etc/rpc + * socket_type stream/dgram/raw/rdm/seqpacket * rpc/protocol "rpc/tcp" etc * wait/nowait[.max] single-threaded/multi-threaded * user[.group] or user[:group] user to run daemon as - * server program full path name - * server program arguments maximum of MAXARGS (20) + * server_program full path name + * server_program_arguments maximum of MAXARGS (20) * * For non-RPC services, the "service name" can be of the form * hostaddress:servicename, in which case the hostaddress is used * as the host portion of the address to listen on. If hostaddress - * consists of a single `*' character, INADDR_ANY is used. + * consists of a single '*' character, INADDR_ANY is used. * * A line can also consist of just * hostaddress: @@ -102,7 +99,7 @@ * one line for any given RPC service, even if the host-address * specifiers are different. * - * Comment lines are indicated by a `#' in column 1. + * Comment lines are indicated by a '#' in column 1. */ /* inetd rules for passing file descriptors to children @@ -133,6 +130,8 @@ * 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. + * + * In short: "stream" can be "wait" or "nowait"; "dgram" must be "wait". */ /* Here's the scoop concerning the user[:group] feature: @@ -152,26 +151,27 @@ #include <syslog.h> #include <sys/un.h> + #include "libbb.h" +#if ENABLE_FEATURE_INETD_RPC +#include <rpc/rpc.h> +#include <rpc/pmap_clnt.h> +#endif + #if !BB_MMU -/* stream versions of these builtins are forking, +/* stream version of chargen is forking but not execing, * can't do that (easily) on NOMMU */ -#undef ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD -#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD 0 #undef ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN #define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN 0 #endif -#if ENABLE_FEATURE_INETD_RPC -#include <rpc/rpc.h> -#include <rpc/pmap_clnt.h> -#endif - #define _PATH_INETDPID "/var/run/inetd.pid" -#define CNT_INTERVAL 60 /* servers in CNT_INTERVAL sec. */ -#define RETRYTIME (60*10) /* retry after bind or server fail */ +#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 @@ -209,20 +209,21 @@ typedef struct servtab_t { #else #define is_rpc_service(sep) 0 #endif - pid_t se_wait; /* 0:"nowait", 1:"wait" */ + pid_t se_wait; /* 0:"nowait", 1:"wait", >1:"wait" */ + /* and waiting for this pid */ socktype_t se_socktype; /* SOCK_STREAM/DGRAM/RDM/... */ family_t se_family; /* AF_UNIX/INET[6] */ - smallint se_proto_no; /* almost "getprotobyname(se_proto)" */ + /* se_proto_no is used by RPC code only... hmm */ + smallint se_proto_no; /* IPPROTO_TCP/UDP, n/a for AF_UNIX */ smallint se_checked; /* looked at during merge */ + unsigned se_max; /* allowed instances per minute */ + unsigned se_count; /* number started since se_time */ + unsigned se_time; /* whem we started counting */ char *se_user; /* user name to run as */ char *se_group; /* group name to run as, can be NULL */ #ifdef INETD_BUILTINS_ENABLED const struct builtin *se_builtin; /* if built-in, description */ #endif -// TODO: wrong algorithm!!! - unsigned se_max; /* max # of instances of this service */ - unsigned se_count; /* number started since se_time */ - unsigned se_time; /* start of se_count */ struct servtab_t *se_next; len_and_sockaddr *se_lsa; char *se_program; /* server program */ @@ -231,60 +232,54 @@ typedef struct servtab_t { } servtab_t; #ifdef INETD_BUILTINS_ENABLED - /* Echo received data */ +/* Echo received data */ #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO static void echo_stream(int, servtab_t *); static void echo_dg(int, servtab_t *); #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 *); #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 *); #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 *); #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 *); #endif struct builtin { - const char *bi_service; /* internally provided service name */ - uint8_t bi_fork; /* 1 if stream fn should run in child */ - /* All builtins are "nowait" */ - /* uint8_t bi_wait; */ /* 1 if should wait for child */ + /* 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 *); void (*bi_dgram_fn)(int, servtab_t *); }; static const struct builtin builtins[] = { #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO - /* Echo received data */ { "echo", 1, echo_stream, echo_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD - /* Internet /dev/null */ { "discard", 1, discard_stream, discard_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN - /* Familiar character generator */ { "chargen", 1, chargen_stream, chargen_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME - /* Return 32 bit time since 1900 */ { "time", 0, machtime_stream, machtime_dg }, #endif #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME - /* Return human-readable time */ { "daytime", 0, daytime_stream, daytime_dg }, #endif }; @@ -305,8 +300,8 @@ struct globals { FILE *fconfig; 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; @@ -333,8 +328,8 @@ struct BUG_G_too_big { #define default_local_hostname (G.default_local_hostname) #define first_ps_byte (G.first_ps_byte ) #define last_ps_byte (G.last_ps_byte ) -#define endring (G.endring ) -#define ringpos (G.ringpos ) +#define end_ring (G.end_ring ) +#define ring_pos (G.ring_pos ) #define ring (G.ring ) #define allsock (G.allsock ) #define line (G.line ) @@ -357,6 +352,8 @@ static len_and_sockaddr *xzalloc_lsa(int family) int sz; sz = sizeof(struct sockaddr_in); + if (family == AF_UNIX) + sz = sizeof(struct sockaddr_un); #if ENABLE_FEATURE_IPV6 if (family == AF_INET6) sz = sizeof(struct sockaddr_in6); @@ -367,7 +364,6 @@ static len_and_sockaddr *xzalloc_lsa(int family) return lsa; } - static void rearm_alarm(void) { if (!alarm_armed) { @@ -660,10 +656,8 @@ static NOINLINE servtab_t *parse_one_line(void) } arg = next_word(&cp); - if (arg == NULL) { - /* A blank line. */ + if (arg == NULL) /* a blank line. */ goto more; - } /* [host:]service socktype proto wait user[:group] prog [args] */ /* Check for "host:...." line */ @@ -764,9 +758,9 @@ static NOINLINE servtab_t *parse_one_line(void) } /* we don't really need getprotobyname()! */ if (strcmp(arg, "tcp") == 0) - sep->se_proto_no = 6; + sep->se_proto_no = IPPROTO_TCP; /* = 6 */ if (strcmp(arg, "udp") == 0) - sep->se_proto_no = 17; + sep->se_proto_no = IPPROTO_UDP; /* = 17 */ if (six) *six = '6'; if (!sep->se_proto_no) /* not tcp/udp?? */ @@ -785,7 +779,11 @@ static NOINLINE servtab_t *parse_one_line(void) if (errno) goto parse_err; } - sep->se_wait = (strcmp(arg, "wait") == 0); + sep->se_wait = (arg[0] != 'n' || arg[1] != 'o'); + if (!sep->se_wait) /* "no" seen */ + arg += 2; + if (strcmp(arg, "wait") != 0) + goto parse_err; /* user[:group] prog [args] */ sep->se_user = xstrdup(next_word(&cp)); @@ -804,48 +802,56 @@ static NOINLINE servtab_t *parse_one_line(void) if (sep->se_program == NULL) goto parse_err; #ifdef INETD_BUILTINS_ENABLED - /* sep->se_builtin = NULL; - done by new_servtab() */ if (strcmp(sep->se_program, "internal") == 0 + && strlen(sep->se_service) <= 7 && (sep->se_socktype == SOCK_STREAM || sep->se_socktype == SOCK_DGRAM) ) { int i; for (i = 0; i < ARRAY_SIZE(builtins); i++) - if (strcmp(builtins[i].bi_service, sep->se_service) == 0) + if (strncmp(builtins[i].bi_service7, sep->se_service, 7) == 0) goto found_bi; bb_error_msg("unknown internal service %s", sep->se_service); goto parse_err; found_bi: sep->se_builtin = &builtins[i]; - sep->se_wait = 0; /* = builtins[i].bi_wait; - always 0 */ + /* stream builtins must be "nowait", dgram must be "wait" */ + if (sep->se_wait != (sep->se_socktype == SOCK_DGRAM)) + goto parse_err; } #endif argc = 0; - while ((arg = next_word(&cp)) != NULL && argc < MAXARGV) { + while ((arg = next_word(&cp)) != NULL && argc < MAXARGV) sep->se_argv[argc++] = xstrdup(arg); + + /* 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; + /* "udp nowait" is a small fork bomb :) */ + if (!sep->se_wait) + goto parse_err; } - /* while (argc <= MAXARGV) */ - /* sep->se_argv[argc++] = NULL; - done by new_servtab() */ - - /* - * Now that we've processed the entire line, check if the hostname - * specifier was a comma separated list of hostnames. If so - * we'll make new entries for each address. - */ + + /* check if the hostname specifier is a comma separated list + * of hostnames. we'll make new entries for each address. */ while ((hostdelim = strrchr(sep->se_local_hostname, ',')) != NULL) { nsep = dup_servtab(sep); - /* - * NUL terminate the hostname field of the existing entry, - * and make a dup for the new entry. - */ + /* NUL terminate the hostname field of the existing entry, + * and make a dup for the new entry. */ *hostdelim++ = '\0'; nsep->se_local_hostname = xstrdup(hostdelim); nsep->se_next = sep->se_next; sep->se_next = nsep; } - /* Was doing it here: */ + /* was doing it here: */ /* DNS resolution, create copies for each IP address */ + /* IPv6-ization destroyed it :( */ return sep; } @@ -947,15 +953,9 @@ static void reread_config_file(int sig ATTRIBUTE_UNUSED) switch (sep->se_family) { struct sockaddr_un *sun; case AF_UNIX: - /* we have poor infrastructure for AF_UNIX... */ - n = strlen(sep->se_service); - if (n > sizeof(sun->sun_path) - 1) - n = sizeof(sun->sun_path) - 1; - lsa = xzalloc(LSA_LEN_SIZE + sizeof(struct sockaddr_un)); - lsa->len = sizeof(struct sockaddr_un); + lsa = xzalloc_lsa(AF_UNIX); sun = (struct sockaddr_un*)&lsa->u.sa; - sun->sun_family = AF_UNIX; - strncpy(sun->sun_path, sep->se_service, n); + safe_strncpy(sun->sun_path, sep->se_service, sizeof(sun->sun_path)); break; default: /* case AF_INET, case AF_INET6 */ @@ -1259,8 +1259,8 @@ int inetd_main(int argc, char **argv) sep->se_count = 0; } } - /* on NOMMU, streamed echo, chargen and discard - * builtins wouldn't work, but they are + /* on NOMMU, streamed chargen + * builtin wouldn't work, but it is * not allowed on NOMMU (ifdefed out) */ #ifdef INETD_BUILTINS_ENABLED if (BB_MMU && sep->se_builtin) @@ -1311,8 +1311,42 @@ int inetd_main(int argc, char **argv) continue; /* -> check next fd in fd set */ } #endif - /* child. prepare env and exec program */ + /* child */ setsid(); +#if 0 +/* This does not work. + * Actually, it _almost_ works. The idea behind it is: child + * can peek at (already received and buffered by kernel) UDP packet, + * and perform 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 returns to + * select(), it will see this descriptor attached to the peer (!) + * and likely still readable, will act on it and mess things up + * (can create many copies of same child, etc). + * If child will create new socket instead, then bind() and + * connect() it to peer's address, descriptor aliasing problem + * is solved, but first packet cannot be "transferred" to the new + * socket. It is not a problem if child can account for this, + * but our child will exec - and exec'ed program does not know + * about this "lost packet" problem! Pity... */ + /* "nowait" udp[6]. Hmmm... */ + if (!sep->se_wait + && sep->se_socktype == SOCK_DGRAM + && sep->se_family != AF_UNIX + ) { + len_and_sockaddr *lsa = xzalloc_lsa(sep->se_family); + /* peek at the packet and remember peer addr */ + int r = recvfrom(ctrl, NULL, 0, MSG_PEEK|MSG_DONTWAIT, + &lsa->u.sa, &lsa->len); + if (r >= 0) + /* 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); + free(lsa); + } +#endif + /* prepare env and exec program */ pwd = getpwnam(sep->se_user); if (pwd == NULL) { bb_error_msg("%s: no such user", sep->se_user); @@ -1342,8 +1376,8 @@ int inetd_main(int argc, char **argv) bb_perror_msg("setrlimit"); closelog(); xmove_fd(ctrl, 0); - dup2(0, 1); - dup2(0, 2); + xdup2(0, 1); + xdup2(0, 2); /* NB: among others, this loop closes listening socket * for nowait stream children */ for (sep2 = serv_list; sep2; sep2 = sep2->se_next) @@ -1378,6 +1412,8 @@ static void echo_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) } #else static const char *const args[] = { "cat", NULL }; + /* no error messages */ + xmove_fd(xopen("/dev/null", O_WRONLY), STDERR_FILENO); BB_EXECVP("cat", (char**)args); _exit(1); #endif @@ -1404,8 +1440,16 @@ static void echo_dg(int s, servtab_t *sep) /* ARGSUSED */ static void discard_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) { +#if BB_MMU while (safe_read(s, line, LINE_SIZE) > 0) continue; +#else + static const char *const args[] = { "dd", "of=/dev/null", NULL }; + /* no error messages */ + xmove_fd(xopen("/dev/null", O_WRONLY), STDERR_FILENO); + BB_EXECVP("dd", (char**)args); + _exit(1); +#endif } /* ARGSUSED */ static void discard_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) @@ -1418,14 +1462,14 @@ static void discard_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) #if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN #define LINESIZ 72 -static void initring(void) +static void init_ring(void) { int i; - endring = ring; + end_ring = ring; for (i = 0; i <= 128; ++i) if (isprint(i)) - *endring++ = i; + *end_ring++ = i; } /* Character generator. MMU arches only. */ /* ARGSUSED */ @@ -1435,8 +1479,8 @@ static void chargen_stream(int s, servtab_t *sep) int len; char text[LINESIZ + 2]; - if (!endring) { - initring(); + if (!end_ring) { + init_ring(); rs = ring; } @@ -1444,14 +1488,14 @@ static void chargen_stream(int s, servtab_t *sep) text[LINESIZ + 1] = '\n'; rs = ring; for (;;) { - len = endring - rs; + len = end_ring - rs; if (len >= LINESIZ) memmove(text, rs, LINESIZ); else { memmove(text, rs, len); memmove(text + len, ring, LINESIZ - len); } - if (++rs == endring) + if (++rs == end_ring) rs = ring; xwrite(s, text, sizeof(text)); } @@ -1469,20 +1513,20 @@ static void chargen_dg(int s, servtab_t *sep) if (recvfrom(s, text, sizeof(text), MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0) return; - if (!endring) { - initring(); - ringpos = ring; + if (!end_ring) { + init_ring(); + ring_pos = ring; } - len = endring - ringpos; + len = end_ring - ring_pos; if (len >= LINESIZ) - memmove(text, ringpos, LINESIZ); + memmove(text, ring_pos, LINESIZ); else { - memmove(text, ringpos, len); + memmove(text, ring_pos, len); memmove(text + len, ring, LINESIZ - len); } - if (++ringpos == endring) - ringpos = ring; + if (++ring_pos == end_ring) + ring_pos = ring; text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; sendto(s, text, sizeof(text), 0, &lsa->u.sa, lsa->len); @@ -1498,7 +1542,7 @@ static void chargen_dg(int s, servtab_t *sep) * we must add 2208988800 seconds to this figure to make up for * some seventy years Bell Labs was asleep. */ -static unsigned machtime(void) +static uint32_t machtime(void) { struct timeval tv; |