[BACK]Return to inetd.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / inetd

File: [local] / src / usr.sbin / inetd / inetd.c (download)

Revision 1.165, Sun Sep 3 22:01:00 2023 UTC (8 months, 4 weeks ago) by bluhm
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, HEAD
Changes since 1.164: +2 -2 lines

Allow UDP for built-in inetd(8) services on 127.0.0.1.

This restriction was added in year 2000 due to IPv6 compatible and
mapped addresses.  Nowadays our kernel does not support these IPv6
features and blocks localhost addresses on non-loopback interfaces.
Make IPv4 127.0.0.1/8 and IPv6 ::1 behave identically and provide
local services if configured.

OK mvs@ deraadt@

/*	$OpenBSD: inetd.c,v 1.165 2023/09/03 22:01:00 bluhm Exp $	*/

/*
 * Copyright (c) 1983,1991 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 begin with
 * a space or tab.  All fields must be present in each entry.
 *
 *	service name			must be in /etc/services
 *	socket type			stream/dgram
 *	protocol			must be in /etc/protocols
 *	wait/nowait[.max]		single-threaded/multi-threaded, max #
 *	user[.group] or user[:group]	user/group to run daemon as
 *	server program			full path name
 *	server program arguments	maximum of MAXARGS (20)
 *
 * For RPC services
 *      service name/version            must be in /etc/rpc
 *	socket type			stream/dgram
 *	protocol			must be in /etc/protocols
 *	wait/nowait[.max]		single-threaded/multi-threaded
 *	user[.group] or user[:group]	user to run daemon as
 *	server program			full path name
 *	server program arguments	maximum of MAXARGS (20)
 *
 * For non-RPC services, the "service name" can be of the form
 * hostaddress:servicename, in which case the hostaddress is used
 * as the host portion of the address to listen on.  If hostaddress
 * consists of a single `*' character, INADDR_ANY is used.
 *
 * A line can also consist of just
 *      hostaddress:
 * where hostaddress is as in the preceding paragraph.  Such a line must
 * have no further fields; the specified hostaddress is remembered and
 * used for all further lines that have no hostaddress specified,
 * until the next such line (or EOF).  (This is why * is provided to
 * allow explicit specification of INADDR_ANY.)  A line
 *      *:
 * is implicitly in effect at the beginning of the file.
 *
 * The hostaddress specifier may (and often will) contain dots;
 * the service name must not.
 *
 * For RPC services, host-address specifiers are accepted and will
 * work to some extent; however, because of limitations in the
 * portmapper interface, it will not work to try to give more than
 * one line for any given RPC service, even if the host-address
 * specifiers are different.
 *
 * Comment lines are indicated by a `#' in column 1.
 */

/*
 * Here's the scoop concerning the user[.:]group feature:
 *
 * 1) set-group-option off.
 *
 *	a) user = root:	NO setuid() or setgid() is done
 *
 *	b) other:	setgid(primary group as found in passwd)
 *			initgroups(name, primary group)
 *			setuid()
 *
 * 2) set-group-option on.
 *
 *	a) user = root:	setgid(specified group)
 *			NO initgroups()
 *			NO setuid()
 *
 *	b) other:	setgid(specified group)
 *			initgroups(name, specified group)
 *			setuid()
 *
 */

#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <err.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <netdb.h>
#include <syslog.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <login_cap.h>
#include <ifaddrs.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/nfs_prot.h>
#include <event.h>
#include "pathnames.h"

#define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))

#define	TOOMANY		256		/* 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 */

int	 debug = 0;
int	 maxsock;
int	 toomany = TOOMANY;
int	 timingout;
struct	 servent *sp;
uid_t	 uid;

#ifndef OPEN_MAX
#define OPEN_MAX	64
#endif

/* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */
#define FD_MARGIN	(8)
rlim_t	rlim_nofile_cur = OPEN_MAX;

struct rlimit	rlim_nofile;

struct	servtab {
	char	*se_hostaddr;		/* host address to listen on */
	char	*se_service;		/* name of service */
	int	se_socktype;		/* type of socket to use */
	int	se_family;		/* address family */
	char	*se_proto;		/* protocol used */
	int	se_rpcprog;		/* rpc program number */
	int	se_rpcversl;		/* rpc program lowest version */
	int	se_rpcversh;		/* rpc program highest version */
#define isrpcservice(sep)	((sep)->se_rpcversl != 0)
	pid_t	se_wait;		/* single threaded server */
	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 */
#define	MAXARGV 20
	char	*se_argv[MAXARGV+1];	/* program arguments */
	int	se_fd;			/* open descriptor */
	union {
		struct	sockaddr se_un_ctrladdr;
		struct	sockaddr_in se_un_ctrladdr_in;
		struct	sockaddr_in6 se_un_ctrladdr_in6;
		struct	sockaddr_un se_un_ctrladdr_un;
		struct	sockaddr_storage se_un_ctrladdr_storage;
	} se_un;			/* bound address */
#define se_ctrladdr	se_un.se_un_ctrladdr
#define se_ctrladdr_in	se_un.se_un_ctrladdr_in
#define se_ctrladdr_in6	se_un.se_un_ctrladdr_in6
#define se_ctrladdr_un	se_un.se_un_ctrladdr_un
#define se_ctrladdr_storage	se_un.se_un_ctrladdr_storage
	int	se_ctrladdr_size;
	int	se_max;			/* max # of instances of this service */
	int	se_count;		/* number started since se_time */
	struct	timeval se_time;	/* start of se_count */
	struct	servtab *se_next;
	struct	event se_event;
} *servtab;

void echo_stream(int, struct servtab *);
void discard_stream(int, struct servtab *);
void machtime_stream(int, struct servtab *);
void daytime_stream(int, struct servtab *);
void chargen_stream(int, struct servtab *);
void echo_dg(int, struct servtab *);
void discard_dg(int, struct servtab *);
void machtime_dg(int, struct servtab *);
void daytime_dg(int, struct servtab *);
void chargen_dg(int, struct servtab *);

struct biltin {
	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, struct servtab *);
} 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 },

	{ 0 }
};

struct event evsig_alrm;
struct event evsig_hup;
struct event evsig_chld;
struct event evsig_term;
struct event evsig_int;

void	config(int, short, void *);
void	reap(int, short, void *);
void	retry(int, short, void *);
void	die(int, short, void *);

