Files
inetutils/inetd/inetd.c
Alfred M. Szmidt c583418f78 Updated copyright years for 2010.
Happy New Year!
2010-01-02 14:58:43 +01:00

2078 lines
48 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 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 user 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#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>
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include <grp.h>
#include "libinetutils.h"
#include "argcv.h"
#include "version-etc.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;
int 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 */
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},
{"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}
};
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
char *p;
int number;
switch (key)
{
case 'd':
debug = true;
options |= SO_DEBUG;
break;
case OPT_ENVIRON:
env_option = true;
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};
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 */
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 */
#if HAVE_GETADDRINFO
struct sockaddr_storage se_ctrladdr; /* bound address */
#else
struct sockaddr_in se_ctrladdr; /* bound address */
#endif
unsigned se_refcnt;
int 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, RETSIGTYPE (*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;
char buf[50];
if (sep->se_bi)
{
(*sep->se_bi->bi_fn) (ctrl, sep);
}
else
{
if (debug)
fprintf (stderr, "%d execl %s\n", getpid (), sep->se_server);
dup2 (ctrl, 0);
close (ctrl);
dup2 (0, 1);
dup2 (0, 2);
if ((pwd = getpwnam (sep->se_user)) == 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 (1);
}
if (pwd->pw_uid)
{
if (setgid (pwd->pw_gid) < 0)
{
syslog (LOG_ERR, "%s: can't set gid %d: %m",
sep->se_service, pwd->pw_gid);
_exit (1);
}
#ifdef HAVE_INITGROUPS
initgroups (pwd->pw_name, 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 (1);
}
}
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 (1);
}
}
void
reapchild (int signo ARG_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", 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;
if ((s = strdup (cp ? cp : "")))
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 builtin=%lx server=%s\n",
sep->se_file, sep->se_line,
action,
sep->se_node ? sep->se_node : "*", sep->se_service, sep->se_proto,
sep->se_wait, sep->se_max, sep->se_user,
(long) sep->se_bi, sep->se_server);
}
/* Configuration */
#if HAVE_GETADDRINFO
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 IPv6 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 `tcp' as a protocol type all connections will be mapped
to IPv6, and with `tcp6' they get mapped IPv4 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)
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,
sizeof (sep->se_ctrladdr));
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 IPv6 silently. */
sep->se_family = AF_INET;
close (sep->se_fd);
goto tryagain;
}
if (debug)
fprintf (stderr, "bind failed on %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;
}
#else
void
setup (struct servtab *sep)
{
int err;
const int on = 1;
sep->se_fd = socket (sep->se_family, sep->se_socktype, 0);
if (sep->se_fd < 0)
{
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;
}
if (strncmp (sep->se_proto, "tcp", 3) == 0 && (options & SO_DEBUG))
{
err = setsockopt (sep->se_fd, SOL_SOCKET, SO_DEBUG,
(char *) &on, sizeof (on));
if (err < 0)
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,
sizeof (sep->se_ctrladdr));
if (err < 0)
{
if (debug)
fprintf (stderr, "bind failed on %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;
}
#endif
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 ARG_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 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;
int 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_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_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;
}
#if HAVE_GETADDRINFO
int
inetd_getaddrinfo (struct servtab *sep, int proto, struct addrinfo **result)
{
struct addrinfo hints;
memset (&hints, 0, sizeof (hints));
hints.ai_flags = AI_PASSIVE;
#ifdef AI_V4MAPPED
if (sep->se_v4mapped)
hints.ai_flags |= AI_V4MAPPED;
#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 IPv6 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);
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)
{
memcpy (&sep->se_ctrladdr, rp->ai_addr, rp->ai_addrlen);
cp = enter (sep);
servent_setup (cp);
}
freeaddrinfo (result);
return 0;
}
#else
int
expand_enter (struct servtab *sep)
{
struct servent *sp;
sp = getservbyname (sep->se_service, sep->se_proto);
if (sp == 0)
{
static struct servent servent;
char *p;
unsigned long val;
unsigned short port;
val = strtoul (sep->se_service, &p, 0);
if (*p || (port = val) != val)
{
syslog (LOG_ERR, "%s/%s: unknown service",
sep->se_service, sep->se_proto);
sep->se_checked = 0;
return 1;
}
servent.s_port = htons (port);
sp = &servent;
}
if (sp->s_port != sep->se_ctrladdr.sin_port)
{
sep->se_ctrladdr.sin_family = AF_INET;
sep->se_ctrladdr.sin_port = sp->s_port;
}
if (sep->se_node == NULL)
{
cp = enter (sep);
servent_setup (cp);
}
else
{
char **p;
struct hostent *host = gethostbyname (sep->se_node);
if (!host)
{
syslog (LOG_ERR, "%s/%s: unknown host %s",
sep->se_service, sep->se_proto, sep->se_node);
return 1;
}
if (host->h_addrtype != AF_INET)
{
syslog (LOG_ERR, "%s/%s: unsupported address family %d",
sep->se_service, sep->se_proto, host->h_addrtype);
return 1;
}
for (p = host->h_addr_list; *p; p++)
{
memcpy (&sep->se_ctrladdr.sin_addr.s_addr, *p, host->h_length);
cp = enter (sep);
servent_setup (cp);
}
}
return 0;
}
#endif
/* 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_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 = strchr (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 IPv6, in setup() we'll fall back to IPv4 if
it doesn't work. */
sep->se_family = AF_INET6;
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;
}
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;
}
}
sep->se_user = newstr (argv[INETD_USER]);
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;
}
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;
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)))
{
if ((pwd = getpwnam (sep->se_user)) == NULL)
{
syslog (LOG_ERR, "%s/%s: No such user '%s', service ignored",
sep->se_service, sep->se_proto, sep->se_user);
continue;
}
if (ISMUX (sep))
{
sep->se_fd = -1;
sep->se_checked = 1;
enter (sep);
}
else
expand_enter (sep);
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 ()
{
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_service = newstr ("tcpmux");
serv.se_socktype = SOCK_STREAM;
serv.se_proto = newstr ("tcp");
serv.se_checked = 1;
serv.se_user = newstr ("root");
serv.se_bi = bi_lookup (&serv);
if (!serv.se_bi)
{
/* Should not happen */
freeconfig (&serv);
if (debug)
fprintf (stderr, "INETERNAL ERROR: could not found tcpmux built-in");
syslog (LOG_ERR, "INETERNAL ERROR: could not found 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_family = AF_INET6;
serv.se_v4mapped = 1;
#else
serv.se_family = AF_INET;
#endif
if (debug)
fprintf (stderr, "inserting default tcpmux entry\n");
syslog (LOG_INFO, "inserting default tcpmux entry");
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 (0);
}
/* Echo service -- echo data back */
void
echo_dg (int s, struct servtab *sep)
{
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 (0);
}
void
/* Discard service -- ignore data */
discard_dg (int s, struct servtab *sep)
{
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;;)
{
if ((len = endring - rs) >= 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 (0);
}
/* Character generator */
void
chargen_dg (int s, struct servtab *sep)
{
#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;
if ((len = endring - rs) >= 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 ((u_long)25567 * 24*60*60)
return (htonl ((long) (tv.tv_sec + OFFSET)));
#undef OFFSET
}
void
machtime_stream (int s, struct servtab *sep)
{
long result;
result = machtime ();
write (s, (char *) &result, sizeof result);
}
void
machtime_dg (int s, struct servtab *sep)
{
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)
{
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)
{
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 */
if ((len = fd_getline (s, service, MAX_SERV_LEN)) < 0)
{
strwrite (s, "-Error reading service name\r\n");
_exit (1);
}
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 (1);
}
/* 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 (1);
}
/* Set TCP environment variables, modelled after djb's ucspi-tcp tools:
http://cr.yp.to/ucspi-tcp/environment.html
FIXME: This needs support for IPv6
*/
void
prepenv (int ctrl, struct sockaddr_in sa_client)
{
char str[16];
char *ip;
struct hostent *host;
struct sockaddr_in sa_server;
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
{
ip = inet_ntoa (sa_server.sin_addr);
if (ip)
{
if (setenv ("TCPLOCALIP", ip, 1) < 0)
syslog (LOG_WARNING, "setenv (TCPLOCALIP): %m");
}
snprintf (str, sizeof (str), "%d", ntohs (sa_server.sin_port));
setenv ("TCPLOCALPORT", str, 1);
if (resolve_option)
{
if ((host = gethostbyaddr ((char *) &sa_server.sin_addr,
sizeof (sa_server.sin_addr),
AF_INET)) == NULL)
syslog (LOG_WARNING, "gethostbyaddr: %m");
else if (setenv ("TCPLOCALHOST", host->h_name, 1) < 0)
syslog (LOG_WARNING, "setenv(TCPLOCALHOST): %m");
}
}
ip = inet_ntoa (sa_client.sin_addr);
if (ip)
{
if (setenv ("TCPREMOTEIP", ip, 1) < 0)
syslog (LOG_WARNING, "setenv(TCPREMOTEIP): %m");
}
snprintf (str, sizeof (str), "%d", ntohs (sa_client.sin_port));
if (setenv ("TCPREMOTEPORT", str, 1) < 0)
syslog (LOG_WARNING, "setenv(TCPREMOTEPORT): %m");
if (resolve_option)
{
if ((host = gethostbyaddr ((char *) &sa_client.sin_addr,
sizeof (sa_client.sin_addr),
AF_INET)) == NULL)
syslog (LOG_WARNING, "gethostbyaddr: %m");
else if (setenv ("TCPREMOTEHOST", host->h_name, 1) < 0)
syslog (LOG_WARNING, "setenv(TCPREMOTEHOST): %m");
}
}
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)
{
daemon (0, 0);
}
openlog ("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON);
{
FILE *fp = fopen (PATH_INETDPID, "w");
if (fp != NULL)
{
fprintf (fp, "%d\n", getpid ());
fclose (fp);
}
else
syslog (LOG_CRIT, "can't open %s: %s\n", PATH_INETDPID,
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;
if ((n = select (maxsock + 1, &readable, NULL, NULL, NULL)) <= 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)
{
struct sockaddr_in sa_client;
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, sa_client);
}
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);
}
}
}