/
niceSOFT
/
inetutils
Обзор
Документация
Войти
/
niceSOFT
/
inetutils
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
v2.0
src/inetd.c
2 140 строк
51 KB
Alfred M. Szmidt
Happy GNU 2021 year!
01 янв 2021, 13:35
01 янв 2021, 13:35
911413e
Код
Авторство
О чём код?
/* Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 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 "unused-parameter.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 #define SIGBLOCK (sigmask(SIGCHLD)|sigmask(SIGHUP)|sigmask(SIGALRM)) bool debug = 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_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 {"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 _GL_UNUSED_PARAMETER) { char *p; int number; switch (key) { case 'd': debug = true; options |= SO_DEBUG; 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} }; #define NUMINT (sizeof(intab) / sizeof(struct inent)) 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) ()) { #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 _GL_UNUSED_PARAMETER) { 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 _GL_UNUSED_PARAMETER) { 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 deamon: 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 ((caddr_t) sep, 0, sizeof *sep); while (1) { argcv_free (argc, argv); freeconfig (sep); memset ((caddr_t) 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 _GL_UNUSED_PARAMETER) { 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 _GL_UNUSED_PARAMETER) { 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 _GL_UNUSED_PARAMETER) { #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 _GL_UNUSED_PARAMETER) { long result; result = machtime (); write (s, (char *) &result, sizeof result); } void machtime_dg (int s, struct servtab *sep _GL_UNUSED_PARAMETER) { 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 _GL_UNUSED_PARAMETER) { 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 _GL_UNUSED_PARAMETER) { 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 (!debug) { 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); } } }