void	spawn(int, short, void *);
void	gettcp(int, short, void *);
int	setconfig(void);
void	endconfig(void);
void	register_rpc(struct servtab *);
void	unregister_rpc(struct servtab *);
void	freeconfig(struct servtab *);
void	print_service(char *, struct servtab *);
void	setup(struct servtab *);
struct servtab *getconfigent(void);
int	bump_nofile(void);
struct servtab *enter(struct servtab *);
int	matchconf(struct servtab *, struct servtab *);
int	dg_broadcast(struct in_addr *in);

#define NUMINT	(sizeof(intab) / sizeof(struct inent))
char	*CONFIG = _PATH_INETDCONF;

int		dg_badinput(struct sockaddr *sa);
void		inetd_setproctitle(char *a, int s);
void		initring(void);
u_int32_t	machtime(void);

int
main(int argc, char *argv[])
{
	int ch;

	while ((ch = getopt(argc, argv, "dR:")) != -1)
		switch (ch) {
		case 'd':
			debug = 1;
			break;
		case 'R': {	/* invocation rate */
			char *p;
			int val;

			val = strtoul(optarg, &p, 0);
			if (val >= 1 && *p == '\0') {
				toomany = val;
				break;
			}
			syslog(LOG_ERR,
			    "-R %s: bad value for service invocation rate",
			    optarg);
			break;
		}
		default:
			fprintf(stderr,
			    "usage: inetd [-d] [-R rate] [configuration_file]\n");
			exit(1);
		}
	argc -= optind;
	argv += optind;

	uid = getuid();
	if (uid != 0)
		CONFIG = NULL;
	if (argc > 0)
		CONFIG = argv[0];
	if (CONFIG == NULL) {
		fprintf(stderr, "inetd: non-root must specify a config file\n");
		exit(1);
	}
	if (argc > 1) {
		fprintf(stderr, "inetd: more than one argument specified\n");
		exit(1);
	}

	umask(022);
	if (debug == 0) {
		daemon(0, 0);
		if (uid == 0)
			(void) setlogin("");
	}

	if (pledge("stdio rpath cpath getpw dns inet unix proc exec id", NULL) == -1)
		err(1, "pledge");

	if (uid == 0) {
		gid_t gid = getgid();

		/* If run by hand, ensure groups vector gets trashed */
		setgroups(1, &gid);
	}

	openlog("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON);

	if (getrlimit(RLIMIT_NOFILE, &rlim_nofile) == -1) {
		syslog(LOG_ERR, "getrlimit: %m");
	} else {
		rlim_nofile_cur = rlim_nofile.rlim_cur;
		if (rlim_nofile_cur == RLIM_INFINITY)	/* ! */
			rlim_nofile_cur = OPEN_MAX;
	}

	event_init();

	signal_set(&evsig_alrm, SIGALRM, retry, NULL);
	signal_add(&evsig_alrm, NULL);

	config(0, 0, NULL);

	signal_set(&evsig_hup, SIGHUP, config, NULL);
	signal_add(&evsig_hup, NULL);
	signal_set(&evsig_chld, SIGCHLD, reap, NULL);
	signal_add(&evsig_chld, NULL);
	signal_set(&evsig_term, SIGTERM, die, NULL);
	signal_add(&evsig_term, NULL);
	signal_set(&evsig_int, SIGINT, die, NULL);
	signal_add(&evsig_int, NULL);

	signal(SIGPIPE, SIG_IGN);

	event_dispatch();

	return (0);
}

void
gettcp(int fd, short events, void *xsep)
{
	struct servtab *sep = xsep;
	int ctrl;

	if (debug)
		fprintf(stderr, "someone wants %s\n", sep->se_service);

	ctrl = accept(sep->se_fd, NULL, NULL);
	if (debug)
		fprintf(stderr, "accept, ctrl %d\n", ctrl);
	if (ctrl == -1) {
		if (errno != EWOULDBLOCK && errno != EINTR &&
		    errno != ECONNABORTED)
			syslog(LOG_WARNING, "accept (for %s): %m",
			    sep->se_service);
		return;
	}
	if ((sep->se_family == AF_INET || sep->se_family == AF_INET6) &&
	    sep->se_socktype == SOCK_STREAM) {
		struct sockaddr_storage peer;
		socklen_t plen = sizeof(peer);
		char sbuf[NI_MAXSERV];

		if (getpeername(ctrl, (struct sockaddr *)&peer, &plen) == -1) {
			syslog(LOG_WARNING, "could not getpeername");
			close(ctrl);
			return;
		}
		if (getnameinfo((struct sockaddr *)&peer, plen, NULL, 0,
		    sbuf, sizeof(sbuf), NI_NUMERICSERV) == 0 &&
		    strtonum(sbuf, 1, USHRT_MAX, NULL) == 20) {
			/*
			 * ignore things that look like ftp bounce
			 */
			close(ctrl);
			return;
		}
	}

	spawn(ctrl, 0, sep);
}

int
dg_badinput(struct sockaddr *sa)
{
	struct in_addr in;
	struct in6_addr *in6;
	u_int16_t port;

	switch (sa->sa_family) {
	case AF_INET:
		in.s_addr = ntohl(((struct sockaddr_in *)sa)->sin_addr.s_addr);
		port = ntohs(((struct sockaddr_in *)sa)->sin_port);
		if (IN_MULTICAST(in.s_addr))
			goto bad;
		switch ((in.s_addr & 0xff000000) >> 24) {
		case 0: case 255:
			goto bad;
		}
		if (dg_broadcast(&in))
			goto bad;
		break;
	case AF_INET6:
		in6 = &((struct sockaddr_in6 *)sa)->sin6_addr;
		port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
		if (IN6_IS_ADDR_MULTICAST(in6) || IN6_IS_ADDR_UNSPECIFIED(in6))
			goto bad;
		/*
		 * OpenBSD does not support IPv4-mapped and
		 * IPv4-compatible IPv6 addresses (RFC2553). We should
		 * drop the packet.
		 */
		if (IN6_IS_ADDR_V4MAPPED(in6) || IN6_IS_ADDR_V4COMPAT(in6))
			goto bad;
		break;
	default:
		/* Unsupported AF */
		goto bad;
	}

	if (port < IPPORT_RESERVED || port == NFS_PORT)
		goto bad;

	return (0);

bad:
	return (1);
}

int
dg_broadcast(struct in_addr *in)
{
	struct ifaddrs *ifa, *ifap;
	struct sockaddr_in *sin;

	if (getifaddrs(&ifap) == -1)
		return (0);
	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL ||
		    ifa->ifa_addr->sa_family != AF_INET ||
		    (ifa->ifa_flags & IFF_BROADCAST) == 0)
			continue;
		sin = (struct sockaddr_in *)ifa->ifa_broadaddr;
		if (sin->sin_addr.s_addr == in->s_addr) {
			freeifaddrs(ifap);
			return (1);
		}
	}
	freeifaddrs(ifap);
	return (0);
}

