/
niceSOFT
/
inetutils
Обзор
Документация
Войти
/
niceSOFT
/
inetutils
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/inetd.c
2 142 строки
51 KB
Guillem Jover
inetd: Add new --foreground option
26 янв 2026, 21:48
26 янв 2026, 21:48
db70e62
Код
Авторство
О чём код?
/* Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see `http://www.gnu.org/licenses/'. */ /* * Copyright (c) 1983, 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Inetd - Internet super-server * * This program invokes all internet services as needed. 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 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''. * * Inetd uses a configuration file which is read at startup * and, possibly, at some later time in response to a hangup signal. * The configuration file is ``free format'' with fields given in the * order shown below. Continuation lines for an entry must being with * a space or tab. All fields must be present in each entry. * * service name must be in /etc/services or must * name a tcpmux service * socket type stream/dgram/raw/rdm/seqpacket * protocol must be in /etc/protocols * wait/nowait[.max] single-threaded/multi-threaded * [with an optional fork limit] * user[:group] or user[.group] user (and group) to run daemon as * server program full path name * server program arguments arguments starting with argv[0] * * TCP services without official port numbers are handled with the * RFC1078-based tcpmux internal service. Tcpmux listens on port 1 for * requests. When a connection is made from a foreign host, the service * requested is passed to tcpmux, which looks it up in the servtab list * and returns the proper entry for the service. Tcpmux returns a * negative reply if the service doesn't exist, otherwise the invoked * server is expected to return the positive reply if the service type in * inetd.conf file has the prefix "tcpmux/". If the service type has the * prefix "tcpmux/+", tcpmux will return the positive reply for the * process; this is for compatibility with older server code, and also * allows you to invoke programs that use stdin/stdout without putting any * special server code in them. Services that use tcpmux are "nowait" * because they do not have a well-known port and hence cannot listen * for new requests. * * Comment lines are indicated by a `#' in column 1. */ #include <config.h> #include <sys/types.h> #include <sys/param.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/time.h> #include <time.h> #ifdef HAVE_SYS_RESOURCE_H # include <sys/resource.h> #endif #include <netinet/in.h> #include <arpa/inet.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <netdb.h> #include <pwd.h> #include <signal.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include <unistd.h> #include <argp.h> #include <argp-version-etc.h> #include <progname.h> #include <sys/select.h> #include <grp.h> #include "libinetutils.h" #include "argcv.h" #include "version-etc.h" #include "attribute.h" #ifndef EAI_ADDRFAMILY # define EAI_ADDRFAMILY 1 #endif #define TOOMANY 1000 /* don't start more than TOOMANY */ #define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ #define RETRYTIME (60*10) /* retry after bind or server fail */ #ifndef SIGCHLD # define SIGCHLD SIGCLD #endif #ifndef HAVE_SIGACTION # define SIGBLOCK (sigmask(SIGCHLD)|sigmask(SIGHUP)|sigmask(SIGALRM)) #endif bool debug = false; bool foreground = false; int nsock, maxsock; fd_set allsock; int options; int timingout; unsigned toomany = TOOMANY; char **Argv; char *LastArg; char **config_files; static bool env_option = false; /* Set environment variables */ static bool resolve_option = false; /* Resolve IP addresses */ static bool pidfile_option = true; /* Record the PID in a file */ static const char *pid_file = PATH_INETDPID; const char args_doc[] = "[CONF-FILE [CONF-DIR]]..."; const char doc[] = "Internet super-server."; /* Define keys for long options that do not have short counterparts. */ enum { OPT_ENVIRON = 256, OPT_FOREGROUND, OPT_RESOLVE }; const char *program_authors[] = { "Alain Magloire", "Alfred M. Szmidt", "Debarshi Ray", "Jakob 'sparky' Kaivo", "Jeff Bailey", "Jeroen Dekkers", "Marcus Brinkmann", "Sergey Poznyakoff", "others", NULL }; static struct argp_option argp_options[] = { #define GRP 0 {"foreground", OPT_FOREGROUND, NULL, 0, "run in foreground mode", GRP + 1}, {"debug", 'd', NULL, 0, "turn on debugging, run in foreground mode", GRP + 1}, {"environment", OPT_ENVIRON, NULL, 0, "pass local and remote socket information in environment variables", GRP + 1}, {"pidfile", 'p', "PIDFILE", OPTION_ARG_OPTIONAL, "override pidfile (default: \"" PATH_INETDPID "\")", GRP + 1}, {"rate", 'R', "NUMBER", 0, "maximum invocation rate (per minute)", GRP + 1}, {"resolve", OPT_RESOLVE, NULL, 0, "resolve IP addresses when setting environment variables " "(see --environment)", GRP + 1}, #undef GRP {NULL, 0, NULL, 0, NULL, 0} }; static error_t parse_opt (int key, char *arg, struct argp_state *state MAYBE_UNUSED) { char *p; int number; switch (key) { case 'd': debug = true; foreground = true; options |= SO_DEBUG; break; case OPT_FOREGROUND: foreground = true; break; case OPT_ENVIRON: env_option = true; break; case 'p': if (arg && strlen (arg)) pid_file = arg; else pidfile_option = false; break; case 'R': number = strtol (arg, &p, 0); if (number < 1 || *p) syslog (LOG_ERR, "-R %s: bad value for service invocation rate", arg); else toomany = number; break; case OPT_RESOLVE: resolve_option = true; break; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { argp_options, parse_opt, args_doc, doc, NULL, NULL, NULL }; struct servtab { const char *se_file; int se_line; char *se_node; /* node name */ char *se_service; /* name of service */ int se_socktype; /* type of socket to use */ char *se_proto; /* protocol used */ pid_t se_wait; /* single threaded server */ unsigned se_max; /* Maximum number of instances per CNT_INTVL */ short se_checked; /* looked at during merge */ char *se_user; /* user name to run as */ char *se_group; /* group name to run as */ struct biltin *se_bi; /* if built-in, description */ char *se_server; /* server program */ char **se_argv; /* program arguments */ size_t se_argc; /* number of arguments */ int se_fd; /* open descriptor */ int se_type; /* type */ sa_family_t se_family; /* address family of the socket */ char se_v4mapped; /* 1 = accept v4mapped connection, 0 = don't */ struct sockaddr_storage se_ctrladdr; /* bound address */ socklen_t se_addrlen; /* exact address length in use */ unsigned se_refcnt; unsigned se_count; /* number started since se_time */ struct timeval se_time; /* start of se_count */ struct servtab *se_next; } *servtab; #define NORM_TYPE 0 #define MUX_TYPE 1 #define MUXPLUS_TYPE 2 #define ISMUX(sep) (((sep)->se_type == MUX_TYPE) || \ ((sep)->se_type == MUXPLUS_TYPE)) #define ISMUXPLUS(sep) ((sep)->se_type == MUXPLUS_TYPE) /* Built-in services */ void chargen_dg (int, struct servtab *); void chargen_stream (int, struct servtab *); void daytime_dg (int, struct servtab *); void daytime_stream (int, struct servtab *); void discard_dg (int, struct servtab *); void discard_stream (int, struct servtab *); void echo_dg (int, struct servtab *); void echo_stream (int, struct servtab *); void machtime_dg (int, struct servtab *); void machtime_stream (int, struct servtab *); void tcpmux (int s, struct servtab *sep); struct biltin { const char *bi_service; /* internally provided service name */ int bi_socktype; /* type of socket supported */ short bi_fork; /* 1 if should fork before call */ short bi_wait; /* 1 if should wait for child */ void (*bi_fn) (int s, struct servtab *); /*function which performs it */ } biltins[] = { /* Echo received data */ {"echo", SOCK_STREAM, 1, 0, echo_stream}, {"echo", SOCK_DGRAM, 0, 0, echo_dg}, /* Internet /dev/null */ {"discard", SOCK_STREAM, 1, 0, discard_stream}, {"discard", SOCK_DGRAM, 0, 0, discard_dg}, /* Return 32 bit time since 1900 */ {"time", SOCK_STREAM, 0, 0, machtime_stream}, {"time", SOCK_DGRAM, 0, 0, machtime_dg}, /* Return human-readable time */ {"daytime", SOCK_STREAM, 0, 0, daytime_stream}, {"daytime", SOCK_DGRAM, 0, 0, daytime_dg}, /* Familiar character generator */ {"chargen", SOCK_STREAM, 1, 0, chargen_stream}, {"chargen", SOCK_DGRAM, 0, 0, chargen_dg}, {"tcpmux", SOCK_STREAM, 1, 0, tcpmux}, {NULL, 0, 0, 0, NULL} }; struct biltin * bi_lookup (const struct servtab *sep) { struct biltin *bi; for (bi = biltins; bi->bi_service; bi++) if (bi->bi_socktype == sep->se_socktype && strcmp (bi->bi_service, sep->se_service) == 0) return bi; return NULL; } /* Signal handling */ #if defined HAVE_SIGACTION # define SIGSTATUS sigset_t # define sigstatus_empty(s) sigemptyset(&s) # define inetd_pause(s) sigsuspend(&s) #else # define SIGSTATUS long # define sigstatus_empty(s) s = 0 # define inetd_pause(s) sigpause (s) #endif void signal_set_handler (int signo, void (*handler) (int)) { #if defined HAVE_SIGACTION struct sigaction sa; memset ((char *) &sa, 0, sizeof (sa)); sigemptyset (&sa.sa_mask); sigaddset (&sa.sa_mask, signo); # ifdef SA_RESTART sa.sa_flags = SA_RESTART; # endif sa.sa_handler = handler; sigaction (signo, &sa, NULL); #elif defined(HAVE_SIGVEC) struct sigvec sv; memset (&sv, 0, sizeof (sv)); sv.sv_mask = SIGBLOCK; sv.sv_handler = handler; sigvec (signo, &sv, NULL); #else /* !HAVE_SIGVEC */ signal (signo, handler); #endif /* HAVE_SIGACTION */ } void signal_block (SIGSTATUS *old_status) { #ifdef HAVE_SIGACTION sigset_t sigs; sigemptyset (&sigs); sigaddset (&sigs, SIGCHLD); sigaddset (&sigs, SIGHUP); sigaddset (&sigs, SIGALRM); sigprocmask (SIG_BLOCK, &sigs, old_status); #else long omask = sigblock (SIGBLOCK); if (old_status) *old_status = omask; #endif } void signal_unblock (SIGSTATUS *status) { #ifdef HAVE_SIGACTION if (status) sigprocmask (SIG_SETMASK, status, 0); else { sigset_t empty; sigemptyset (&empty); sigprocmask (SIG_SETMASK, &empty, 0); } #else sigsetmask (status ? *status : 0); #endif } void run_service (int ctrl, struct servtab *sep) { struct passwd *pwd; struct group *grp = NULL; char buf[50]; if (sep->se_bi) { (*sep->se_bi->bi_fn) (ctrl, sep); } else { if (debug) fprintf (stderr, "%d execl %s\n", (int) getpid (), sep->se_server); dup2 (ctrl, 0); close (ctrl); dup2 (0, 1); dup2 (0, 2); pwd = getpwnam (sep->se_user); if (pwd == NULL) { syslog (LOG_ERR, "%s/%s: %s: No such user", sep->se_service, sep->se_proto, sep->se_user); if (sep->se_socktype != SOCK_STREAM) recv (0, buf, sizeof buf, 0); _exit (EXIT_FAILURE); } if (sep->se_group && *sep->se_group) { grp = getgrnam (sep->se_group); if (grp == NULL) { syslog (LOG_ERR, "%s/%s: %s: No such group", sep->se_service, sep->se_proto, sep->se_group); if (sep->se_socktype != SOCK_STREAM) recv (0, buf, sizeof buf, 0); _exit (EXIT_FAILURE); } } if (pwd->pw_uid) { if (grp && grp->gr_gid) { if (setgid (grp->gr_gid) < 0) { syslog (LOG_ERR, "%s: can't set gid %d: %m", sep->se_service, grp->gr_gid); _exit (EXIT_FAILURE); } } else if (setgid (pwd->pw_gid) < 0) { syslog (LOG_ERR, "%s: can't set gid %d: %m", sep->se_service, pwd->pw_gid); _exit (EXIT_FAILURE); } #ifdef HAVE_INITGROUPS initgroups (pwd->pw_name, (grp && grp->gr_gid) ? grp->gr_gid : pwd->pw_gid); #endif if (setuid (pwd->pw_uid) < 0) { syslog (LOG_ERR, "%s: can't set uid %d: %m", sep->se_service, pwd->pw_uid); _exit (EXIT_FAILURE); } } execv (sep->se_server, sep->se_argv); if (sep->se_socktype != SOCK_STREAM) recv (0, buf, sizeof buf, 0); syslog (LOG_ERR, "cannot execute %s: %m", sep->se_server); _exit (EXIT_FAILURE); } } void reapchild (int signo MAYBE_UNUSED) { int status; pid_t pid; struct servtab *sep; for (;;) { #ifdef HAVE_WAIT3 pid = wait3 (&status, WNOHANG, NULL); #else pid = wait (&status); #endif if (pid <= 0) break; if (debug) fprintf (stderr, "%d reaped, status %#x\n", (int) pid, status); for (sep = servtab; sep; sep = sep->se_next) if (sep->se_wait == pid) { if (status) syslog (LOG_WARNING, "%s: exit status 0x%x", sep->se_server, status); if (debug) fprintf (stderr, "restored %s, fd %d\n", sep->se_service, sep->se_fd); FD_SET (sep->se_fd, &allsock); nsock++; sep->se_wait = 1; } } } char * newstr (const char *cp) { char *s; s = strdup (cp ? cp : ""); if (s != NULL) return s; syslog (LOG_ERR, "strdup: %m"); exit (-1); } void dupmem (void **pptr, size_t size) { void *ptr = malloc (size); if (!ptr) { syslog (LOG_ERR, "dupmem: %m"); exit (-1); } memcpy (ptr, *pptr, size); *pptr = ptr; } void dupstr (char **pstr) { if (*pstr) dupmem ((void **) pstr, strlen (*pstr) + 1); } /* * print_service: * Dump relevant information to stderr */ void print_service (const char *action, struct servtab *sep) { fprintf (stderr, "%s:%d: %s: %s:%s proto=%s, wait=%d, max=%u, " "user=%s group=%s builtin=%s server=%s\n", sep->se_file, sep->se_line, action, ISMUX (sep) ? (ISMUXPLUS (sep) ? "tcpmuxplus" : "tcpmux") : (sep->se_node ? sep->se_node : "*"), sep->se_service, sep->se_proto, (int) sep->se_wait, sep->se_max, sep->se_user, sep->se_group, sep->se_bi ? sep->se_bi->bi_service : "no", sep->se_server); } /* Configuration */ int setup (struct servtab *sep) { int err; int on = 1; tryagain: sep->se_fd = socket (sep->se_family, sep->se_socktype, 0); if (sep->se_fd < 0) { /* If we don't support creating AF_INET6 sockets, create AF_INET sockets. */ if (errno == EAFNOSUPPORT && sep->se_family == AF_INET6 && sep->se_v4mapped) { /* Fall back to IPv4 silently. */ sep->se_family = AF_INET; goto tryagain; } if (debug) fprintf (stderr, "socket failed on %s/%s: %s\n", sep->se_service, sep->se_proto, strerror (errno)); syslog (LOG_ERR, "%s/%s: socket: %m", sep->se_service, sep->se_proto); return 1; } #ifdef IPV6 if (sep->se_family == AF_INET6) { /* Reverse the value of SEP->se_v4mapped, since otherwise if using `tcp6' as a protocol type, all connections will be mapped to IPv6, and with `tcp6only', IPv4 gets mapped to IPv6. */ int val = sep->se_v4mapped ? 0 : 1; if (setsockopt (sep->se_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &val, sizeof (val)) < 0) syslog (LOG_ERR, "setsockopt (IPV6_V6ONLY): %m"); } #endif if (strncmp (sep->se_proto, "tcp", 3) == 0 && (options & SO_DEBUG)) { if (setsockopt (sep->se_fd, SOL_SOCKET, SO_DEBUG, (char *) &on, sizeof (on)) < 0 && errno != EACCES) /* Ignore insufficient permission. */ syslog (LOG_ERR, "setsockopt (SO_DEBUG): %m"); } err = setsockopt (sep->se_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)); if (err < 0) syslog (LOG_ERR, "setsockopt (SO_REUSEADDR): %m"); err = bind (sep->se_fd, (struct sockaddr *) &sep->se_ctrladdr, sep->se_addrlen); if (err < 0) { /* If we can't bind with AF_INET6 try again with AF_INET. */ if ((errno == EADDRNOTAVAIL || errno == EAFNOSUPPORT) && sep->se_family == AF_INET6 && sep->se_v4mapped) { /* Fall back to IPv4 silently. */ sep->se_family = AF_INET; close (sep->se_fd); goto tryagain; } if (sep->se_node) { if (debug) fprintf (stderr, "bind failed for %s %s/%s: %s\n", sep->se_node, sep->se_service, sep->se_proto, strerror (errno)); syslog (LOG_ERR, "%s %s/%s: bind: %m", sep->se_node, sep->se_service, sep->se_proto); } else { if (debug) fprintf (stderr, "bind failed for %s/%s: %s\n", sep->se_service, sep->se_proto, strerror (errno)); syslog (LOG_ERR, "%s/%s: bind: %m", sep->se_service, sep->se_proto); } close (sep->se_fd); sep->se_fd = -1; if (!timingout) { timingout = 1; alarm (RETRYTIME); } return 1; } return 0; } void servent_setup (struct servtab *sep) { sep->se_checked = 1; if (sep->se_fd == -1 && setup (sep) == 0) { if (sep->se_socktype == SOCK_STREAM) listen (sep->se_fd, 10); FD_SET (sep->se_fd, &allsock); nsock++; if (sep->se_fd > maxsock) maxsock = sep->se_fd; if (debug) fprintf (stderr, "registered %s on %d\n", sep->se_server, sep->se_fd); } } void retry (int signo MAYBE_UNUSED) { struct servtab *sep; timingout = 0; for (sep = servtab; sep; sep = sep->se_next) if (sep->se_fd == -1 && !ISMUX (sep)) setup (sep); } /* * Finish with a service and its socket. */ void close_sep (struct servtab *sep) { if (sep->se_fd >= 0) { nsock--; FD_CLR (sep->se_fd, &allsock); close (sep->se_fd); sep->se_fd = -1; } sep->se_count = 0; /* * Don't keep the pid of this running daemon: when reapchild() * reaps this pid, it would erroneously increment nsock. */ if (sep->se_wait > 1) sep->se_wait = 1; } struct servtab * enter (struct servtab *cp) { struct servtab *sep; SIGSTATUS sigstatus; size_t i; /* Checking/Removing duplicates */ for (sep = servtab; sep; sep = sep->se_next) if (memcmp (&sep->se_ctrladdr, &cp->se_ctrladdr, sizeof (sep->se_ctrladdr)) == 0 && strcmp (sep->se_service, cp->se_service) == 0 && strcmp (sep->se_proto, cp->se_proto) == 0 && ISMUX (sep) == ISMUX (cp)) break; if (sep != 0) { signal_block (&sigstatus); /* * 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 (cp->se_bi == 0 && (sep->se_wait == 1 || cp->se_wait == 0)) sep->se_wait = cp->se_wait; #define SWAP(a, b) { char *c = a; a = b; b = c; } if (cp->se_user) SWAP (sep->se_user, cp->se_user); if (cp->se_group) SWAP (sep->se_group, cp->se_group); if (cp->se_server) SWAP (sep->se_server, cp->se_server); argcv_free (sep->se_argc, sep->se_argv); sep->se_argc = cp->se_argc; sep->se_argv = cp->se_argv; cp->se_argc = 0; cp->se_argv = NULL; sep->se_checked = 1; signal_unblock (&sigstatus); if (debug) print_service ("REDO", sep); return sep; } if (debug) print_service ("ADD ", cp); sep = (struct servtab *) malloc (sizeof (*sep)); if (sep == NULL) { syslog (LOG_ERR, "Out of memory."); exit (-1); } *sep = *cp; dupstr (&sep->se_node); dupstr (&sep->se_service); dupstr (&sep->se_proto); dupstr (&sep->se_user); dupstr (&sep->se_group); dupstr (&sep->se_server); dupmem ((void **) &sep->se_argv, sep->se_argc * sizeof (sep->se_argv[0])); for (i = 0; i < sep->se_argc; i++) dupstr (&sep->se_argv[i]); sep->se_fd = -1; signal_block (&sigstatus); sep->se_next = servtab; servtab = sep; signal_unblock (&sigstatus); return sep; } #define IPV4_NUMCHARS ".0123456789" #define IPV6_NUMCHARS ".:0123456789abcdefABCDEF" int inetd_getaddrinfo (struct servtab *sep, int proto, struct addrinfo **result) { struct addrinfo hints; #ifdef IPV6 bool numeric_address = false; /* In case a numerical address is supplied, which does not apply to the indicated domain, a non-local resolver will wait in vain until time out occurs, thus blocking. Avoid this by falling back to numerical host resolving when address string seems to be numerical. */ /* Purely numeric address? Separate criteria for IPv4 and IPv6, since IPv6 allows hexadecimal coding and IPv4 mapping! */ if (sep->se_node && (strspn (sep->se_node, IPV4_NUMCHARS) == strlen (sep->se_node) || (strchr (sep->se_node, ':') && strspn (sep->se_node, IPV6_NUMCHARS)))) numeric_address = true; else if (debug && sep->se_node) fprintf (stderr, "Resolving address: %s\n", sep->se_node); #endif /* IPV6 */ memset (&hints, 0, sizeof (hints)); hints.ai_flags = AI_PASSIVE; #ifdef AI_V4MAPPED if (sep->se_v4mapped && (sep->se_family != AF_INET)) hints.ai_flags |= AI_V4MAPPED; #endif #ifdef IPV6 if (numeric_address) hints.ai_flags |= AI_NUMERICHOST; #endif hints.ai_family = sep->se_family; hints.ai_socktype = sep->se_socktype; hints.ai_protocol = proto; return getaddrinfo (sep->se_node, sep->se_service, &hints, result); } int expand_enter (struct servtab *sep) { int err; struct addrinfo *result, *rp; struct protoent *proto; struct servtab *cp; /* Make sure that tcp6 etc also work. */ if (strncmp (sep->se_proto, "tcp", 3) == 0) proto = getprotobyname ("tcp"); else if (strncmp (sep->se_proto, "udp", 3) == 0) proto = getprotobyname ("udp"); else proto = getprotobyname (sep->se_proto); if (!proto) { syslog (LOG_ERR, "%s: Unknown protocol", sep->se_proto); return 1; } err = inetd_getaddrinfo (sep, proto->p_proto, &result); #if IPV6 if (err == EAI_ADDRFAMILY && sep->se_family == AF_INET6 && sep->se_v4mapped) { /* Fall back to IPv4 silently. */ sep->se_family = AF_INET; err = inetd_getaddrinfo (sep, proto->p_proto, &result); } #endif if (err) { const char *errmsg; if (err == EAI_SYSTEM) errmsg = strerror (errno); else errmsg = gai_strerror (err); if (sep->se_node) { if (debug) fprintf (stderr, "resolution of %s %s/%s failed: %s\n", sep->se_node, sep->se_service, sep->se_proto, errmsg); syslog (LOG_ERR, "%s %s/%s: getaddrinfo: %s", sep->se_node, sep->se_service, sep->se_proto, errmsg); } else { if (debug) fprintf (stderr, "resolution of %s/%s failed: %s\n", sep->se_service, sep->se_proto, errmsg); syslog (LOG_ERR, "%s/%s: getaddrinfo: %s", sep->se_service, sep->se_proto, errmsg); } return 1; } for (rp = result; rp != NULL; rp = rp->ai_next) { memset (&sep->se_ctrladdr, 0, sizeof (sep->se_ctrladdr)); memcpy (&sep->se_ctrladdr, rp->ai_addr, rp->ai_addrlen); sep->se_addrlen = rp->ai_addrlen; cp = enter (sep); servent_setup (cp); } freeaddrinfo (result); return 0; } /* Configuration parser */ char *global_serv_node; char *serv_node; size_t serv_node_offset; char *linebuf = NULL; size_t linebufsize = 0; FILE * setconfig (const char *file) { return fopen (file, "r"); } void endconfig (FILE *fconfig) { if (fconfig) fclose (fconfig); } void freeconfig (struct servtab *cp) { free (cp->se_node); free (cp->se_service); free (cp->se_proto); free (cp->se_user); free (cp->se_group); free (cp->se_server); argcv_free (cp->se_argc, cp->se_argv); } #define INETD_SERVICE 0 /* service name */ #define INETD_SOCKET 1 /* socket type */ #define INETD_PROTOCOL 2 /* protocol */ #define INETD_WAIT 3 /* wait/nowait */ #define INETD_USER 4 /* user */ #define INETD_SERVER_PATH 5 /* server program path */ #define INETD_SERVER_ARGS 6 /* server program arguments */ #define INETD_FIELDS_MIN 6 /* Minimum number of fields in entry */ struct servtab * next_node_sep (struct servtab *sep) { if (serv_node) { size_t i = strcspn (serv_node + serv_node_offset, ","); sep->se_node = malloc (i + 1); if (!sep->se_node) { syslog (LOG_ERR, "malloc: %m"); exit (-1); } memcpy (sep->se_node, serv_node + serv_node_offset, i); sep->se_node[i] = 0; serv_node_offset += i; if (serv_node[serv_node_offset]) serv_node_offset++; else { free (serv_node); serv_node = NULL; } } return sep; } struct servtab * getconfigent (FILE *fconfig, const char *file, size_t *line) { static struct servtab serv; struct servtab *sep = &serv; int argc = 0; size_t i; char **argv = NULL; char *node, *service; static char TCPMUX_TOKEN[] = "tcpmux/"; #define MUX_LEN (sizeof(TCPMUX_TOKEN)-1) if (serv_node) return next_node_sep (sep); memset (sep, 0, sizeof *sep); while (1) { argcv_free (argc, argv); freeconfig (sep); memset (sep, 0, sizeof *sep); do { ssize_t n = getline (&linebuf, &linebufsize, fconfig); if (n < 0) return 0; else if (n == 0) continue; if (linebuf[n - 1] == '\n') linebuf[n - 1] = 0; ++*line; } while (*linebuf == '#' || *linebuf == 0); if (argcv_get (linebuf, "", &argc, &argv)) continue; if (argc < INETD_FIELDS_MIN) { if (argc == 1 && argv[0][strlen (argv[0]) - 1] == ':') { argv[0][strlen (argv[0]) - 1] = 0; free (global_serv_node); if (strcmp (argv[0], "*")) global_serv_node = newstr (argv[0]); } else syslog (LOG_ERR, "%s:%lu: not enough fields", file, (unsigned long) *line); continue; } sep->se_file = file; sep->se_line = *line; node = argv[INETD_SERVICE]; service = strrchr (node, ':'); if (!service) { if (global_serv_node) { node = global_serv_node; serv_node = newstr (node); serv_node_offset = 0; } else node = NULL; service = argv[INETD_SERVICE]; } else { *service++ = 0; if (strcmp (node, "*") == 0) node = NULL; else { serv_node = newstr (node); serv_node_offset = 0; } } if (strncmp (service, TCPMUX_TOKEN, MUX_LEN) == 0) { char *c = service + MUX_LEN; if (*c == '+') { sep->se_type = MUXPLUS_TYPE; c++; } else sep->se_type = MUX_TYPE; sep->se_service = newstr (c); } else { sep->se_service = newstr (service); sep->se_type = NORM_TYPE; } if (strcmp (argv[INETD_SOCKET], "stream") == 0) sep->se_socktype = SOCK_STREAM; else if (strcmp (argv[INETD_SOCKET], "dgram") == 0) sep->se_socktype = SOCK_DGRAM; else if (strcmp (argv[INETD_SOCKET], "rdm") == 0) sep->se_socktype = SOCK_RDM; else if (strcmp (argv[INETD_SOCKET], "seqpacket") == 0) sep->se_socktype = SOCK_SEQPACKET; else if (strcmp (argv[INETD_SOCKET], "raw") == 0) sep->se_socktype = SOCK_RAW; else { syslog (LOG_WARNING, "%s:%lu: bad socket type", file, (unsigned long) *line); sep->se_socktype = -1; } sep->se_proto = newstr (argv[INETD_PROTOCOL]); #ifdef IPV6 /* We default to IPv4. */ sep->se_family = AF_INET; sep->se_v4mapped = 1; if ((strncmp (sep->se_proto, "tcp", 3) == 0) || (strncmp (sep->se_proto, "udp", 3) == 0)) { if (sep->se_proto[3] == '6') { sep->se_family = AF_INET6; sep->se_v4mapped = 0; /* Check for tcp6only and udp6only. */ if (strcmp (&sep->se_proto[3], "6only") == 0) sep->se_v4mapped = 0; } else if (sep->se_proto[3] == '4') { sep->se_family = AF_INET; } } #else if ((strncmp (sep->se_proto, "tcp6", 4) == 0) || (strncmp (sep->se_proto, "udp6", 4) == 0)) { syslog (LOG_ERR, "%s:%lu: %s: IPv6 support isn't enabled", file, (unsigned long) *line, sep->se_proto); continue; } sep->se_family = AF_INET; #endif { char *p, *q; p = strchr (argv[INETD_WAIT], '.'); if (p) *p++ = 0; if (strcmp (argv[INETD_WAIT], "wait") == 0) sep->se_wait = 1; else if (strcmp (argv[INETD_WAIT], "nowait") == 0) sep->se_wait = 0; else { syslog (LOG_WARNING, "%s:%lu: bad wait type", file, (unsigned long) *line); } if (p) { sep->se_max = strtoul (p, &q, 10); if (*q) syslog (LOG_WARNING, "%s:%lu: invalid number (%s)", file, (unsigned long) *line, p); } } if (ISMUX (sep)) { /* * Silently enforce "nowait" for TCPMUX services since * they don't have an assigned port to listen on. */ sep->se_wait = 0; if (strncmp (sep->se_proto, "tcp", 3)) { syslog (LOG_ERR, "%s:%lu: bad protocol for tcpmux service %s", file, (unsigned long) *line, sep->se_service); continue; } if (sep->se_socktype != SOCK_STREAM) { syslog (LOG_ERR, "%s:%lu: bad socket type for tcpmux service %s", file, (unsigned long) *line, sep->se_service); continue; } } /* Establish optional group identity: * user:group, user.group */ { char *p; sep->se_user = newstr (argv[INETD_USER]); p = strchr (sep->se_user, ':'); if (!p) p = strchr (sep->se_user, '.'); if (p) { *p = '\0'; sep->se_group = newstr (++p); } else sep->se_group = newstr (NULL); } sep->se_server = newstr (argv[INETD_SERVER_PATH]); if (strcmp (sep->se_server, "internal") == 0) { sep->se_bi = bi_lookup (sep); if (!sep->se_bi) { syslog (LOG_ERR, "%s:%lu: internal service %s unknown", file, (unsigned long) *line, sep->se_service); continue; } sep->se_wait = sep->se_bi->bi_wait; } else sep->se_bi = NULL; sep->se_argc = argc - INETD_FIELDS_MIN + 1; sep->se_argv = calloc (sep->se_argc + 1, sizeof sep->se_argv[0]); if (!sep->se_argv) { syslog (LOG_ERR, "%s:%lu: Out of memory.", file, (unsigned long) *line); exit (-1); } for (i = 0; i < sep->se_argc; i++) { sep->se_argv[i] = argv[INETD_SERVER_ARGS + i]; argv[INETD_SERVER_ARGS + i] = 0; } /* If no arguments are provided, use server name as argv[0]. */ if (sep->se_argc == 1) { const char *argv0 = strrchr (sep->se_server, '/'); if (argv0) argv0++; else argv0 = sep->se_server; sep->se_argv[0] = newstr (argv0); } sep->se_argv[i] = NULL; break; } argcv_free (argc, argv); return next_node_sep (sep); } void nextconfig (const char *file) { #ifndef IPV6 struct servent *sp; #endif struct servtab *sep, **sepp; struct passwd *pwd; struct group *grp; FILE *fconfig; SIGSTATUS sigstatus; size_t line = 0; fconfig = setconfig (file); if (!fconfig) { syslog (LOG_ERR, "%s: %m", file); return; } while ((sep = getconfigent (fconfig, file, &line))) { pwd = getpwnam (sep->se_user); if (pwd == NULL) { syslog (LOG_ERR, "%s/%s: No such user '%s', service ignored", sep->se_service, sep->se_proto, sep->se_user); continue; } if (sep->se_group && *sep->se_group) { grp = getgrnam (sep->se_group); if (grp == NULL) { syslog (LOG_ERR, "%s/%s: No such group '%s', service ignored", sep->se_service, sep->se_proto, sep->se_group); continue; } } if (ISMUX (sep)) { sep->se_fd = -1; sep->se_checked = 1; enter (sep); } else expand_enter (sep); if (serv_node) free (sep->se_node); else freeconfig (sep); } endconfig (fconfig); /* * Purge anything not looked at above. */ signal_block (&sigstatus); sepp = &servtab; while ((sep = *sepp)) { if (sep->se_checked) { sepp = &sep->se_next; continue; } *sepp = sep->se_next; if (sep->se_fd >= 0) close_sep (sep); if (debug) print_service ("FREE", sep); freeconfig (sep); free (sep); } signal_unblock (&sigstatus); } void fix_tcpmux (void) { struct servtab *sep; int need_tcpmux = 0; int has_tcpmux = 0; for (sep = servtab; sep; sep = sep->se_next) { if (sep->se_checked) { if (ISMUX (sep)) { if (has_tcpmux) return; need_tcpmux = 1; } if (strcmp (sep->se_service, "tcpmux") == 0) { if (need_tcpmux) return; has_tcpmux = 1; } } } if (need_tcpmux && !has_tcpmux) { struct servtab serv; memset (&serv, 0, sizeof (serv)); serv.se_file = "fix_tcpmux"; serv.se_service = newstr ("tcpmux"); serv.se_socktype = SOCK_STREAM; serv.se_checked = 1; serv.se_user = newstr ("root"); serv.se_group = newstr (NULL); /* Group name for root is not portable. */ serv.se_bi = bi_lookup (&serv); if (!serv.se_bi) { /* Should not happen */ freeconfig (&serv); if (debug) fprintf (stderr, "INTERNAL ERROR: could not find tcpmux built-in"); syslog (LOG_ERR, "INTERNAL ERROR: could not find tcpmux built-in"); return; } serv.se_wait = serv.se_bi->bi_wait; serv.se_server = newstr ("internal"); serv.se_fd = -1; serv.se_type = NORM_TYPE; #ifdef IPV6 serv.se_proto = newstr ("tcp6"); serv.se_family = AF_INET6; serv.se_v4mapped = 1; #else serv.se_proto = newstr ("tcp"); serv.se_family = AF_INET; #endif if (debug) fprintf (stderr, "inserting default tcpmux entry\n"); syslog (LOG_INFO, "inserting default tcpmux entry"); expand_enter (&serv); } } void config (int signo) { int i; struct stat stats; struct servtab *sep; for (sep = servtab; sep; sep = sep->se_next) sep->se_checked = 0; for (i = 0; config_files[i]; i++) { struct stat statbuf; if (stat (config_files[i], &statbuf) == 0) { if (S_ISDIR (statbuf.st_mode)) { DIR *dirp = opendir (config_files[i]); if (dirp) { struct dirent *dp; while ((dp = readdir (dirp)) != NULL) { char *path = calloc (strlen (config_files[i]) + strlen (dp->d_name) + 2, 1); if (path) { sprintf (path, "%s/%s", config_files[i], dp->d_name); if (stat (path, &stats) == 0 && S_ISREG (stats.st_mode)) { nextconfig (path); } free (path); } } closedir (dirp); } } else if (S_ISREG (statbuf.st_mode)) { nextconfig (config_files[i]); } } else { if (signo == 0) fprintf (stderr, "inetd: %s, %s\n", config_files[i], strerror (errno)); else syslog (LOG_ERR, "%s: %m", config_files[i]); } } free (linebuf); linebuf = NULL; linebufsize = 0; fix_tcpmux (); } void set_proc_title (char *a, int s) { socklen_t size; char *cp; #ifdef IPV6 struct sockaddr_storage saddr; #else struct sockaddr_in saddr; #endif char buf[80]; cp = Argv[0]; size = sizeof saddr; if (getpeername (s, (struct sockaddr *) &saddr, &size) == 0) { #ifdef IPV6 int err; char buf2[80]; err = getnameinfo ((struct sockaddr *) &saddr, sizeof (saddr), buf2, sizeof (buf2), NULL, 0, NI_NUMERICHOST); if (!err) snprintf (buf, sizeof buf, "-%s [%s]", a, buf2); else snprintf (buf, sizeof buf, "-%s", a); #else snprintf (buf, sizeof buf, "-%s [%s]", a, inet_ntoa (saddr.sin_addr)); #endif } else snprintf (buf, sizeof buf, "-%s", a); strncpy (cp, buf, LastArg - cp); cp += strlen (cp); while (cp < LastArg) *cp++ = ' '; } /* * Internet services provided internally by inetd: */ #define BUFSIZE 8192 /* Echo service -- echo data back */ void echo_stream (int s, struct servtab *sep) { char buffer[BUFSIZE]; int i; set_proc_title (sep->se_service, s); while ((i = read (s, buffer, sizeof buffer)) > 0 && write (s, buffer, i) > 0) ; exit (EXIT_SUCCESS); } /* Echo service -- echo data back */ void echo_dg (int s, struct servtab *sep MAYBE_UNUSED) { char buffer[BUFSIZE]; int i; socklen_t size; #ifdef IPV6 struct sockaddr_storage sa; #else struct sockaddr sa; #endif size = sizeof sa; i = recvfrom (s, buffer, sizeof buffer, 0, (struct sockaddr *) &sa, &size); if (i < 0) return; sendto (s, buffer, i, 0, (struct sockaddr *) &sa, sizeof sa); } /* Discard service -- ignore data */ void discard_stream (int s, struct servtab *sep) { int ret; char buffer[BUFSIZE]; set_proc_title (sep->se_service, s); while (1) { while ((ret = read (s, buffer, sizeof buffer)) > 0) ; if (ret == 0 || errno != EINTR) break; } exit (EXIT_SUCCESS); } void /* Discard service -- ignore data */ discard_dg (int s, struct servtab *sep MAYBE_UNUSED) { char buffer[BUFSIZE]; read (s, buffer, sizeof buffer); } #include <ctype.h> #define LINESIZ 72 char ring[128]; char *endring; void initring (void) { int i; endring = ring; for (i = 0; i <= 128; ++i) if (isprint (i)) *endring++ = i; } /* Character generator */ void chargen_stream (int s, struct servtab *sep) { int len; char *rs, text[LINESIZ + 2]; set_proc_title (sep->se_service, s); if (!endring) { initring (); rs = ring; } text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; for (rs = ring;;) { len = endring - rs; if (len >= LINESIZ) memmove (text, rs, LINESIZ); else { memmove (text, rs, len); memmove (text + len, ring, LINESIZ - len); } if (++rs == endring) rs = ring; if (write (s, text, sizeof text) != sizeof text) break; } exit (EXIT_SUCCESS); } /* Character generator */ void chargen_dg (int s, struct servtab *sep MAYBE_UNUSED) { #ifdef IPV6 struct sockaddr_storage sa; #else struct sockaddr sa; #endif static char *rs; int len; socklen_t size; char text[LINESIZ + 2]; if (endring == 0) { initring (); rs = ring; } size = sizeof sa; if (recvfrom (s, text, sizeof text, 0, (struct sockaddr *) &sa, &size) < 0) return; len = endring - rs; if (len >= LINESIZ) memmove (text, rs, LINESIZ); else { memmove (text, rs, len); memmove (text + len, ring, LINESIZ - len); } if (++rs == endring) rs = ring; text[LINESIZ] = '\r'; text[LINESIZ + 1] = '\n'; sendto (s, text, sizeof text, 0, (struct sockaddr *) &sa, sizeof sa); } /* * Return a machine readable date and time, in the form of the * number of seconds since midnight, Jan 1, 1900. Since gettimeofday * returns the number of seconds since midnight, Jan 1, 1970, * we must add 2208988800 seconds to this figure to make up for * some seventy years Bell Labs was asleep. */ long machtime (void) { struct timeval tv; if (gettimeofday (&tv, NULL) < 0) { if (debug) fprintf (stderr, "Unable to get time of day\n"); return 0L; } #define OFFSET ((unsigned long)25567 * 24*60*60) return (htonl ((long) (tv.tv_sec + OFFSET))); #undef OFFSET } void machtime_stream (int s, struct servtab *sep MAYBE_UNUSED) { long result; result = machtime (); write (s, (char *) &result, sizeof result); } void machtime_dg (int s, struct servtab *sep MAYBE_UNUSED) { long result; #ifdef IPV6 struct sockaddr_storage sa; #else struct sockaddr sa; #endif socklen_t size; size = sizeof sa; if (recvfrom (s, (char *) &result, sizeof result, 0, (struct sockaddr *) &sa, &size) < 0) return; result = machtime (); sendto (s, (char *) &result, sizeof result, 0, (struct sockaddr *) &sa, sizeof sa); } void /* Return human-readable time of day */ daytime_stream (int s, struct servtab *sep MAYBE_UNUSED) { char buffer[256]; time_t lclock; lclock = time ((time_t *) 0); sprintf (buffer, "%.24s\r\n", ctime (&lclock)); write (s, buffer, strlen (buffer)); } /* Return human-readable time of day */ void daytime_dg (int s, struct servtab *sep MAYBE_UNUSED) { char buffer[256]; time_t lclock; #ifdef IPV6 struct sockaddr_storage sa; #else struct sockaddr sa; #endif socklen_t size; lclock = time ((time_t *) 0); size = sizeof sa; if (recvfrom (s, buffer, sizeof buffer, 0, (struct sockaddr *) &sa, &size) < 0) return; sprintf (buffer, "%.24s\r\n", ctime (&lclock)); sendto (s, buffer, strlen (buffer), 0, (struct sockaddr *) &sa, sizeof sa); } /* * Based on TCPMUX.C by Mark K. Lottor November 1988 * sri-nic::ps:<mkl>tcpmux.c */ /* # of characters upto \r,\n or \0 */ static int fd_getline (int fd, char *buf, int len) { int count = 0, n; do { n = read (fd, buf, len - count); if (n == 0) return count; if (n < 0) return -1; while (--n >= 0) { if (*buf == '\r' || *buf == '\n' || *buf == '\0') return count; count++; buf++; } } while (count < len); return count; } #define MAX_SERV_LEN (256+2) /* 2 bytes for \r\n */ #define strwrite(fd, buf) write(fd, buf, sizeof(buf)-1) void tcpmux (int s, struct servtab *sep) { char service[MAX_SERV_LEN + 1]; int len; /* Get requested service name */ len = fd_getline (s, service, MAX_SERV_LEN); if (len < 0) { strwrite (s, "-Error reading service name\r\n"); _exit (EXIT_FAILURE); } service[len] = '\0'; if (debug) fprintf (stderr, "tcpmux: someone wants %s\n", service); /* * Help is a required command, and lists available services, * one per line. */ if (!strcasecmp (service, "help")) { for (sep = servtab; sep; sep = sep->se_next) { if (!ISMUX (sep)) continue; write (s, sep->se_service, strlen (sep->se_service)); strwrite (s, "\r\n"); } _exit (EXIT_FAILURE); } /* Try matching a service in inetd.conf with the request */ for (sep = servtab; sep; sep = sep->se_next) { if (ISMUX (sep) && !strcasecmp (service, sep->se_service)) { if (ISMUXPLUS (sep)) { strwrite (s, "+Go\r\n"); } run_service (s, sep); return; } } strwrite (s, "-Service not available\r\n"); exit (EXIT_FAILURE); } /* Set TCP environment variables, modelled after djb's ucspi-tcp tools: http://cr.yp.to/ucspi-tcp/environment.html */ void prepenv (int ctrl, struct sockaddr *sa_client, socklen_t sa_len) { char str[16]; /* IP is used both for numeric addresses and for symbolic ones. * Being statically allocated, and only for logging purposes, * a full MAXPATHLEN is exaggerated, so a compromise is made. */ char ip[4 * INET6_ADDRSTRLEN]; int ret; #ifdef IPV6 struct sockaddr_storage sa_server; #else struct sockaddr_in sa_server; #endif socklen_t len = sizeof (sa_server); setenv ("PROTO", "TCP", 1); unsetenv ("TCPLOCALIP"); unsetenv ("TCPLOCALHOST"); unsetenv ("TCPLOCALPORT"); unsetenv ("TCPREMOTEIP"); unsetenv ("TCPREMOTEPORT"); unsetenv ("TCPREMOTEHOST"); if (getsockname (ctrl, (struct sockaddr *) &sa_server, &len) < 0) syslog (LOG_WARNING, "getsockname(): %m"); else { ret = getnameinfo ((struct sockaddr *) &sa_server, len, ip, sizeof (ip), str, sizeof (str), NI_NUMERICHOST | NI_NUMERICSERV); if (ret == 0) { if (setenv ("TCPLOCALIP", ip, 1) < 0) syslog (LOG_WARNING, "setenv (TCPLOCALIP): %m"); else if (debug) fprintf (stderr, "Assigned TCPLOCALIP = %s\n", ip); if (setenv ("TCPLOCALPORT", str, 1) < 0) syslog (LOG_WARNING, "setenv (TCPLOCALPORT): %m"); } else syslog (LOG_WARNING, "getnameinfo: %s", gai_strerror (ret)); if (resolve_option) { ret = getnameinfo ((struct sockaddr *) &sa_server, len, ip, sizeof (ip), NULL, 0, 0); if (ret != 0) syslog (LOG_WARNING, "getnameinfo: %s", gai_strerror (ret)); else if (setenv ("TCPLOCALHOST", ip, 1) < 0) syslog (LOG_WARNING, "setenv(TCPLOCALHOST): %m"); } } ret = getnameinfo (sa_client, sa_len, ip, sizeof (ip), str, sizeof (str), NI_NUMERICHOST | NI_NUMERICSERV); if (ret == 0) { if (setenv ("TCPREMOTEIP", ip, 1) < 0) syslog (LOG_WARNING, "setenv(TCPREMOTEIP): %m"); else if (debug) fprintf (stderr, "Assigned TCPREMOTEIP = %s\n", ip); if (setenv ("TCPREMOTEPORT", str, 1) < 0) syslog (LOG_WARNING, "setenv(TCPREMOTEPORT): %m"); if (resolve_option) { ret = getnameinfo (sa_client, sa_len, ip, sizeof (ip), NULL, 0, 0); if (ret != 0) syslog (LOG_WARNING, "getnameinfo: %s", gai_strerror (ret)); else if (setenv ("TCPREMOTEHOST", ip, 1) < 0) syslog (LOG_WARNING, "setenv(TCPREMOTEHOST): %m"); else if (debug) fprintf (stderr, "Assigned TCPREMOTEHOST = %s\n", ip); } } else syslog (LOG_WARNING, "getnameinfo: %s", gai_strerror (ret)); } int main (int argc, char *argv[], char *envp[]) { int index; struct servtab *sep; int dofork; pid_t pid; set_program_name (argv[0]); Argv = argv; if (envp == 0 || *envp == 0) envp = argv; while (*envp) envp++; LastArg = envp[-1] + strlen (envp[-1]); /* Parse command line */ iu_argp_init ("inetd", program_authors); argp_parse (&argp, argc, argv, 0, &index, NULL); if (resolve_option) env_option = true; if (index < argc) { int i; config_files = calloc (argc - index + 1, sizeof (*config_files)); for (i = 0; index < argc; index++, i++) { config_files[i] = strdup (argv[index]); } } else { config_files = calloc (3, sizeof (*config_files)); config_files[0] = newstr (PATH_INETDCONF); config_files[1] = newstr (PATH_INETDDIR); } if (!foreground) { if (daemon (0, 0) < 0) { syslog (LOG_DAEMON | LOG_ERR, "%s: Unable to enter daemon mode, %m", argv[0]); exit (EXIT_FAILURE); }; } openlog ("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON); if (pidfile_option) { FILE *fp = fopen (pid_file, "w"); if (fp != NULL) { if (debug) fprintf (stderr, "Using pid-file at \"%s\".\n", pid_file); fprintf (fp, "%d\n", (int) getpid ()); fclose (fp); } else syslog (LOG_CRIT, "can't open %s: %s\n", pid_file, strerror (errno)); } signal_set_handler (SIGALRM, retry); config (0); signal_set_handler (SIGHUP, config); signal_set_handler (SIGCHLD, reapchild); signal_set_handler (SIGPIPE, SIG_IGN); { /* space for daemons to overwrite environment for ps */ #define DUMMYSIZE 100 char dummy[DUMMYSIZE]; memset (dummy, 'x', DUMMYSIZE - 1); dummy[DUMMYSIZE - 1] = '\0'; setenv ("inetd_dummy", dummy, 1); } for (;;) { int n, ctrl; fd_set readable; if (nsock == 0) { SIGSTATUS stat; sigstatus_empty (stat); signal_block (NULL); while (nsock == 0) inetd_pause (stat); signal_unblock (NULL); } readable = allsock; n = select (maxsock + 1, &readable, NULL, NULL, NULL); if (n <= 0) { if (n < 0 && errno != EINTR) syslog (LOG_WARNING, "select: %m"); sleep (1); continue; } for (sep = servtab; n && sep; sep = sep->se_next) if (sep->se_fd != -1 && FD_ISSET (sep->se_fd, &readable)) { n--; if (debug) fprintf (stderr, "someone wants %s\n", sep->se_service); if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { #ifdef IPV6 struct sockaddr_storage sa_client; #else struct sockaddr_in sa_client; #endif socklen_t len = sizeof (sa_client); ctrl = accept (sep->se_fd, (struct sockaddr *) &sa_client, &len); if (debug) fprintf (stderr, "accept, ctrl %d\n", ctrl); if (ctrl < 0) { if (errno != EINTR) syslog (LOG_WARNING, "accept (for %s): %m", sep->se_service); continue; } if (env_option) prepenv (ctrl, (struct sockaddr *) &sa_client, len); } else ctrl = sep->se_fd; signal_block (NULL); pid = 0; dofork = (sep->se_bi == 0 || sep->se_bi->bi_fork); if (dofork) { if (sep->se_count++ == 0) gettimeofday (&sep->se_time, NULL); else if ((sep->se_max && sep->se_count > sep->se_max) || sep->se_count >= toomany) { struct timeval now; gettimeofday (&now, NULL); if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL) { sep->se_time = now; sep->se_count = 1; } else { syslog (LOG_ERR, "%s/%s server failing (looping), service terminated", sep->se_service, sep->se_proto); close_sep (sep); if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) close (ctrl); signal_unblock (NULL); if (!timingout) { timingout = 1; alarm (RETRYTIME); } continue; } } pid = fork (); } if (pid < 0) { syslog (LOG_ERR, "fork: %m"); if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) close (ctrl); signal_unblock (NULL); sleep (1); continue; } if (pid && sep->se_wait) { sep->se_wait = pid; if (sep->se_fd >= 0) { FD_CLR (sep->se_fd, &allsock); nsock--; } } signal_unblock (NULL); if (pid == 0) { if (debug && dofork) setsid (); if (dofork) { int sock; if (debug) fprintf (stderr, "+ Closing from %d\n", maxsock); for (sock = maxsock; sock > 2; sock--) if (sock != ctrl) close (sock); } run_service (ctrl, sep); } if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) close (ctrl); } } }