void
reap(int sig, short event, void *arg)
{
	struct servtab *sep;
	int status;
	pid_t pid;

	if (debug)
		fprintf(stderr, "reaping asked for\n");

	for (;;) {
		if ((pid = wait3(&status, WNOHANG, NULL)) <= 0) {
			if (pid == -1 && errno == EINTR)
				continue;
			break;
		}
		if (debug)
			fprintf(stderr, "%ld reaped, status %x\n",
			    (long)pid, status);
		for (sep = servtab; sep; sep = sep->se_next)
			if (sep->se_wait == pid) {
				if (WIFEXITED(status) && WEXITSTATUS(status))
					syslog(LOG_WARNING,
					    "%s: exit status %d",
					    sep->se_server, WEXITSTATUS(status));
				else if (WIFSIGNALED(status))
					syslog(LOG_WARNING,
					    "%s: exit signal %d",
					    sep->se_server, WTERMSIG(status));
				sep->se_wait = 1;
				event_add(&sep->se_event, NULL);
				if (debug)
					fprintf(stderr, "restored %s, fd %d\n",
					    sep->se_service, sep->se_fd);
			}
	}
}

void
config(int sig, short event, void *arg)
{
	struct servtab *sep, *cp, **sepp;
	int add;
	char protoname[11];

	if (!setconfig()) {
		syslog(LOG_ERR, "%s: %m", CONFIG);
		exit(1);
	}
	for (sep = servtab; sep; sep = sep->se_next)
		sep->se_checked = 0;
	cp = getconfigent();
	while (cp != NULL) {
		for (sep = servtab; sep; sep = sep->se_next)
			if (matchconf(sep, cp))
				break;
		add = 0;
		if (sep != NULL) {
			int i;

#define SWAP(type, a, b) {type c=(type)a; a=(type)b; b=(type)c;}

			/*
			 * 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;
			SWAP(int, cp->se_max, sep->se_max);
			SWAP(char *, sep->se_user, cp->se_user);
			SWAP(char *, sep->se_group, cp->se_group);
			SWAP(char *, sep->se_server, cp->se_server);
			for (i = 0; i < MAXARGV; i++)
				SWAP(char *, sep->se_argv[i], cp->se_argv[i]);
#undef SWAP
			if (isrpcservice(sep))
				unregister_rpc(sep);
			sep->se_rpcversl = cp->se_rpcversl;
			sep->se_rpcversh = cp->se_rpcversh;
			freeconfig(cp);
			add = 1;
		} else {
			sep = enter(cp);
		}
		sep->se_checked = 1;

		switch (sep->se_family) {
		case AF_UNIX:
			if (sep->se_fd != -1)
				break;
			sep->se_ctrladdr_size =
			    strlcpy(sep->se_ctrladdr_un.sun_path,
			    sep->se_service,
			    sizeof sep->se_ctrladdr_un.sun_path);
			if (sep->se_ctrladdr_size >=
			    sizeof sep->se_ctrladdr_un.sun_path) {
				syslog(LOG_WARNING, "%s/%s: UNIX domain socket "
				    "path too long", sep->se_service,
				    sep->se_proto);
				goto serv_unknown;
			}
			sep->se_ctrladdr_un.sun_family = AF_UNIX;
			sep->se_ctrladdr_size +=
			    1 + sizeof sep->se_ctrladdr_un.sun_family;
			(void)unlink(sep->se_service);
			setup(sep);
			break;
		case AF_INET:
			sep->se_ctrladdr_in.sin_family = AF_INET;
			/* se_ctrladdr_in was set in getconfigent */
			sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in;

			if (isrpcservice(sep)) {
				struct rpcent *rp;

				sep->se_rpcprog = strtonum(sep->se_service,
				    1, USHRT_MAX, NULL);
				if (sep->se_rpcprog == 0) {
					rp = getrpcbyname(sep->se_service);
					if (rp == 0) {
						syslog(LOG_ERR,
						    "%s: unknown rpc service",
						    sep->se_service);
						goto serv_unknown;
					}
					sep->se_rpcprog = rp->r_number;
				}
				if (sep->se_fd == -1)
					setup(sep);
				if (sep->se_fd != -1)
					register_rpc(sep);
			} else {
				u_short port = htons(strtonum(sep->se_service,
				    1, USHRT_MAX, NULL));

				if (!port) {
					(void)strlcpy(protoname, sep->se_proto,
					    sizeof(protoname));
					if (isdigit((unsigned char)
					    protoname[strlen(protoname) - 1]))
						protoname[strlen(protoname) - 1] = '\0';
					sp = getservbyname(sep->se_service,
					    protoname);
					if (sp == 0) {
						syslog(LOG_ERR,
						    "%s/%s: unknown service",
						    sep->se_service, sep->se_proto);
						goto serv_unknown;
					}
					port = sp->s_port;
				}
				if (port != sep->se_ctrladdr_in.sin_port) {
					sep->se_ctrladdr_in.sin_port = port;
					if (sep->se_fd != -1) {
						event_del(&sep->se_event);
						(void) close(sep->se_fd);
					}
					sep->se_fd = -1;
				}
				if (sep->se_fd == -1)
					setup(sep);
			}
			break;
		case AF_INET6:
			sep->se_ctrladdr_in6.sin6_family = AF_INET6;
			/* se_ctrladdr_in was set in getconfigent */
			sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in6;

			if (isrpcservice(sep)) {
				struct rpcent *rp;

				sep->se_rpcprog = strtonum(sep->se_service,
				    1, USHRT_MAX, NULL);
				if (sep->se_rpcprog == 0) {
					rp = getrpcbyname(sep->se_service);
					if (rp == 0) {
						syslog(LOG_ERR,
						    "%s: unknown rpc service",
						    sep->se_service);
						goto serv_unknown;
					}
					sep->se_rpcprog = rp->r_number;
				}
				if (sep->se_fd == -1)
					setup(sep);
				if (sep->se_fd != -1)
					register_rpc(sep);
			} else {
				u_short port = htons(strtonum(sep->se_service,
				    1, USHRT_MAX, NULL));

				if (!port) {
					(void)strlcpy(protoname, sep->se_proto,
					    sizeof(protoname));
					if (isdigit((unsigned char)
					    protoname[strlen(protoname) - 1]))
						protoname[strlen(protoname) - 1] = '\0';
					sp = getservbyname(sep->se_service,
					    protoname);
					if (sp == 0) {
						syslog(LOG_ERR,
						    "%s/%s: unknown service",
						    sep->se_service, sep->se_proto);
						goto serv_unknown;
					}
					port = sp->s_port;
				}
				if (port != sep->se_ctrladdr_in6.sin6_port) {
					sep->se_ctrladdr_in6.sin6_port = port;
					if (sep->se_fd != -1) {
						event_del(&sep->se_event);
						(void) close(sep->se_fd);
					}
					sep->se_fd = -1;
				}
				if (sep->se_fd == -1)
					setup(sep);
			}
			break;
		}
	serv_unknown:
		if (cp->se_next != NULL) {
			struct servtab *tmp = cp;

			cp = cp->se_next;
			free(tmp);
		} else {
			free(cp);
			cp = getconfigent();
		}
		if (debug)
			print_service(add ? "REDO" : "ADD", sep);
	}
	endconfig();
	/*
	 * Purge anything not looked at above.
	 */
	sepp = &servtab;
	while ((sep = *sepp)) {
		if (sep->se_checked) {
			sepp = &sep->se_next;
			continue;
		}
		*sepp = sep->se_next;
		if (sep->se_fd != -1) {
			event_del(&sep->se_event);
			(void) close(sep->se_fd);
		}
		if (isrpcservice(sep))
			unregister_rpc(sep);
		if (sep->se_family == AF_UNIX)
			(void)unlink(sep->se_service);
		if (debug)
			print_service("FREE", sep);
		freeconfig(sep);
		free(sep);
	}
}

void
retry(int sig, short events, void *arg)
{
	struct servtab *sep;

	timingout = 0;
	for (sep = servtab; sep; sep = sep->se_next) {
		if (sep->se_fd == -1) {
			switch (sep->se_family) {
			case AF_UNIX:
			case AF_INET:
			case AF_INET6:
				setup(sep);
				if (sep->se_fd != -1 && isrpcservice(sep))
					register_rpc(sep);
				break;
			}
		}
	}
}

void
die(int sig, short events, void *arg)
{
	struct servtab *sep;

	for (sep = servtab; sep; sep = sep->se_next) {
		if (sep->se_fd == -1)
			continue;

		switch (sep->se_family) {
		case AF_UNIX:
			(void)unlink(sep->se_service);
			break;
		case AF_INET:
		case AF_INET6:
			if (sep->se_wait == 1 && isrpcservice(sep))
				unregister_rpc(sep);
			break;
		}
		(void)close(sep->se_fd);
	}
	exit(0);
}

void
setup(struct servtab *sep)
{
	int on = 1;
	int r;
	mode_t mask = 0;

	if ((sep->se_fd = socket(sep->se_family, sep->se_socktype, 0)) == -1) {
		syslog(LOG_ERR, "%s/%s: socket: %m",
		    sep->se_service, sep->se_proto);
		return;
	}
#define	turnon(fd, opt) \
setsockopt(fd, SOL_SOCKET, opt, &on, sizeof (on))
	if (strncmp(sep->se_proto, "tcp", 3) == 0 && debug &&
	    turnon(sep->se_fd, SO_DEBUG) < 0)
		syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
	if (turnon(sep->se_fd, SO_REUSEADDR) < 0)
		syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m");
#undef turnon
	if (isrpcservice(sep)) {
		struct passwd *pwd;

		/*
		 * for RPC services, attempt to use a reserved port
		 * if they are going to be running as root.
		 *
		 * Also, zero out the port for all RPC services; let bind()
		 * find one.
		 */
		sep->se_ctrladdr_in.sin_port = 0;
		if (sep->se_user && (pwd = getpwnam(sep->se_user)) &&
		    pwd->pw_uid == 0 && uid == 0)
			r = bindresvport(sep->se_fd, &sep->se_ctrladdr_in);
		else {
			r = bind(sep->se_fd, &sep->se_ctrladdr,
			    sep->se_ctrladdr_size);
			if (r == 0) {
				socklen_t len = sep->se_ctrladdr_size;
				int saveerrno = errno;

				/* update se_ctrladdr_in.sin_port */
				r = getsockname(sep->se_fd, &sep->se_ctrladdr,
				    &len);
				if (r <= 0)
					errno = saveerrno;
			}
		}
	} else {
		if (sep->se_family == AF_UNIX)
			mask = umask(0111);
		r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size);
		if (sep->se_family == AF_UNIX)
			umask(mask);
	}
	if (r == -1) {
		syslog(LOG_ERR, "%s/%s: bind: %m",
		    sep->se_service, sep->se_proto);
		(void) close(sep->se_fd);
		sep->se_fd = -1;
		if (!timingout) {
			timingout = 1;
			alarm(RETRYTIME);
		}
		return;
	}
	if (sep->se_socktype == SOCK_STREAM)
		listen(sep->se_fd, 10);

	if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) {
		event_set(&sep->se_event, sep->se_fd, EV_READ|EV_PERSIST,
		    gettcp, sep);
	} else {
		event_set(&sep->se_event, sep->se_fd, EV_READ|EV_PERSIST,
		    spawn, sep);
	}

	event_add(&sep->se_event, NULL);

	if (sep->se_fd > maxsock) {
		maxsock = sep->se_fd;
		if (maxsock > rlim_nofile_cur - FD_MARGIN)
			bump_nofile();
	}
}

void
register_rpc(struct servtab *sep)
{
	socklen_t n;
	struct sockaddr_in sin;
	struct protoent *pp;

	if ((pp = getprotobyname(sep->se_proto+4)) == NULL) {
		syslog(LOG_ERR, "%s: getproto: %m",
		    sep->se_proto);
		return;
	}
	n = sizeof sin;
	if (getsockname(sep->se_fd, (struct sockaddr *)&sin, &n) == -1) {
		syslog(LOG_ERR, "%s/%s: getsockname: %m",
		    sep->se_service, sep->se_proto);
		return;
	}

	for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) {
		if (debug)
			fprintf(stderr, "pmap_set: %u %u %u %u\n",
			    sep->se_rpcprog, n, pp->p_proto,
			    ntohs(sin.sin_port));
		(void)pmap_unset(sep->se_rpcprog, n);
		if (!pmap_set(sep->se_rpcprog, n, pp->p_proto, ntohs(sin.sin_port)))
			syslog(LOG_ERR, "%s %s: pmap_set: %u %u %u %u: %m",
			    sep->se_service, sep->se_proto,
			    sep->se_rpcprog, n, pp->p_proto,
			    ntohs(sin.sin_port));
	}
}

void
unregister_rpc(struct servtab *sep)
{
	int n;

	for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) {
		if (debug)
			fprintf(stderr, "pmap_unset(%u, %u)\n",
			    sep->se_rpcprog, n);
		if (!pmap_unset(sep->se_rpcprog, n))
			syslog(LOG_ERR, "pmap_unset(%u, %u)",
			    sep->se_rpcprog, n);
	}
}


struct servtab *
enter(struct servtab *cp)
{
	struct servtab *sep;

	sep = malloc(sizeof (*sep));
	if (sep == NULL) {
		syslog(LOG_ERR, "Out of memory.");
		exit(1);
	}
	*sep = *cp;
	sep->se_fd = -1;
	sep->se_rpcprog = -1;
	sep->se_next = servtab;
	servtab = sep;
	return (sep);
}

int
matchconf(struct servtab *old, struct servtab *new)
{
	if (strcmp(old->se_service, new->se_service) != 0)
		return (0);

	if (strcmp(old->se_hostaddr, new->se_hostaddr) != 0)
		return (0);

	if (strcmp(old->se_proto, new->se_proto) != 0)
		return (0);

	/*
	 * If the new servtab is bound to a specific address, check that the
	 * old servtab is bound to the same entry. If the new service is not
	 * bound to a specific address then the check of se_hostaddr above
	 * is sufficient.
	 */

	if (old->se_family == AF_INET && new->se_family == AF_INET &&
	    bcmp(&old->se_ctrladdr_in.sin_addr,
	    &new->se_ctrladdr_in.sin_addr,
	    sizeof(new->se_ctrladdr_in.sin_addr)) != 0)
		return (0);

	if (old->se_family == AF_INET6 && new->se_family == AF_INET6 &&
	    bcmp(&old->se_ctrladdr_in6.sin6_addr,
	    &new->se_ctrladdr_in6.sin6_addr,
	    sizeof(new->se_ctrladdr_in6.sin6_addr)) != 0)
		return (0);
	if (old->se_family == AF_INET6 && new->se_family == AF_INET6 &&
	    old->se_ctrladdr_in6.sin6_scope_id !=
	    new->se_ctrladdr_in6.sin6_scope_id)
		return (0);

	return (1);
}

FILE		*fconfig = NULL;
char		line[1024];
char		*defhost;
char		*skip(char **, int);
char		*nextline(FILE *);
char		*newstr(char *);
struct servtab	*dupconfig(struct servtab *);

int
setconfig(void)
{
	free(defhost);
	defhost = newstr("*");
	if (fconfig != NULL) {
		fseek(fconfig, 0L, SEEK_SET);
		return (1);
	}
	fconfig = fopen(CONFIG, "r");
	return (fconfig != NULL);
}

void
endconfig(void)
{
	if (fconfig) {
		(void) fclose(fconfig);
		fconfig = NULL;
	}
	if (defhost) {
		free(defhost);
		defhost = 0;
	}
}

struct servtab *
getconfigent(void)
{
	struct servtab *sep, *tsep;
	char *arg, *cp, *hostdelim, *s;
	int argc;

	sep = calloc(1, sizeof(struct servtab));
	if (sep == NULL) {
		syslog(LOG_ERR, "calloc: %m");
		exit(1);
	}
more:
	freeconfig(sep);

	while ((cp = nextline(fconfig)) && *cp == '#')
		;
	if (cp == NULL) {
		free(sep);
		return (NULL);
	}

	memset(sep, 0, sizeof *sep);
	arg = skip(&cp, 0);
	if (arg == NULL) {
		/* A blank line. */
		goto more;
	}

	/* Check for a host name. */
	hostdelim = strrchr(arg, ':');
	if (hostdelim) {
		*hostdelim = '\0';
		if (arg[0] == '[' && hostdelim > arg && hostdelim[-1] == ']') {
			hostdelim[-1] = '\0';
			sep->se_hostaddr = newstr(arg + 1);
		} else if (hostdelim == arg)
			sep->se_hostaddr = newstr("*");
		else
			sep->se_hostaddr = newstr(arg);
		arg = hostdelim + 1;
		/*
		 * If the line is of the form `host:', then just change the
		 * default host for the following lines.
		 */
		if (*arg == '\0') {
			arg = skip(&cp, 0);
			if (cp == NULL) {
				free(defhost);
				defhost = newstr(sep->se_hostaddr);
				goto more;
			}
		}
	} else
		sep->se_hostaddr = newstr(defhost);

	sep->se_service = newstr(arg);
	if ((arg = skip(&cp, 1)) == NULL)
		goto more;

	if (strcmp(arg, "stream") == 0)
		sep->se_socktype = SOCK_STREAM;
	else if (strcmp(arg, "dgram") == 0)
		sep->se_socktype = SOCK_DGRAM;
	else
		sep->se_socktype = -1;

	if ((arg = skip(&cp, 1)) == NULL)
		goto more;

	sep->se_proto = newstr(arg);

	if (strcmp(sep->se_proto, "unix") == 0) {
		sep->se_family = AF_UNIX;
	} else {
		int s;

		sep->se_family = AF_INET;
		if (sep->se_proto[strlen(sep->se_proto) - 1] == '6')
			sep->se_family = AF_INET6;

		/* check if the family is supported */
		s = socket(sep->se_family, SOCK_DGRAM, 0);
		if (s == -1) {
			syslog(LOG_WARNING, "%s/%s: %s: the address family is "
			    "not supported by the kernel", sep->se_service,
			    sep->se_proto, sep->se_hostaddr);
			goto more;
		}
		close(s);

		if (strncmp(sep->se_proto, "rpc/", 4) == 0) {
			char *cp, *ccp;
			long l;

			cp = strchr(sep->se_service, '/');
			if (cp == 0) {
				syslog(LOG_ERR, "%s: no rpc version",
				    sep->se_service);
				goto more;
			}
			*cp++ = '\0';
			l = strtol(cp, &ccp, 0);
			if (ccp == cp || l < 0 || l > INT_MAX) {
		badafterall:
				syslog(LOG_ERR, "%s/%s: bad rpc version",
				    sep->se_service, cp);
				goto more;
			}
			sep->se_rpcversl = sep->se_rpcversh = l;
			if (*ccp == '-') {
				cp = ccp + 1;
				l = strtol(cp, &ccp, 0);
				if (ccp == cp || l < 0 || l > INT_MAX ||
				    l < sep->se_rpcversl || *ccp)
					goto badafterall;
				sep->se_rpcversh = l;
			} else if (*ccp != '\0')
				goto badafterall;
		}
	}
	arg = skip(&cp, 1);
	if (arg == NULL)
		goto more;

	s = strchr(arg, '.');
	if (s) {
		char *p;

		*s++ = '\0';
		sep->se_max = strtoul(s, &p, 0);
		if (sep->se_max < 1 || *p) {
			syslog(LOG_ERR,
			    "%s: illegal max field \"%s\", setting to %d",
			    sep->se_service, s, toomany);
			sep->se_max = toomany;
		}
	} else
		sep->se_max = toomany;

	sep->se_wait = strcmp(arg, "wait") == 0;
	if ((arg = skip(&cp, 1)) == NULL)
		goto more;
	sep->se_user = newstr(arg);
	arg = strchr(sep->se_user, '.');
	if (arg == NULL)
		arg = strchr(sep->se_user, ':');
	if (arg) {
		*arg++ = '\0';
		sep->se_group = newstr(arg);
	}
	if ((arg = skip(&cp, 1)) == NULL)
		goto more;

	sep->se_server = newstr(arg);
	if (strcmp(sep->se_server, "internal") == 0) {
		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)
				break;
		if (bi->bi_service == 0) {
			syslog(LOG_ERR, "internal service %s unknown",
			    sep->se_service);
			goto more;
		}
		sep->se_bi = bi;
		sep->se_wait = bi->bi_wait;
	} else
		sep->se_bi = NULL;
	argc = 0;
	for (arg = skip(&cp, 0); cp; arg = skip(&cp, 0)) {
		if (argc < MAXARGV)
			sep->se_argv[argc++] = newstr(arg);
	}
	if (argc == 0 && sep->se_bi == NULL) {
		if ((arg = strrchr(sep->se_server, '/')) != NULL)
			arg++;
		else
			arg = sep->se_server;
		sep->se_argv[argc++] = newstr(arg);
	}
	while (argc <= MAXARGV)
		sep->se_argv[argc++] = NULL;

	/*
	 * Resolve each hostname in the se_hostaddr list (if any)
	 * and create a new entry for each resolved address.
	 */
	if (sep->se_hostaddr != NULL && strcmp(sep->se_proto, "unix") != 0) {
		struct addrinfo hints, *res0, *res;
		char *host, *hostlist0, *hostlist, *port;
		int error;

		hostlist = hostlist0 = sep->se_hostaddr;
		sep->se_hostaddr = NULL;
		sep->se_checked = -1;
		while ((host = strsep(&hostlist, ",")) != NULL) {
			if (*host == '\0')
				continue;

			memset(&hints, 0, sizeof(hints));
			hints.ai_family = sep->se_family;
			hints.ai_socktype = sep->se_socktype;
			hints.ai_flags = AI_PASSIVE;
			port = "0";
			error = getaddrinfo(strcmp(host, "*") ? host : NULL,
			    port, &hints, &res0);
			if (error) {
				syslog(LOG_ERR, "%s/%s: %s: %s",
				    sep->se_service, sep->se_proto,
				    host, gai_strerror(error));
				continue;
			}
			for (res = res0; res; res = res->ai_next) {
				/*
				 * If sep is unused, store host in there.
				 * Otherwise, dup a new entry and prepend it.
				 */
				if (sep->se_checked == -1) {
					sep->se_checked = 0;
				} else {
					tsep = dupconfig(sep);
					tsep->se_next = sep;
					sep = tsep;
				}
				sep->se_hostaddr = newstr(host);
				memcpy(&sep->se_ctrladdr_storage,
				    res->ai_addr, res->ai_addrlen);
				sep->se_ctrladdr_size = res->ai_addrlen;
			}
			freeaddrinfo(res0);
		}
		free(hostlist0);
		if (sep->se_checked == -1)
			goto more;	/* no resolvable names/addresses */
	}

	return (sep);
}

void
freeconfig(struct servtab *cp)
{
	int i;

	free(cp->se_hostaddr);
	cp->se_hostaddr = NULL;
	free(cp->se_service);
	cp->se_service = NULL;
	free(cp->se_proto);
	cp->se_proto = NULL;
	free(cp->se_user);
	cp->se_user = NULL;
	free(cp->se_group);
	cp->se_group = NULL;
	free(cp->se_server);
	cp->se_server = NULL;
	for (i = 0; i < MAXARGV; i++) {
		free(cp->se_argv[i]);
		cp->se_argv[i] = NULL;
	}
}

char *
skip(char **cpp, int report)
{
	char *cp = *cpp;
	char *start;

erp:
	if (*cpp == NULL) {
		if (report)
			syslog(LOG_ERR, "syntax error in inetd config file");
		return (NULL);
	}

again:
	while (*cp == ' ' || *cp == '\t')
		cp++;
	if (*cp == '\0') {
		int c;

		c = getc(fconfig);
		(void) ungetc(c, fconfig);
		if (c == ' ' || c == '\t')
			if ((cp = nextline(fconfig)))
				goto again;
		*cpp = NULL;
		goto erp;
	}
	start = cp;
	while (*cp && *cp != ' ' && *cp != '\t')
		cp++;
	if (*cp != '\0')
		*cp++ = '\0';
	if ((*cpp = cp) == NULL)
		goto erp;

	return (start);
}

char *
nextline(FILE *fd)
{
	if (fgets(line, sizeof (line), fd) == NULL)
		return (NULL);
	line[strcspn(line, "\n")] = '\0';
	return (line);
}

char *
newstr(char *cp)
{
	if ((cp = strdup(cp ? cp : "")))
		return(cp);
	syslog(LOG_ERR, "strdup: %m");
	exit(1);
}

struct servtab *
dupconfig(struct servtab *sep)
{
	struct servtab *newtab;
	int argc;

	newtab = calloc(1, sizeof(struct servtab));

	if (newtab == NULL) {
		syslog(LOG_ERR, "calloc: %m");
		exit(1);
	}

	newtab->se_service = sep->se_service ? newstr(sep->se_service) : NULL;
	newtab->se_socktype = sep->se_socktype;
	newtab->se_family = sep->se_family;
	newtab->se_proto = sep->se_proto ? newstr(sep->se_proto) : NULL;
	newtab->se_rpcprog = sep->se_rpcprog;
	newtab->se_rpcversl = sep->se_rpcversl;
	newtab->se_rpcversh = sep->se_rpcversh;
	newtab->se_wait = sep->se_wait;
	newtab->se_user = sep->se_user ? newstr(sep->se_user) : NULL;
	newtab->se_group = sep->se_group ? newstr(sep->se_group) : NULL;
	newtab->se_bi = sep->se_bi;
	newtab->se_server = sep->se_server ? newstr(sep->se_server) : 0;

	for (argc = 0; argc <= MAXARGV; argc++)
		newtab->se_argv[argc] = sep->se_argv[argc] ?
		    newstr(sep->se_argv[argc]) : NULL;
	newtab->se_max = sep->se_max;

	return (newtab);
}

void
inetd_setproctitle(char *a, int s)
{
	socklen_t size;
	struct sockaddr_storage ss;
	char hbuf[NI_MAXHOST];

	size = sizeof(ss);
	if (getpeername(s, (struct sockaddr *)&ss, &size) == 0) {
		if (getnameinfo((struct sockaddr *)&ss, size, hbuf,
		    sizeof(hbuf), NULL, 0, NI_NUMERICHOST) == 0)
			setproctitle("-%s [%s]", a, hbuf);
		else
			setproctitle("-%s [?]", a);
	} else
		setproctitle("-%s", a);
}

int
bump_nofile(void)
{
#define FD_CHUNK	32

	struct rlimit rl;

	if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
		syslog(LOG_ERR, "getrlimit: %m");
		return -1;
	}
	rl.rlim_cur = MINIMUM(rl.rlim_max, rl.rlim_cur + FD_CHUNK);
	rl.rlim_cur = MINIMUM(FD_SETSIZE, rl.rlim_cur + FD_CHUNK);
	if (rl.rlim_cur <= rlim_nofile_cur) {
		syslog(LOG_ERR,
		    "bump_nofile: cannot extend file limit, max = %d",
		    (int)rl.rlim_cur);
		return -1;
	}

	if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
		syslog(LOG_ERR, "setrlimit: %m");
		return -1;
	}

	rlim_nofile_cur = rl.rlim_cur;
	return 0;
}

/*
 * Internet services provided internally by inetd:
 */
#define	BUFSIZE	4096

void
echo_stream(int s, struct servtab *sep)
{
	char buffer[BUFSIZE];
	int i;

	inetd_setproctitle(sep->se_service, s);
	while ((i = read(s, buffer, sizeof(buffer))) > 0 &&
	    write(s, buffer, i) > 0)
		;
	exit(0);
}

void
echo_dg(int s, struct servtab *sep)
{
	char buffer[BUFSIZE];
	int i;
	socklen_t size;
	struct sockaddr_storage ss;

	size = sizeof(ss);
	if ((i = recvfrom(s, buffer, sizeof(buffer), 0,
	    (struct sockaddr *)&ss, &size)) == -1)
		return;
	if (dg_badinput((struct sockaddr *)&ss))
		return;
	(void) sendto(s, buffer, i, 0, (struct sockaddr *)&ss, size);
}

void
discard_stream(int s, struct servtab *sep)
{
	char buffer[BUFSIZE];

	inetd_setproctitle(sep->se_service, s);
	while ((errno = 0, read(s, buffer, sizeof(buffer)) > 0) ||
	    errno == EINTR)
		;
	exit(0);
}

void
discard_dg(int s, struct servtab *sep)
{
	char buffer[BUFSIZE];

	(void) read(s, buffer, sizeof(buffer));
}

#define LINESIZ 72
char ring[128];
char *endring;

void
initring(void)
{
	int i;

	endring = ring;

	for (i = 0; i <= sizeof ring; ++i)
		if (isprint((unsigned char)i))
			*endring++ = i;
}

void
chargen_stream(int s, struct servtab *sep)
{
	char *rs;
	int len;
	char text[LINESIZ+2];

	inetd_setproctitle(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);
}

void
chargen_dg(int s, struct servtab *sep)
{
	struct sockaddr_storage ss;
	static char *rs;
	int len;
	socklen_t size;
	char text[LINESIZ+2];

	if (endring == 0) {
		initring();
		rs = ring;
	}

	size = sizeof(ss);
	if (recvfrom(s, text, sizeof(text), 0, (struct sockaddr *)&ss,
	    &size) == -1)
		return;
	if (dg_badinput((struct sockaddr *)&ss))
		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';
	(void) sendto(s, text, sizeof(text), 0, (struct sockaddr *)&ss, size);
}

/*
 * 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.
 */
u_int32_t
machtime(void)
{
	struct timeval tv;

	if (gettimeofday(&tv, NULL) == -1)
		return (0L);

	return (htonl((u_int32_t)tv.tv_sec + 2208988800UL));
}

void
machtime_stream(int s, struct servtab *sep)
{
	u_int32_t result;

	result = machtime();
	(void) write(s, &result, sizeof(result));
}

void
machtime_dg(int s, struct servtab *sep)
{
	u_int32_t result;
	struct sockaddr_storage ss;
	socklen_t size;

	size = sizeof(ss);
	if (recvfrom(s, &result, sizeof(result), 0,
	    (struct sockaddr *)&ss, &size) == -1)
		return;
	if (dg_badinput((struct sockaddr *)&ss))
		return;
	result = machtime();
	(void) sendto(s, &result, sizeof(result), 0,
	    (struct sockaddr *)&ss, size);
}

/* Return human-readable time of day */
void
daytime_stream(int s, struct servtab *sep)
{
	char buffer[256];
	time_t clock;

	clock = time(NULL);

	(void) snprintf(buffer, sizeof buffer, "%.24s\r\n", ctime(&clock));
	(void) write(s, buffer, strlen(buffer));
}

/* Return human-readable time of day */
void
daytime_dg(int s, struct servtab *sep)
{
	char buffer[256];
	time_t clock;
	struct sockaddr_storage ss;
	socklen_t size;

	clock = time(NULL);

	size = sizeof(ss);
	if (recvfrom(s, buffer, sizeof(buffer), 0, (struct sockaddr *)&ss,
	    &size) == -1)
		return;
	if (dg_badinput((struct sockaddr *)&ss))
		return;
	(void) snprintf(buffer, sizeof buffer, "%.24s\r\n", ctime(&clock));
	(void) sendto(s, buffer, strlen(buffer), 0, (struct sockaddr *)&ss,
	    size);
}

/*
 * print_service:
 *	Dump relevant information to stderr
 */
void
print_service(char *action, struct servtab *sep)
{
	if (strcmp(sep->se_hostaddr, "*") == 0)
		fprintf(stderr, "%s: %s ", action, sep->se_service);
	else
		fprintf(stderr, "%s: %s:%s ", action, sep->se_hostaddr,
		    sep->se_service);

	if (isrpcservice(sep))
		fprintf(stderr, "rpcprog=%d, rpcvers=%d/%d, proto=%s,",
		    sep->se_rpcprog, sep->se_rpcversh,
		    sep->se_rpcversl, sep->se_proto);
	else
		fprintf(stderr, "proto=%s,", sep->se_proto);

	fprintf(stderr,
	    " wait.max=%d.%d user:group=%s:%s builtin=%lx server=%s\n",
	    sep->se_wait, sep->se_max, sep->se_user,
	    sep->se_group ? sep->se_group : "wheel",
	    (long)sep->se_bi, sep->se_server);
}

void
spawn(int ctrl, short events, void *xsep)
{
	struct servtab *sep = xsep;
	struct passwd *pwd;
	int tmpint, dofork;
	struct group *grp = NULL;
	char buf[50];
	pid_t pid;

	if (debug)
		fprintf(stderr, "someone wants %s\n", sep->se_service);

	pid = 0;
	dofork = (sep->se_bi == 0 || sep->se_bi->bi_fork);
	if (dofork) {
		if (sep->se_count++ == 0)
		    (void)gettimeofday(&sep->se_time, NULL);
		else if (sep->se_count >= sep->se_max) {
			struct timeval now;

			(void)gettimeofday(&now, NULL);
			if (now.tv_sec - sep->se_time.tv_sec >
			    CNT_INTVL) {
				sep->se_time = now;
				sep->se_count = 1;
			} else {
				if (!sep->se_wait &&
				    sep->se_socktype == SOCK_STREAM)
					close(ctrl);
				if (sep->se_family == AF_INET &&
				    ntohs(sep->se_ctrladdr_in.sin_port) >=
				    IPPORT_RESERVED) {
					/*
					 * Cannot close it -- there are
					 * thieves on the system.
					 * Simply ignore the connection.
					 */
					--sep->se_count;
					return;
				}
				syslog(LOG_ERR,
				    "%s/%s server failing (looping), service terminated",
				    sep->se_service, sep->se_proto);
				if (!sep->se_wait &&
				    sep->se_socktype == SOCK_STREAM)
					close(ctrl);
				event_del(&sep->se_event);
				(void) close(sep->se_fd);

				sep->se_fd = -1;
				sep->se_count = 0;
				if (!timingout) {
					timingout = 1;
					alarm(RETRYTIME);
				}
				return;
			}
		}
		pid = fork();
	}
	if (pid == -1) {
		syslog(LOG_ERR, "fork: %m");
		if (!sep->se_wait && sep->se_socktype == SOCK_STREAM)
			close(ctrl);
		sleep(1);
		return;
	}

	if (pid && sep->se_wait) {
		sep->se_wait = pid;
		event_del(&sep->se_event);
	}
	if (pid == 0) {
		if (sep->se_bi) {
			if (dofork && pledge("stdio inet", NULL) == -1)
				err(1, "pledge");
			(*sep->se_bi->bi_fn)(ctrl, sep);
		} else {
			if ((pwd = getpwnam(sep->se_user)) == NULL) {
				syslog(LOG_ERR,
				    "getpwnam: %s: No such user",
				    sep->se_user);
				if (sep->se_socktype != SOCK_STREAM)
					recv(0, buf, sizeof (buf), 0);
				exit(1);
			}
			if (setsid() <0)
				syslog(LOG_ERR, "%s: setsid: %m",
				    sep->se_service);
			if (sep->se_group &&
			    (grp = getgrnam(sep->se_group)) == NULL) {
				syslog(LOG_ERR,
				    "getgrnam: %s: No such group",
				    sep->se_group);
				if (sep->se_socktype != SOCK_STREAM)
					recv(0, buf, sizeof (buf), 0);
				exit(1);
			}
			if (uid != 0) {
				/* a user running private inetd */
				if (uid != pwd->pw_uid)
					exit(1);
			} else {
				tmpint = LOGIN_SETALL &
				    ~(LOGIN_SETGROUP|LOGIN_SETLOGIN);
				if (pwd->pw_uid)
					tmpint |= LOGIN_SETGROUP|LOGIN_SETLOGIN;
				if (sep->se_group) {
					pwd->pw_gid = grp->gr_gid;
					tmpint |= LOGIN_SETGROUP;
				}
				if (setusercontext(NULL, pwd, pwd->pw_uid,
				    tmpint) == -1) {
					syslog(LOG_ERR,
					    "%s/%s: setusercontext: %m",
					    sep->se_service, sep->se_proto);
					exit(1);
				}
			}
			if (debug)
				fprintf(stderr, "%ld execv %s\n",
				    (long)getpid(), sep->se_server);
			if (ctrl != STDIN_FILENO) {
				dup2(ctrl, STDIN_FILENO);
				close(ctrl);
			}
			dup2(STDIN_FILENO, STDOUT_FILENO);
			dup2(STDIN_FILENO, STDERR_FILENO);
			closelog();
			closefrom(3);
			signal(SIGPIPE, SIG_DFL);
			execv(sep->se_server, sep->se_argv);
			if (sep->se_socktype != SOCK_STREAM)
				recv(0, buf, sizeof (buf), 0);
			syslog(LOG_ERR, "execv %s: %m", sep->se_server);
			exit(1);
		}
	}
	if (!sep->se_wait && sep->se_socktype == SOCK_STREAM)
		close(ctrl);
}