/*
**  This file is derived from inetd.c
**	"@(#)from: inetd.c	8.4 (Berkeley) 4/13/94";
**  as available in the OpenBSD source tree.
*/

/*
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. 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.
 */

#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1983, 1991, 1993, 1994\n\
	The Regents of the University of California.  All rights reserved.\n";
#endif /* not lint */

#ifndef lint
#if 0
static char sccsid[] = "@(#)from: inetd.c	8.4 (Berkeley) 4/13/94";
#endif
#endif /* not lint */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: inetd.c,v 1.83 2007/09/30 03:09:39 ca Exp $")

/*
 * MCP - Master Control Program
 * (based on inetd)
 *
 * This program invokes all programs as needed.
 *
 * MCP uses a configuration file which is read at startup
 * and, possibly, at some later time in response to a hangup signal.
 */

#include "sm/error.h"
#include "sm/assert.h"
#include "sm/memops.h"
#include "sm/ctype.h"
#include "sm/unixsock.h"
#include "sm/cmsg.h"

#include "sm/param.h"
#include "sm/stat.h"
#include <sys/ioctl.h>
#include "sm/socket.h"
#include "sm/filio.h"
#include "sm/wait.h"
#include "sm/time.h"
#include <sys/resource.h>

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include <errno.h>
#include "sm/fcntl.h"
#include <grp.h>
#include <netdb.h>
#include "sm/pwd.h"
#include "sm/signal.h"
#include <stdio.h>
#include <stdlib.h>
#include "sm/string.h"
#include "sm/syslog.h"
#include <unistd.h>
#include "sm/sysexits.h"
#include "sm/misc.h"
#include <stdarg.h>

#ifdef LOGIN_CAP
#include <login_cap.h>

/* see init.c */
#define RESOURCE_RC "daemon"
#endif

#ifndef MAXCHILD
/* maximum number of this service; < 0 = no limit */
#define MAXCHILD	-1
#endif

#ifndef MAXCPM
/* rate limit invocations from a single remote address, < 0 = no limit */
#define MAXCPM		-1
#endif

#ifndef MAX_FAILS
#define MAX_FAILS	4	/* maximum number of failures */
#endif
#define FAIL_TIMEOUT	60	/* reset failure counter after this */

#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 */
#define MAX_MAXCHLD	32767		/* max allowable max children */
#define DEVNULL	"/dev/null"

static char *progname = "mcp";

static FILE *Err_file = NULL; /* file to use for error output */

#if SM_HEAP_CHECK
#include "sm/io.h"
extern SM_DEBUG_T SmHeapCheck;
# define HEAP_CHECK (SmHeapCheck > 0)
#else
# define HEAP_CHECK 0
#endif

/*
**  Todo:
**  cleanup.
**  use a context structure, not globals.
**  abstract out signal handling code (can only one version be used?)
**  use something else than syslog()? [later on...]
*/

static void
vwarnc(int code, const char *fmt, va_list ap)
{
	fprintf(Err_file, "%ld: %s: ", (long) time(0), progname);
	if (fmt != NULL)
	{
		vfprintf(Err_file, fmt, ap);
		fprintf(Err_file, ": ");
	}
	fprintf(Err_file, "%s\n", strerror(code));
}

static void PRINTFLIKE(1, 2)
warn(const char *fmt,...)
{
	va_list ap;

	va_start(ap, fmt);
	vwarnc(errno, fmt, ap);
	va_end(ap);
}

static void
vwarnx(const char *fmt, va_list ap)
{
	fprintf(Err_file, "%ld: %s: ", (long) time(0), progname);
	if (fmt != NULL)
		vfprintf(Err_file, fmt, ap);
	fprintf(Err_file, "\n");
}

static void PRINTFLIKE(1, 2)
warnx(const char *fmt,...)
{
	va_list ap;

	va_start(ap, fmt);
	vwarnx(fmt, ap);
	va_end(ap);
}

int			Debug = 0;
bool		UseSyslog = true;
fd_set			Allsock;
int			Minchild = 0;
int			Maxchild = MAXCHILD;
sigset_t		Blockmask;

static bool		Log = false;
static bool		TestOnly = false;
static int		Nsock, Maxsock;
static int		Options = 0;
static bool		Timingout = false;
static int		Toomany = TOOMANY;
static int		Maxfails = MAX_FAILS;
static int		Maxcpm = MAXCPM;
struct in_addr		Bind_address;
static int		Signalpipe[2];
static sigset_t		Emptymask;
static char		Logdir[128];

#include "mcp.h"
#include "inetdconf.h"

servtab_P Servtab;

#define MCP_ST_NONE	0u
#define MCP_ST_RUNNING	1u
#define MCP_ST_STOPPING	4u
#define MCP_ST_STOPPED	8u

#define MSP_IS_RUNNING(mcp_ctx)	((mcp_ctx)->mcp_status == MCP_ST_RUNNING)
#define MSP_IS_SHUTTING_DOWN(mcp_ctx)	((mcp_ctx)->mcp_status >= MCP_ST_STOPPING)

#define MCP_FL_NONE		0x0000u

/* request restart of all processes */
#define MCP_FL_RESTART		0x0001u
#define MCP_FL_RESTARTING	0x0002u

/* restarting stages */
#define MCP_FL_RS_STOP		0x0004u	/* stopping */
#define MCP_FL_RS_START		0x0008u	/* starting */

#define MCP_SET_FLAG(mcp_ctx, fl)	(mcp_ctx)->mcp_flags |= (fl)
#define MCP_CLR_FLAG(mcp_ctx, fl)	(mcp_ctx)->mcp_flags &= ~(fl)
#define MCP_IS_FLAG(mcp_ctx, fl)	(((mcp_ctx)->mcp_flags & (fl)) != 0)

/* prototypes */
static void	 flag_signal(char _c);
static void	 flag_config(int _signo);
static void	 flag_term(int _signo);
static void	 flag_int(int _signo);
static void	 se_addchild(servtab_P _sep, pid_t _pid, uint _id);
static void	 flag_reapchild(int _signo);
static void	 se_reapchild(mcp_ctx_P _mcp_ctx);
void		 se_enable(servtab_P _sep, bool _completely);
void		 se_disable(servtab_P _sep, bool _completely);
static void	 flag_retry(int _signo);
static void	 se_retry(void);
#if MTA_USE_CPML
static int	 cpmip(servtab_P _sep, int _ctrl);
#endif
static int	 se_startproc(mcp_ctx_P _mcp_ctx, servtab_P _sep, int _fd, struct sigaction *_sapipe);
static void	 se_startall(mcp_ctx_P _mcp_ctx, servtab_P _servtab, struct sigaction *_sapipe, bool _marked);
static void	 se_stopall(servtab_P _servtab, bool _marked);
static void	 flag_usr1(int _signo);
static void	 flag_usr2(int _signo);
static void	 se_signalchildren(servtab_P _servtab, char _c, bool _marked);
static bool	 se_depstopped(servtab_P _servtab);

char		*CONFIG = "/etc/meta1/meta1.conf";
static char	*Pid_file = "/var/run/mcp.pid";
static int	 Pid_fd = -1;
#ifdef OLD_SETPROCTITLE
char		**Argv;
char		 *LastArg;
#endif

void PRINTFLIKE(2, 3)
m_syslog(int priority, const char *fmt,...)
{
	va_list ap;

	va_start(ap, fmt);
	if (UseSyslog)
		vsyslog(priority, fmt, ap);
	else
		vwarnx(fmt, ap);
	va_end(ap);
}

/*
**  SE_GETVALUE -- get an integer value
**
**	Parameters:
**		arg -- argument from which to read value
**		value -- (pointer to) integer value (output)
**		whine -- error message if something goes wrong
**
**	Returns:
**		true iff value could be read
*/

static bool
se_getvalue(char *arg, int *value, char *whine)
{
	int tmp;
	char *p;

	tmp = strtol(arg, &p, 0);
	if (tmp < 1 || *p)
	{
		m_syslog(LOG_ERR, whine, arg);
		return false;	/* failure */
	}
	*value = tmp;
	return true;		/* success */
}

/*
**  SM_EXIT -- cleanup and sm_exit()
**
**	Parameters:
**		value -- sm_exit() code
**
**	Returns:
**		doesn't
*/

void
sm_exit(int value)
{
	if (Pid_fd >= 0)
	{
		close(Pid_fd);
		Pid_fd = -1;
		(void) unlink(Pid_file);
	}
	exit(value);
	/* NOTREACHED */
	SM_ASSERT(false);
}

/*
**  MAIN -- Master Control Program
**
**	Parameters:
**		argc -- number of arguments
**		argv -- vector of arguments
**		envp -- environment
**
**	Returns:
**		exit code
*/

int
main(int argc, char *argv[], char *envp[])
{
	servtab_P sep;
	struct sigaction sa, sapipe;
	int ch;
	struct sockaddr_in peer;
	int i;
#ifdef LOGIN_CAP
	login_cap_t *lc = NULL;
#endif
	mcp_ctx_T mcp_ctx;

	Err_file = stderr;

#if SM_HEAP_CHECK
	SmHeapCheck = 1;
#endif

#ifdef OLD_SETPROCTITLE
	Argv = argv;
	if (envp == 0 || *envp == 0)
		envp = argv;
	while (*envp)
		envp++;
	LastArg = envp[-1] + strlen(envp[-1]);
#endif

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

	sm_memzero(&mcp_ctx, sizeof(mcp_ctx));
	mcp_ctx.mcp_status = MCP_ST_NONE;
	mcp_ctx.mcp_flags = MCP_FL_NONE;

	Bind_address.s_addr = htonl(INADDR_ANY);
	Logdir[0] = '\0';
	while ((ch = getopt(argc, argv, "a:c:C:Ddf:hlL:R:tp:")) != -1)
	{
		switch (ch)
		{
		case 'a':
			if (!inet_aton(optarg, &Bind_address))
			{
				m_syslog(LOG_ERR,
				       "-a %s: invalid IP address", optarg);
				sm_exit(EX_USAGE);
			}
			break;
		case 'c':
			se_getvalue(optarg, &Maxchild,
				 "-c %s: bad value for maximum children");
			break;
		case 'C':
			se_getvalue(optarg, &Maxcpm,
			      "-C %s: bad value for maximum children/minute");
			break;
		case 'D':
			UseSyslog = false;
			break;
		case 'd':
			++Debug;
			Options |= SO_DEBUG;
			break;
		case 'f':
			se_getvalue(optarg, &Maxfails,
				 "-f %s: bad value for maximum failures");
			break;
		case 'l':
			Log = true;
			break;
		case 'L':
			if (strlcpy(Logdir, optarg, sizeof(Logdir))
				>= sizeof(Logdir))
			{
				m_syslog(LOG_ERR, "logdir too long, %d max",
					(int) sizeof(Logdir));
				sm_exit(EX_USAGE);
			}
			break;
		case 'p':
			Pid_file = optarg;
			break;
		case 'R':
			se_getvalue(optarg, &Toomany,
			      "-R %s: bad value for service invocation rate");
			break;
		case 't':
			TestOnly = true;
			break;

#define MCP_USAGE "usage: mcp [-Ddlt] [-a address] [-R rate]" \
	       " [-c maximum] [-C rate]" \
	       " [-L logdir]" \
	       " [-p pidfile] [conf-file]"

		case 'h':
		case '?':
		default:
			if (isatty(STDERR_FILENO))
				fprintf(stderr, "%s\n", MCP_USAGE);
			else
				m_syslog(LOG_ERR, "%s", MCP_USAGE);
			sm_exit(EX_USAGE);
		}
	}
	argc -= optind;
	argv += optind;

	if (argc > 0)
		CONFIG = argv[0];
	if (Debug == 0)
	{
		char errtxt[128];

#if 0
		if (daemon(0, 0) < 0)
			m_syslog(LOG_WARNING, "daemon(0,0) failed: %m");
#endif

#if HAVE_SETLOGIN
		/*
		 * In case somebody has started mcp manually, we need to
		 * clear the logname, so that old servers run as root do not
		 * get the user's logname..
		 */
		if (setlogin("") < 0)
		{
			m_syslog(LOG_WARNING,
				"status=setlogin() failed, error=%s",
				strerror(errno));
			/* no big deal if it fails.. */
		}
#endif /* HAVE_SETLOGIN */
		i = sm_chk_pidfile(Pid_file, &Pid_fd, errtxt, sizeof(errtxt));
		if (sm_is_err(i))
		{
			m_syslog(LOG_ERR, "%s", errtxt);
			sm_exit(EX_CONFIG);
		}
	}
	sigemptyset(&Emptymask);
	sigemptyset(&Blockmask);
	sigaddset(&Blockmask, SIGCHLD);
	sigaddset(&Blockmask, SIGHUP);
	sigaddset(&Blockmask, SIGALRM);

	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGALRM);
	sigaddset(&sa.sa_mask, SIGCHLD);
	sigaddset(&sa.sa_mask, SIGHUP);
	sa.sa_handler = flag_retry;
	sigaction(SIGALRM, &sa, (struct sigaction *) 0);
	Servtab = NULL;
	i = se_config(&mcp_ctx, true);
	if (i != SM_SUCCESS)
		sm_exit(EX_USAGE);
	sa.sa_handler = flag_config;
	sigaction(SIGHUP, &sa, (struct sigaction *) 0);
	sa.sa_handler = flag_int;
	sigaction(SIGINT, &sa, (struct sigaction *) 0);
	sa.sa_handler = flag_term;
	sigaction(SIGTERM, &sa, (struct sigaction *) 0);
	sa.sa_handler = flag_reapchild;
	sigaction(SIGCHLD, &sa, (struct sigaction *) 0);
	sa.sa_handler = flag_usr1;
	sigaction(SIGUSR1, &sa, (struct sigaction *) 0);
	sa.sa_handler = flag_usr2;
	sigaction(SIGUSR2, &sa, (struct sigaction *) 0);
	sa.sa_handler = SIG_IGN;
	sigaction(SIGPIPE, &sa, &sapipe);

	{
		/* space for daemons to overwrite environment for ps */
#define DUMMYSIZE	100
		char dummy[DUMMYSIZE];

		(void) sm_memset(dummy, 'x', DUMMYSIZE - 1);
		dummy[DUMMYSIZE - 1] = '\0';
#if 0
		/* XXX ? */
		(void) setenv("mcp_dummy", dummy, 1);
#endif
	}

	if (pipe(Signalpipe) != 0)
	{
		m_syslog(LOG_ERR, "status=pipe() failed, error=%s",
			strerror(errno));
		sm_exit(EX_OSERR);
	}
	if (fcntl(Signalpipe[0], F_SETFD, FD_CLOEXEC) < 0 ||
	    fcntl(Signalpipe[1], F_SETFD, FD_CLOEXEC) < 0)
	{
		m_syslog(LOG_ERR, "status=fcntl(FD_CLOEXEC) for signalpipe "
			"failed, error=%s",
			strerror(errno));
		sm_exit(EX_OSERR);
	}
	FD_SET(Signalpipe[0], &Allsock);
	Nsock++;
	if (Signalpipe[0] > Maxsock)
		Maxsock = Signalpipe[0];
	if (Signalpipe[1] > Maxsock)
		Maxsock = Signalpipe[1];

	mcp_ctx.mcp_status = MCP_ST_RUNNING;

	for (;;)
	{
		int n, ctrl;
		fd_set readable;

#if 0
		if (Nsock == 0)
		{
			m_syslog(LOG_ERR, "?: Nsock=0");
			sm_exit(EX_SOFTWARE);
		}
#endif /* 0 */

		if (Debug)
			warnx("begin loop");

		if (!MCP_IS_FLAG(&mcp_ctx, MCP_FL_RESTART))
			se_startall(&mcp_ctx, Servtab, &sapipe, false);

		readable = Allsock;
		n = select(Maxsock + 1, &readable, (fd_set *) 0,
			   (fd_set *) 0, (struct timeval *) 0);
		if (n <= 0)
		{
			if (n < 0 && errno != EINTR)
			{
				m_syslog(LOG_WARNING,
					"status=select() failed, error=%s",
					strerror(errno));
				sleep(1);
				if (Debug <= 0)
					continue;

				/* Debug output */
				warnx("Signal_fd=%d", Signalpipe[0]);
				for (n = 0; n < Maxsock; n++)
				{
					if (FD_ISSET(n, &readable))
						warnx("fd_isset=%d", n);
				}
			}
			continue;
		}

		if (Debug)
			warnx("after select");

		/* handle any queued signal flags */
		if (FD_ISSET(Signalpipe[0], &readable))
		{
			int l;

			/* FIONREAD returns number of bytes in buffer... */
			if (ioctl(Signalpipe[0], FIONREAD, &l) != 0)
			{
				m_syslog(LOG_ERR,
					"status=ioctl() failed, error=%s",
					strerror(errno));
				sm_exit(EX_OSERR);
			}
			while (--l >= 0)
			{
				char c;

				if (read(Signalpipe[0], &c, 1) != 1)
				{
					m_syslog(LOG_ERR,
						"status=read() failed, error=%s"
						, strerror(errno));
					sm_exit(EX_OSERR);
				}
				if (Debug)
					warnx("Handling signal flag %c", c);
				switch (c)
				{
				  case SM_SIG_ALRM:	/* sigalrm */
					se_retry();
					break;
				  case SM_SIG_CHLD:	/* sigchld */
					se_reapchild(&mcp_ctx);
					break;
				  case SM_SIG_HUP:	/* sighup */
					(void) se_config(&mcp_ctx, false);
					break;
				  case SM_SIG_TERM:	/* sigterm */
				  case SM_SIG_INT:	/* sigint */
					mcp_ctx.mcp_status = MCP_ST_STOPPING;
					se_signalchildren(Servtab, c, false);
					mcp_ctx.mcp_status = MCP_ST_STOPPED;

#if SM_HEAP_CHECK
					if (HEAP_CHECK)
						sm_heap_report(smioerr, 3);
#endif

					sm_exit(0);
					break;
				  case SM_SIG_USR1:
				  case SM_SIG_USR2:
					se_signalchildren(Servtab, c, false);
					break;
				}
			}
		}

		for (sep = Servtab; n > 0 && sep != NULL; sep = sep->se_next)
		{
			if (sep->se_fd != INVALID_SOCKET &&
			    FD_ISSET(sep->se_fd, &readable))
			{
				n--;
				if (Debug)
					warnx("someone wants %s", sep->se_prg);
				if (SE_IS_ACCEPT(sep))
				{
					ctrl = accept(sep->se_fd,
						      (sockaddr_P) 0,
						      (socklen_T *) 0);
					if (Debug)
						warnx("accept, ctrl %d", ctrl);
					if (ctrl < 0)
					{
						if (errno != EINTR)
							m_syslog(LOG_WARNING,
								"service=%s, status=accept() failed, error= %s"
								, sep->se_prg, strerror(errno));

						/* fixme: already checked above */
						if (SE_IS_ACCEPT(sep))
							close(ctrl);
						continue;
					}
#if MTA_USE_CPML
					if (cpmip(sep, ctrl) < 0)
					{
						close(ctrl);
						continue;
					}
#endif /* MTA_USE_CPML */

					/* only log for non UNIX sockets */
					if (Log &&
					    !MCP_OPT_IS_SET(sep->se_socket_name))
					{
						i = sizeof peer;
						if (getpeername(ctrl,
							(sockaddr_P) &peer,
							(socklen_T *) &i))
						{
							m_syslog(LOG_WARNING,
							       "service=%s, "
								"status=getpeername() failed"
								", error= %s",
								sep->se_prg,
								strerror(errno));
							close(ctrl);
							continue;
						}
						m_syslog(LOG_INFO,
							"service=%s, from=%s",
							sep->se_prg,
							inet_ntoa(peer.sin_addr));
					}
				}
				else
					ctrl = sep->se_fd;

				/* XXX check return value? */
				se_startproc(&mcp_ctx, sep, ctrl, &sapipe);
			}
		}

		if (MCP_IS_FLAG(&mcp_ctx, MCP_FL_RESTART) &&
		    MCP_IS_FLAG(&mcp_ctx, MCP_FL_RS_START) &&
		    se_depstopped(Servtab) &&
		    MSP_IS_RUNNING(&mcp_ctx))
		{
			if (Debug)
				fprintf(stderr, "mcp: startall\n");
			se_startall(&mcp_ctx, Servtab, &sapipe, true);
			MCP_CLR_FLAG(&mcp_ctx, MCP_FL_RS_START|MCP_FL_RESTART);
		}

		if (MCP_IS_FLAG(&mcp_ctx, MCP_FL_RESTART) &&
		    MCP_IS_FLAG(&mcp_ctx, MCP_FL_RS_STOP))
		{
			if (Debug)
				fprintf(stderr, "mcp: stopall\n");
			se_stopall(Servtab, true);
			MCP_CLR_FLAG(&mcp_ctx, MCP_FL_RS_STOP);
			MCP_SET_FLAG(&mcp_ctx, MCP_FL_RS_START);
			if (se_depstopped(Servtab) && MSP_IS_RUNNING(&mcp_ctx))
			{
				if (Debug)
					fprintf(stderr,
						"mcp: startall (immediate)\n");
				se_startall(&mcp_ctx, Servtab, &sapipe, true);
				MCP_CLR_FLAG(&mcp_ctx, MCP_FL_RS_START|MCP_FL_RESTART);
			}
		}

	}
	/* NOTREACHED */
	return 0;
}

/*
**  SE_STOPALL -- stop all processes in service table
**
**	Parameters:
**		servtab -- list of service table entries
**		marked -- only stop those processes that are marked for restart
**
**	Returns:
**		none.
*/

static void
se_stopall(servtab_P servtab, bool marked)
{
	se_signalchildren(servtab, SM_SIG_TERM, marked);
}

/*
**  SE_STARTALL -- start all processes in service table
**
**	Parameters:
**		mcp_ctx -- MCP context
**		servtab -- list of service table entries
**		sapipe -- signal pipe
**		marked -- only stop those processes that are marked for restart
**
**	Returns:
**		none.
*/

static void
se_startall(mcp_ctx_P mcp_ctx, servtab_P servtab, struct sigaction *sapipe, bool marked)
{
	int  n;
	servtab_P sep;

	for (sep = servtab; sep != NULL; sep = sep->se_next)
	{
		if (SE_IS_FLAG(sep, SE_FL_DISABLED)
		    || SE_IS_FLAG(sep, SE_FL_W4REQ))
			continue;
		if (marked && !SE_IS_FLAG(sep, SE_FL_RESTART))
			continue;
		if (marked && SE_IS_FLAG(sep, SE_FL_RESTART))
		{
			if (Debug && !SE_IS_FLAG(sep, SE_FL_LEADER))
				fprintf(stderr,
					"mcp: start %s due to restart dependency\n",
					sep->se_prg);
			SE_CLR_FLAG(sep, SE_FL_RESTART|SE_FL_LEADER);
			se_setup(sep);
		}

		n = sep->se_minchild - sep->se_numchild;

		/*
		**  Use two counters for "safety": numchild
		**  could be changed due to child termination.
		*/

		while (sep->se_minchild > sep->se_numchild && n-- > 0)
			(void) se_startproc(mcp_ctx, sep, sep->se_fd, sapipe);
	}
}

/*
**  MCP_OPEN_LOG -- open a logfile
**
**	Parameters:
**		sep -- description of process to start
**		id -- id for logfile
**
**	Returns:
**		file descriptor of logfile (<0: error)
*/

static int
mcp_open_log(servtab_P sep, int id)
{
	int lfd;
	char logfile[256];

	if (sep->se_logf_id && sep->se_pass_id != NULL)
	{
		snprintf(logfile, sizeof(logfile), "%s%s%d.log",
			Logdir, sep->se_prg, id);
	}
	else
	{
		strlcpy(logfile, Logdir, sizeof(logfile));
		strlcat(logfile, sep->se_prg, sizeof(logfile));
		strlcat(logfile, ".log", sizeof(logfile));
	}
	lfd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, 0600);
	if (lfd < 0)
	{
		m_syslog(LOG_WARNING,
			"service=%s, filename=%s, status=open() failed, "
			"error=%s",
			sep->se_prg, logfile, strerror(errno));
	}
	return lfd;
}

/*
**  SE_STARTPROC -- start a process
**
**	Parameters:
**		mcp_ctx -- MCP context
**		sep -- description of process to start
**		fd -- file descriptor to pass
**			NOTE: for a process that uses "pass" fd MUST be
**			the same as se_fd; this is used in here!
**		sapipe -- signal pipe
**
**	Returns:
**		nothing useful yet
*/

static int
se_startproc(mcp_ctx_P mcp_ctx, servtab_P sep, int fd, struct sigaction *sapipe)
{
	pid_t pid;
	time_t now;
	int tmpint;
	uint id;
	struct passwd  *pwd;
	struct group   *grp;
	char passid[16];

	if (SE_IS_FLAG(sep, SE_FL_DISABLED))
		return -1;
	now = time(NULLT);
	if (Maxfails > 0 && sep->se_failed >= Maxfails &&
	    now - sep->se_lastfail < FAIL_TIMEOUT)
	{
		if (SE_IS_FLAG(sep, SE_FL_UNRECOVERABLE)) {
			m_syslog(LOG_ERR,
				"service=%s, status=server failed with unrecoverable error; service terminated"
				, sep->se_prg);
		}
		else if (sep->se_failed == Maxfails) {
			m_syslog(LOG_ERR,
				"service=%s, failures=%d, status=server failed too often; service terminated",
				sep->se_prg, Maxfails);
		}
		SE_SET_FLAG(sep, SE_FL_DISABLED);
		return -1;
	}

	sigprocmask(SIG_BLOCK, &Blockmask, NULL);
	pid = 0;
	SE_CLR_FLAG(sep, SE_FL_UNRECOVERABLE);

	if (Debug)
		fprintf(stderr,
			"mcp: se_startproc: %s: X-socket=%s, fd=%d, flags=0x%x, failed=%d, Maxfails=%d\n",
			sep->se_prg, sep->se_exsock, fd,
			sep->se_flags, sep->se_failed, Maxfails);

	/* Need to open fd again when restart! */
	if (SE_IS_PASS(sep))
	{
		/* remove socket before starting server */
		(void) unlink(sep->se_exsock);
		if (!is_valid_socket(fd))
		{
			se_setup(sep);
			fd = sep->se_fd;
		}
	}

	if (sep->se_count++ == 0)
		(void) gettimeofday(&sep->se_time, (struct timezone *) NULL);
	else if (sep->se_count >= Toomany)
	{
		struct timeval  now;

		(void) gettimeofday(&now, (struct timezone *) NULL);
		if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL)
		{
			sep->se_time = now;
			sep->se_count = 1;
		}
		else
		{
			m_syslog(LOG_ERR,
				"service=%s, status=server failing (looping); service terminated",
				sep->se_prg);
			close_sep(sep);
			sigprocmask(SIG_SETMASK, &Emptymask, NULL);
			if (!Timingout)
			{
				Timingout = true;
				alarm(RETRYTIME);
			}
			return 0;
		}
	}

	/* get an id for this process */
	if (sep->se_pass_id != NULL)
	{
		tmpint = sm_new_id(mcp_ctx->mcp_id_ctx, &id);
		if (sm_is_err(tmpint))
		{
			m_syslog(LOG_ERR,
				"service=%s, new_id=%#x",
				sep->se_prg, tmpint);
			return 0;
		}
	}
	else
		id = 0;

	pid = fork();
	if (pid < 0)
	{
		m_syslog(LOG_ERR, "status=fork() failed, error=%s",
			strerror(errno));
		if (fd >= 0)
			close(fd);
		sigprocmask(SIG_SETMASK, &Emptymask, NULL);
		if (sep->se_pass_id != NULL)
			(void) sm_free_id(mcp_ctx->mcp_id_ctx, id);
		sleep(1);
		return 0;
	}
	if (pid > 0)
	{
		if (Debug)
			fprintf(stderr,
				"mcp: start=%s: id=%u, pid=%ld\n",
				sep->se_prg, id, (long) pid);
		se_addchild(sep, pid, id);
	}

	sigprocmask(SIG_SETMASK, &Emptymask, NULL);
	if (pid == 0)
	{
		if (Debug)
			warnx("+ closing from %d", Maxsock);

		/* XXX set FD_CLOEXEC instead? */
		/* XXX what about other open files? */
		/* XXX use closefrom() */
		for (tmpint = Maxsock; tmpint > STDERR_FILENO; tmpint--)
		{
			if (tmpint != fd)
				(void) close(tmpint);
		}

		if (Debug)
			warnx("%d execl %s", getpid(), sep->se_server);

		/* use fd as stdin/stdout only for "accept" services */
		if (SE_IS_ACCEPT(sep) && fd >= 0)
		{
			int lfd;

			if (fd != STDIN_FILENO)
			{
				dup2(fd, STDIN_FILENO);
				close(fd);
			}
			dup2(STDIN_FILENO, STDOUT_FILENO);

			/*
			**  Connect stderr with a logfile to avoid error
			**  output messing out some protocol dialogue.
			**  Should this be an option?
			*/

			lfd = mcp_open_log(sep, id);
			if (lfd < 0)
			{
				lfd = open(DEVNULL, O_RDWR, 0600);
				if (lfd < 0)
				{
					m_syslog(LOG_ERR,
						"service=%s, filename=%s, "
						"status=open() failed, "
						"error=%s",
						sep->se_prg, DEVNULL,
						strerror(errno));
					_exit(EX_OSERR);
				}
			}
			dup2(lfd, STDERR_FILENO);
		}

		/* Could be checked when conf is read (CC) */
		if ((pwd = getpwnam(sep->se_user)) == NULL)
		{
			m_syslog(LOG_ERR,
			       "service=%s, user=%s, status=No such user",
			       sep->se_prg, sep->se_user);
			_exit(EX_NOUSER);
		}
		grp = NULL;

		/* Could be checked when conf is read (CC) */
		if (sep->se_group != NULL
		    && (grp = getgrnam(sep->se_group)) == NULL)
		{
			m_syslog(LOG_ERR,
			       "service=%s, group=%s, status=No such group",
			       sep->se_prg, sep->se_group);
			_exit(EX_NOUSER);
		}
		if (grp != NULL)
			pwd->pw_gid = grp->gr_gid;
#ifdef LOGIN_CAP
		/* Could be checked when conf is read (CC) */
		if ((lc = login_getclass(sep->se_class)) == NULL)
		{
			/* error syslogged by getclass */
			m_syslog(LOG_ERR,
				"service=%s, class=%s, status=login class error"
			       , sep->se_prg, sep->se_class);
			_exit(EX_NOUSER);
		}
#endif /* LOGIN_CAP */
		if (setsid() < 0)
		{
			m_syslog(LOG_ERR,
			       "service=%s, status=setsid() failed, error=%s",
			       sep->se_prg, strerror(errno));
			/* _exit(EX_OSERR); not fatal yet */
		}
#ifdef LOGIN_CAP
		if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETALL) != 0)
		{
			m_syslog(LOG_ERR,
				"service=%s, user=%s, status=setusercontext()"
				"failed, error=%s",
				sep->se_prg, sep->se_user, strerror(errno));
			_exit(EX_OSERR);
		}
#else /* LOGIN_CAP */
		if (pwd->pw_uid != 0 && !TestOnly)
		{
#if HAVE_SETLOGIN
			if (setlogin(sep->se_user) < 0)
			{
				m_syslog(LOG_ERR,
					"service=%s, user=%s, status=setlogin()"
					"failed, error=%s",
					sep->se_prg, sep->se_user,
					strerror(errno));
				/* _exit(EX_OSERR); not yet */
			}
#endif /* HAVE_SETLOGIN */
			if (setgid(pwd->pw_gid) < 0)
			{
				m_syslog(LOG_ERR,
					"service=%s, gid=%ld, status=setgid() "
					"failed, error=%s",
					sep->se_prg, (long) pwd->pw_gid,
					strerror(errno));
				_exit(EX_OSERR);
			}
			(void) initgroups(pwd->pw_name, pwd->pw_gid);
			if (setuid(pwd->pw_uid) < 0)
			{
				m_syslog(LOG_ERR,
					"service=%s, uid=%ld, status=setuid()"
					"failed, error=%s",
					sep->se_prg, (long) pwd->pw_uid,
					strerror(errno));
				_exit(EX_OSERR);
			}
		}
#endif /* LOGIN_CAP */

		/* open logfile after setuid() */
		if (!SE_IS_ACCEPT(sep))
		{
			int nfd, lfd;

			/* stdin:/dev/null, stdout/err=logfile */
			/* XXX use two different files for stdout/stderr? */
			nfd = open(DEVNULL, O_RDWR, 0600);
			if (nfd < 0)
			{
				m_syslog(LOG_ERR,
					"service=%s, filename=%s, "
					"status=open() failed, error=%s",
					sep->se_prg, DEVNULL, strerror(errno));
				_exit(EX_OSERR);
			}
			dup2(nfd, STDIN_FILENO);
			lfd = mcp_open_log(sep, id);
			if (lfd < 0)
				lfd = nfd;
			dup2(lfd, STDOUT_FILENO);
			dup2(lfd, STDERR_FILENO);
			close(nfd);
			if (lfd != nfd)
				close(lfd);
		}

		if (sep->se_pass_id != NULL)
		{
			snprintf(passid, sizeof(passid), "%s %d",
				sep->se_pass_id, id);
			sep->se_argv[1] = passid;
		}
		if (sep->se_workdir != NULL && sep->se_workdir[0] != '\0' &&
		    chdir(sep->se_workdir) < 0)
		{
			tmpint = errno;
			m_syslog(LOG_ERR,
				"service=%s, directory=%s, status=chdir() "
				"failed, error=%s",
				sep->se_prg, sep->se_workdir,
				strerror(errno));
			_exit((ENOENT == tmpint
#ifdef EACCES
				|| EACCES == tmpint
#endif
#ifdef ELOOP
				|| ELOOP == tmpint
#endif
#ifdef ENAMETOOLONG
				|| ENAMETOOLONG == tmpint
#endif
#ifdef ENOTDIR
				|| ENOTDIR == tmpint
#endif
				) ? EX_CONFIG : EX_OSERR);
		}
		sigaction(SIGPIPE, sapipe, (struct sigaction *) 0);
		execv(sep->se_server, sep->se_argv);
		tmpint = errno;
		m_syslog(LOG_ERR,
			"service=%s, program=%s, status=execv() failed,"
			" error=%s",
			sep->se_prg, sep->se_server, strerror(errno));
		_exit(ENOENT == tmpint ? EX_CONFIG : EX_OSERR);
	}

	/* parent only */
	if (SE_IS_ACCEPT(sep) && fd >= 0)
		(void) close(fd);
	else if (SE_PASS_FD(sep, fd))
	{
		int cltfd;
		int res, attempts;
		char buf[2];
		struct stat stb;

		/* pass fd to child */

		/* wait for socket to "show up" */
		attempts = 10;
		res = -1;
		while (attempts-- > 0 && res != 0)
		{
			res = stat(sep->se_exsock, &stb);
			if (res == -1)
			{
				if (errno == ENOENT)
					sleep(1);
				else
					break;
			}
		}
		if (res != 0)
		{
			m_syslog(LOG_ERR,
				"service=%s, socket=%s, status=does not exist"
				", error=%s"
				", check whether client created socket"
				, sep->se_prg, sep->se_exsock, strerror(errno));
			return -1;
		}

		if (Debug > 1)
			warnx("%s: connect to %sc"
				, sep->se_prg, sep->se_exsock);
		/* connect to server (timeout?) */
		(void) unix_client_connect(sep->se_exsock, &cltfd);
		if (cltfd < 0)
		{
			m_syslog(LOG_ERR,
				"service=%s, socket=%s, status=failed to "
				"connect to socket , error=%s"
				, sep->se_prg, sep->se_exsock
				, strerror(errno));
			return -1;
		}
		buf[0] = '\0';
		buf[1] = '\0';

		if (Debug > 1)
			warnx("%s: send %d to %sc"
				, sep->se_prg, fd, sep->se_exsock);

		/* send fd to server */
		res = sm_write_fd(cltfd, (void *) buf, 1, fd);
		if (sm_is_err(res))
		{
			m_syslog(LOG_ERR,
				"service=%s, status=pass fd failed, res=%#x, error=%s",
				sep->se_prg, res, strerror(errno));
		}

		close(fd);
		fd = INVALID_SOCKET;
		sep->se_fd = INVALID_SOCKET;
		sleep(1);	/* really?? */
		close(cltfd);
	}
	return 0;
}

/*
**  FLAG_SIGNAL -- Add a signal flag to the queue for later handling
**
**	Parameters:
**		c -- char denoting type of signal
**
**	Returns:
**		none.
*/

static void
flag_signal(char c)
{
	if (write(Signalpipe[1], &c, 1) != 1)
	{
		m_syslog(LOG_ERR,
			"func=flag_signal, status=write() failed, error=%m");
		sm_exit(EX_OSERR);
	}
}

/* XXX consolidate signal handlers into one function and switch on signo? */

/*
**  FLAG_RETRY -- signal handler for SIGALRM
**
**	Parameters:
**		signo -- signal number (ignored)
**
**	Returns:
**		none.
*/

static void
flag_retry(int signo)
{
	flag_signal(SM_SIG_ALRM);
}

/*
**  FLAG_CONFIG -- signal handler for SIGHUP
**
**	Parameters:
**		signo -- signal number (ignored)
**
**	Returns:
**		none.
*/

static void
flag_config(int signo)
{
	flag_signal(SM_SIG_HUP);
}

/*
**  FLAG_CHLD -- signal handler for SIGCHLD
**
**	Parameters:
**		signo -- signal number (ignored)
**
**	Returns:
**		none.
*/

static void
flag_reapchild(int signo)
{
	flag_signal(SM_SIG_CHLD);
}

/*
**  FLAG_TERM -- signal handler for SIGTERM
**
**	Parameters:
**		signo -- signal number (ignored)
**
**	Returns:
**		none.
*/

static void
flag_term(int signo)
{
	flag_signal(SM_SIG_TERM);
}

/*
**  FLAG_INT -- signal handler for SIGINT
**
**	Parameters:
**		signo -- signal number (ignored)
**
**	Returns:
**		none.
*/

static void
flag_int(int signo)
{
	flag_signal(SM_SIG_INT);
}

/*
**  FLAG_USR1 -- signal handler for SIGUSR1
**
**	Parameters:
**		signo -- signal number (ignored)
**
**	Returns:
**		none.
*/

static void
flag_usr1(int signo)
{
	flag_signal(SM_SIG_USR1);
}

/*
**  FLAG_USR2 -- signal handler for SIGUSR2
**
**	Parameters:
**		signo -- signal number (ignored)
**
**	Returns:
**		none.
*/

static void
flag_usr2(int signo)
{
	flag_signal(SM_SIG_USR2);
}

/*
**  SE_SIGNALCHILDREN -- pass signal to children
**
**	Parameters:
**		servtab -- list of service table entries
**		c -- type of signal
**		marked -- only stop those processes that are marked for restart
**
**	Returns:
**		none.
*/

static void
se_signalchildren(servtab_P servtab, char c, bool marked)
{
	int sig, k, r;
	pid_t pid;
	servtab_P sep;

	if (c == SM_SIG_HUP )
		sig = SIGHUP;
	else if (c == SM_SIG_USR1)
		sig = SIGUSR1;
	else if (c == SM_SIG_USR2)
		sig = SIGUSR2;
	else
		sig = SIGTERM;

	for (sep = servtab; sep; sep = sep->se_next)
	{
		if (Debug > 1)
			warnx("se_signalchildren=%s, marked=%d, se_flags=0x%x, sig=%c"
				, sep->se_prg, marked, sep->se_flags, c);
		if (marked && !SE_IS_FLAG(sep, SE_FL_RESTART))
			continue;
		se_disable(sep, !marked);

		if (sep->se_numchild > sep->se_maxchild)
		{
			m_syslog(LOG_ALERT, "mcp/se_signalchildren: %d >= %d",
				sep->se_numchild, sep->se_maxchild);
		}

		for (k = 0; k < sep->se_numchild && k < sep->se_maxchild; k++)
		{
			pid = sep->se_pids[k];
			if (pid > 0)
			{
				r = kill(pid, sig);
				if (r != 0)
				{
					m_syslog(LOG_WARNING,
						"%s[%d]: kill=%d",
						sep->se_server, pid, r);
				}
				else if (Debug > 1)
					fprintf(stderr, "killed %d\n", pid);
			}
		}
	}
}

/*
**  SE_ADDCHILD -- Record a new child pid for this service.
**	If we've reached the limit on children, then stop accepting
**	incoming requests.
**
**	Parameters:
**		sep -- service entry
**		pid -- pid of child
**		id -- id for process
**
**	Returns:
**		none.
*/

static void
se_addchild(servtab_P sep, pid_t pid, uint id)
{
#if SANITY_CHECK
	if (sep->se_numchild >= sep->se_maxchild)
	{
		m_syslog(LOG_ALERT, "%s: %d >= %d",
		       __FUNCTION__, sep->se_numchild, sep->se_maxchild);
		sm_exit(EX_SOFTWARE);
	}
#endif
	if (sep->se_maxchild == 0)
		return;
	SM_ASSERT(sep->se_numchild < sep->se_maxchild);
	sep->se_pids[sep->se_numchild] = pid;
	sep->se_ids[sep->se_numchild] = id;
	++sep->se_numchild;
	if (sep->se_numchild == sep->se_maxchild)
		se_disable(sep, false);
}

/*
**  SE_FINDSEPBYNAME -- Find service by name
**
**	Parameters:
**		name -- name of service to find
**
**	Returns:
**		pointer to service entry (NULL if not found)
*/

servtab_P
se_findsepbyname(char const *name)
{
	servtab_P sep;

	for (sep = Servtab; sep != NULL; sep = sep->se_next)
	{
		if (strcmp(sep->se_prg, name) == 0)
			return sep;
	}
	return (servtab_P) 0;
}

/*
**  SE_CHKRESTART -- figure out what needs to be restarted
**
**	Parameters:
**		mcp_ctx -- MCP context
**		sep -- service entry
**
**	Returns:
**		none.
*/

static void
se_chkrestart(mcp_ctx_P mcp_ctx, servtab_P sep)
{
	int i;
	servtab_P se_restart;

	if (Debug > 1)
		warnx("chkrestart=%s, dep=%d, mcp_flags=0x%x, se_flags=0x%x"
			, sep->se_prg, sep->se_nrestartdep
			, mcp_ctx->mcp_flags, sep->se_flags
			);

	/*
	**  Don't check dependencies iff
	**  there are none				or
	**  the system is already in RESTART mode	or
	**  this service have already been checked
	*/

	if (sep->se_nrestartdep == 0
	    || MCP_IS_FLAG(mcp_ctx, MCP_FL_RS_START|MCP_FL_RS_STOP)
	    || SE_IS_FLAG(sep, SE_FL_RESTART))
		return;
	MCP_SET_FLAG(mcp_ctx, MCP_FL_RESTART);
	SE_SET_FLAG(sep, SE_FL_RESTART);

	for (i = 0; i < sep->se_nrestartdep; i++)
	{
		if (Debug > 1)
			warnx("chkrestart=%s, dep[%i]=\"%s\""
				, sep->se_prg, i, sep->se_restartdep[i]);
		if (sep->se_restartdep[i] != NULL &&
		    (se_restart = se_findsepbyname(sep->se_restartdep[i])) != NULL)
		{
			/* transitive hull */
			if (!SE_IS_FLAG(se_restart, SE_FL_RESTART))
				se_chkrestart(mcp_ctx, se_restart);
			if (Debug > 2)
				warnx("enabling restart for %s",
					se_restart->se_prg);
			SE_SET_FLAG(se_restart, SE_FL_RESTART);
		}
	}

	/* set this after the recursive call, it is checked at the begin */
	MCP_SET_FLAG(mcp_ctx, MCP_FL_RS_STOP);
}

/*
**  SE_DEPENDSON -- does sep depend on se?
**
**	Parameters:
**		mcp_ctx -- MCP context
**		sep -- service entry
**
**	Returns:
**		true iff sep depends on se
*/

static bool
se_dependson(mcp_ctx_P mcp_ctx, servtab_P sep, servtab_P se)
{
	int i;

	for (i = 0; i < se->se_nrestartdep; i++)
	{
		if (Debug > 1)
			warnx("finddepon=%s, dep[%i]=\"%s\""
				, se->se_prg, i, se->se_restartdep[i]);
		if (se->se_restartdep[i] != NULL &&
		    strcmp(sep->se_prg, se->se_restartdep[i]) == 0)
		{
			return true;
		}
	}
	return false;
}

/*
**  SE_DEPSTOPPED -- check whether all dependencies stopped
**
**	Parameters:
**		servtab -- list of services
**
**	Returns:
**		true iff all dependencies are stopped
*/

static bool
se_depstopped(servtab_P servtab)
{
	servtab_P sep;

	for (sep = servtab; sep; sep = sep->se_next)
	{
		if (SE_IS_FLAG(sep, SE_FL_RESTART) && sep->se_numchild > 0)
		{
			if (Debug > 1)
				fprintf(stderr,
					"se_depstopped=false, prg=%s, children=%d\n"
					, sep->se_prg, sep->se_numchild);
			return false;
		}
	}
	if (Debug > 1)
		fprintf(stderr, "se_depstopped=true\n");
	return true;
}

/*
**  SE_REAPCHILD -- A child process has exited. See if it's on somebody's list.
**
**	Parameters:
**		mcp_ctx -- MCP context
**
**	Returns:
**		none.
*/

static void
se_reapchild(mcp_ctx_P mcp_ctx)
{
	int k, status;
	pid_t pid;
	servtab_P sep;
	int failed;

	for (;;) {
		failed = SM_FAILED_IGNORE;
		pid = wait3(&status, WNOHANG, (struct rusage *) 0);
		if (pid <= 0)
			break;
		if (Debug)
			warnx("%d reaped, status %#x", pid, status);
		for (sep = Servtab; sep; sep = sep->se_next) {
			if (sep->se_numchild > sep->se_maxchild) {
				m_syslog(LOG_ALERT, "mcp/se_reapchild: %d >= %d",
					sep->se_numchild, sep->se_maxchild);
			}

			for (k = 0; k < sep->se_numchild && k < sep->se_maxchild; k++) {
				if (sep->se_pids[k] == pid)
					break;
			}
			if (k >= sep->se_numchild || sep->se_pids[k] != pid)
				continue;
			SE_SET_FLAG(sep, SE_FL_LEADER);

			if (Debug)
				fprintf(stderr,
					"mcp: reap=%s: id=%u, pid=%ld\n",
					sep->se_prg, sep->se_ids[k],
					(long) sep->se_pids[k]);

			se_chkrestart(mcp_ctx, sep);
			if (sep->se_numchild == sep->se_maxchild)
				se_enable(sep, false);
			sep->se_pids[k] = sep->se_pids[sep->se_numchild - 1];
			(void) sm_free_id(mcp_ctx->mcp_id_ctx, sep->se_ids[k]);
			sep->se_ids[k] = sep->se_ids[sep->se_numchild - 1];
			--sep->se_numchild;
			if (status != 0) {
				char errbuf[128];

				(void) exit2txt_r(status, errbuf, sizeof(errbuf));
				m_syslog(LOG_WARNING, "%s[%d]: %s", sep->se_server, pid, errbuf);
				SE_CLR_FLAG(sep, SE_FL_UNRECOVERABLE);
				failed = sm_child_status(status);
				if (SM_FAILED_RESTARTALL == failed) {
					servtab_P se;

					for (se = Servtab; se != NULL; se = se->se_next)
						SE_SET_FLAG(se, SE_FL_RESTART);
					MCP_SET_FLAG(mcp_ctx, MCP_FL_RESTART|MCP_FL_RS_STOP);
				}
				else if (SM_FAILED_RESTARTDEP == failed) {
					bool found;
					servtab_P se;

					found = false;
					if (Debug)
						fprintf(stderr,
							"mcp: %s: asked_for_restart_dep\n",
							sep->se_prg);
					for (se = Servtab; se != NULL; se = se->se_next) {
						if (se_dependson(mcp_ctx, sep, se)) {
							SE_SET_FLAG(se, SE_FL_RESTART);
							found = true;
							if (Debug)
								fprintf(stderr,
									"mcp: %s: dep=%s\n",
									sep->se_prg, se->se_prg);
						}
					}
					if (found)
						MCP_SET_FLAG(mcp_ctx, MCP_FL_RESTART|MCP_FL_RS_STOP);
					SE_SET_FLAG(sep, SE_FL_RESTART);
				}
				else if (failed == SM_FAILED_COUNT) {
					time_t now;

					now = time(NULLT);
					if (now - sep->se_lastfail > FAIL_TIMEOUT)
						sep->se_failed = 1;
					else
						sep->se_failed++;
					sep->se_lastfail = now;
					failed = SM_FAILED_IGNORE;
				}
				else if (failed == SM_FAILED_STOP)
				{
					sep->se_lastfail = time(NULLT);
					sep->se_failed = Maxfails;
					SE_SET_FLAG(sep, SE_FL_UNRECOVERABLE);
					failed = SM_FAILED_IGNORE;
				}
			}
			break;
		}
	}
}

/*
**  SE_RETRY -- try to (re-)enable services.
**
**	Parameters:
**		none.
**
**	Returns:
**		none.
**
**	Side Effects:
**		might change status of services.
**
**	Note:
**		XXX This might be broken... check!
*/

static void
se_retry(void)
{
	servtab_P sep;

	Timingout = false;
	for (sep = Servtab; sep; sep = sep->se_next)
	{
		if (sep->se_fd == INVALID_SOCKET)
			se_setup(sep);
	}
}

/*
**  SE_SETUP -- setup a service.
**
**	Parameters:
**		sep -- service entry
**
**	Returns:
**		none.
*/

void
se_setup(servtab_P sep)
{
	int on, r, save_errno;
	mode_t mode;

	on = 1;
	if (Debug)
		fprintf(stderr,
			"mcp: se_setup: %s: X-socket=%s, flags=0x%x\n",
			sep->se_prg, sep->se_exsock,
			sep->se_flags);
	if (!SE_IS_FLAG(sep, SE_FL_PASS|SE_FL_ACCEPT) ||
	    SE_IS_FLAG(sep, SE_FL_DISABLED))
		return;
	mode = (mode_t) -1;
	sep->se_fd = socket(sep->se_ctrladdr.sa.sa_family, SOCK_STREAM, 0);
	if (sep->se_fd < 0)
	{
		if (Debug)
			warn("socket failed on %s", sep->se_prg);
		m_syslog(LOG_ERR,
			"service=%s, status=socket() call failed, error=%s",
			sep->se_prg, strerror(errno));
		return;
	}
#define turnon(fd, opt) \
setsockopt(fd, SOL_SOCKET, opt, (char *)&on, sizeof (on))
#if 0

#define SM_MCP_ISTCP(sep) (strcmp(sep->se_proto, "tcp") == 0)
#define SM_MCP_ISTCP(sep) SE_IS_FLAG(sep, SE_FL_PASS|SE_FL_ACCEPT)

	if (SM_MCP_ISTCP(sep) && (Options & SO_DEBUG) &&
	    turnon(sep->se_fd, SO_DEBUG) < 0)
		m_syslog(LOG_ERR, "setsockopt (SO_DEBUG): %m");
#endif /* 0 */
	if (turnon(sep->se_fd, SO_REUSEADDR) < 0)
		m_syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m");
#ifdef SO_PRIVSTATE
	if (turnon(sep->se_fd, SO_PRIVSTATE) < 0)
		m_syslog(LOG_ERR, "setsockopt (SO_PRIVSTATE): %m");
#endif /* SO_PRIVSTATE */
#undef turnon
	if (sep->se_ctrladdr.sa.sa_family == AF_UNIX)
		mode = umask(sep->se_socket_umask);
	r = bind(sep->se_fd, (sockaddr_P) &sep->se_ctrladdr,
		 sep->se_ctrladdr_size);
	save_errno = errno;
	if (mode != (mode_t) -1
	    && sep->se_ctrladdr.sa.sa_family == AF_UNIX)
		(void) umask(mode);

	if (r < 0)
	{
		if (Debug)
		{
			warn("%s: bind failed on %d, fd=%d",
			     sep->se_prg, sep->se_port, sep->se_fd);
		}
		m_syslog(LOG_ERR, "service=%s, status=bind() call failed, error=%s",
			sep->se_prg, strerror(save_errno));
		(void) close(sep->se_fd);
		sep->se_fd = INVALID_SOCKET;
		if (!Timingout)
		{
			Timingout = true;
			alarm(RETRYTIME);
		}
		return;
	}

	if (sep->se_ctrladdr.sa.sa_family == AF_UNIX)
	{
		struct passwd  *pwd;
		struct group   *grp;

		/*
		**  Some OS (e.g., HP UX) don't obey umask for bind().
		**  Of course this has a race condition, hence the socket
		**  MUST be in a "safe" directory (which currently is NOT
		**  checked).
		*/

		mode = 0777 ^ sep->se_socket_umask;
		r = chmod(sep->se_ctrladdr.sunix.sun_path, mode);
		if (r != 0)
		{
			m_syslog(LOG_ERR,
				"service=%s, socket=%s, status=chmod() failed,"
				"mode=%o, error=%s",
				sep->se_prg,
				sep->se_ctrladdr.sunix.sun_path,
				(int) mode, strerror(errno));
			_exit(EX_OSERR);
		}

/* XXX check for NULL? avoid NULL in config? */
		if ((pwd = getpwnam(sep->se_socket_user)) == NULL)
		{
			m_syslog(LOG_ERR,
			       "service=%s, user=%s, status=No such user",
			       sep->se_prg,
			       sep->se_socket_user);
			_exit(EX_NOUSER);
		}
		grp = NULL;
		if (sep->se_socket_group != NULL
		    && (grp = getgrnam(sep->se_socket_group)) == NULL)
		{
			m_syslog(LOG_ERR,
			       "service=%s, group=%s, status=No such group",
			       sep->se_prg,
			       sep->se_socket_group);
			_exit(EX_NOUSER);
		}
		if (grp != NULL)
			pwd->pw_gid = grp->gr_gid;

		r = chown(sep->se_ctrladdr.sunix.sun_path,
			pwd->pw_uid, pwd->pw_gid);
		if (r != 0)
		{
			m_syslog(LOG_ERR,
				"service=%s, socket=%s, status=chown() failed,"
				"uid=%ld, gid=%ld, error=%s",
				sep->se_prg,
				sep->se_ctrladdr.sunix.sun_path,
				(long) pwd->pw_uid, (long) pwd->pw_gid,
				strerror(errno));
			_exit(EX_OSERR);
		}
	}

	r = listen(sep->se_fd, sep->se_listen_len);
	if (r != 0)
	{
		m_syslog(LOG_ERR,
			"service=%s, fd=%d, status=listen() failed, error=%s",
			sep->se_prg, sep->se_fd, strerror(errno));

		/* added, ca, 2004-11-16 */
		(void) close(sep->se_fd);
		sep->se_fd = INVALID_SOCKET;
		if (!Timingout)
		{
			Timingout = true;
			alarm(RETRYTIME);
		}
		return;
	}
	se_enable(sep, false);
	if (Debug)
	{
		warnx("registered %s on %d",
		      sep->se_server, sep->se_fd);
	}
}

/*
**  CLOSE_SEP -- Finish with a service and its socket.
**
**	Parameters:
**		sep -- service entry
**
**	Returns:
**		none.
*/

void
close_sep(servtab_P sep)
{
	SM_REQUIRE(sep != NULL);
	if (is_valid_socket(sep->se_fd))
	{
		if (FD_ISSET(sep->se_fd, &Allsock))
			se_disable(sep, true);
		(void) close(sep->se_fd);
		sep->se_fd = INVALID_SOCKET;
	}
	sep->se_count = 0;
	sep->se_numchild = 0;	/* forget about any existing children */
	SE_SET_FLAG(sep, SE_FL_DISABLED);
}

/*
**  SE_ENABLE -- Enable a service entry.
**
**	Parameters:
**		sep -- service entry
**		completely -- reset DISABLE flag
**
**	Returns:
**		none.
*/

void
se_enable(servtab_P sep, bool completely)
{
	if (Debug)
		warnx("enabling=%s, fd=%d, completely=%d, disabled=%d, flags=0x%x",
			sep->se_prg, sep->se_fd, completely,
			SE_IS_FLAG(sep, SE_FL_DISABLED),
			sep->se_flags);
	if (completely)
		SE_CLR_FLAG(sep, SE_FL_DISABLED);

	if (sep->se_fd < 0 ||
	    !SE_IS_FLAG(sep, SE_FL_W4REQ) ||
	    SE_IS_FLAG(sep, SE_FL_DISABLED)
	   )
		return;

#if SANITY_CHECK
	if (!is_valid_socket(sep->se_fd))
	{
		m_syslog(LOG_ERR,
		       "func=%s, service=%s, status=bad fd",
			__FUNCTION__, sep->se_prg);
		sm_exit(EX_SOFTWARE);
	}
	if (FD_ISSET(sep->se_fd, &Allsock))
	{
		m_syslog(LOG_ERR,
		       "func=%s, service=%s, status=not off",
		       __FUNCTION__, sep->se_prg);
		sm_exit(EX_SOFTWARE);
	}
#endif
	FD_SET(sep->se_fd, &Allsock);
	Nsock++;
	if (sep->se_fd > Maxsock)
		Maxsock = sep->se_fd;
}

/*
**  SE_DISABLE -- Disable a service entry.
**
**	Parameters:
**		sep -- service entry
**		completely -- set DISABLE flag
**
**	Returns:
**		none.
*/

void
se_disable(servtab_P sep, bool completely)
{
	if (Debug)
		warnx("disabling %s, fd %d, completely=%d, children=%d, max=%d"
			, sep->se_prg, sep->se_fd, completely
			, sep->se_numchild, sep->se_maxchild
			);
	if (completely)
		SE_SET_FLAG(sep, SE_FL_DISABLED);
	if (sep->se_fd < 0)
		return;
#if SANITY_CHECK
	if (!is_valid_socket(sep->se_fd))
	{
		m_syslog(LOG_ERR,
		       "func=%s, service=%s, status=bad fd",
		       __FUNCTION__, sep->se_prg);
		sm_exit(EX_SOFTWARE);
	}
	if (!FD_ISSET(sep->se_fd, &Allsock))
	{
		m_syslog(LOG_ERR,
		       "func=%s, service=%s, status=not on",
		       __FUNCTION__, sep->se_prg);
		sm_exit(EX_SOFTWARE);
	}
	if (Nsock == 0)
	{
		m_syslog(LOG_ERR, "func=%s, Nsock=0", __FUNCTION__);
		sm_exit(EX_SOFTWARE);
	}
#endif /* SANITY_CHECK */
	FD_CLR(sep->se_fd, &Allsock);
	Nsock--;
	if (sep->se_fd == Maxsock)
		Maxsock--;
}

#if 0
#ifdef OLD_SETPROCTITLE
static void
inetd_setproctitle(char *a, int s)
{
	int size;
	char *cp;
	struct sockaddr_in sin;
	char buf[80];

	cp = Argv[0];
	size = sizeof(sin);
	if (getpeername(s, (struct sockaddr *) & sin, &size) == 0)
		(void) sprintf(buf, "-%s [%s]", a, inet_ntoa(sin.sin_addr));
	else
		(void) sprintf(buf, "-%s", a);
	strncpy(cp, buf, LastArg - cp);
	cp += strlen(cp);
	while (cp < LastArg)
		*cp++ = ' ';
}
#else
static void
inetd_setproctitle(char *a, int s)
{
	int size;
	struct sockaddr_in sin;
	char buf[80];

	size = sizeof(sin);
	if (getpeername(s, (struct sockaddr *) & sin, &size) == 0)
		(void) sprintf(buf, "%s [%s]", a, inet_ntoa(sin.sin_addr));
	else
		(void) sprintf(buf, "%s", a);
	setproctitle("%s", buf);
}
#endif
#endif /* 0 */

#if MTA_USE_CPML
/*
**  Connections per minute limiter per IP address.
**  Put this into a seperate module?
*/

#define CPMHSIZE	256
#define CPMHMASK	(CPMHSIZE-1)

/* time granularity: 10s (that's one "tick") */
#define CHTGRAN		10
#define CHTSIZE		6

/* Number of connections for a certain "tick" */
typedef struct CTime
{
	ulong	ct_Ticks;
	int	ct_Count;
} CTime_T;

typedef struct CHash
{
	struct in_addr ch_Addr;
	time_t ch_LTime;
	char *ch_Service;

	/* 6 buckets for ticks: 60s */
	CTime_T ch_Times[CHTSIZE];
} CHash_T;

CHash_T CHashAry[CPMHSIZE];

static int
cpmip(servtab_P sep, int ctrl)
{
	struct sockaddr_in rsin;
	socklen_T rsinLen = sizeof(rsin);
	int r = 0;

	/*
	 * If getpeername() fails, just let it through (if logging is
	 * enabled the condition is caught elsewhere)
	 */

	if (sep->se_maxcpm > 0 &&
	    getpeername(ctrl, (struct sockaddr *) & rsin, &rsinLen) == 0)
	{
		time_t t = time(NULLT);
		int hv = 0xABC3D20F;
		int i;
		int cnt = 0;
		CHash_T *chBest = NULL;
		uint ticks = t / CHTGRAN;

		{
			char *p;
			int i;

			/* compute hash value */
			for (i = 0, p = (char *) &rsin.sin_addr;
			     i < sizeof(rsin.sin_addr);
			     ++i, ++p)
				hv = (hv << 5) ^ (hv >> 23) ^ *p;
			hv = (hv ^ (hv >> 16));
		}

		/* What's the magic 5 here? */
		for (i = 0; i < 5; ++i)
		{
			CHash_T *ch = &CHashAry[(hv + i) & CPMHMASK];

			if (rsin.sin_addr.s_addr == ch->ch_Addr.s_addr &&
			    ch->ch_Service != NULL &&
			    strcmp(sep->se_prg, ch->ch_Service) == 0)
			{
				chBest = ch;
				break;
			}
			if (chBest == NULL || ch->ch_LTime == 0 ||
			    ch->ch_LTime < chBest->ch_LTime)
				chBest = ch;
		}

		/*
		**  If it's not a match, then replace the data.
		**  Note: this purges the history of a colliding entry,
		**  which may cause "overruns", i.e., if two entries are
		**  "cancelling" each other out, then they may exceed
		**  the limits that are set. This might be mitigated a bit
		**  by the above "best of 5" function however.
		**
		**  Alternative approach: just use the old data, which may
		**  cause false positives however.
		*/

		if (rsin.sin_addr.s_addr != chBest->ch_Addr.s_addr ||
		    chBest->ch_Service == NULL ||
		    strcmp(sep->se_prg, chBest->ch_Service) != 0)
		{
			chBest->ch_Addr = rsin.sin_addr;
			if (chBest->ch_Service)
				free(chBest->ch_Service);
			chBest->ch_Service = strdup(sep->se_prg);
			bzero(chBest->ch_Times, sizeof(chBest->ch_Times));
		}
		chBest->ch_LTime = t;
		{
			CTime_T  *ct = &chBest->ch_Times[ticks % CHTSIZE];

			if (ct->ct_Ticks != ticks)
			{
				ct->ct_Ticks = ticks;
				ct->ct_Count = 0;
			}
			++ct->ct_Count;
		}
		for (i = 0; i < CHTSIZE; ++i)
		{
			CTime_T *ct = &chBest->ch_Times[i];

			if (ct->ct_Ticks <= ticks &&
			    ct->ct_Ticks >= ticks - CHTSIZE)
			{
				cnt += ct->ct_Count;
			}
		}
		if (cnt * (CHTSIZE * CHTGRAN) / 60 > sep->se_maxcpm)
		{
			r = -1;
			m_syslog(LOG_ERR,
			       "%s from %s exceeded counts/min (limit %d/min)",
			       sep->se_prg, inet_ntoa(rsin.sin_addr),
			       sep->se_maxcpm);
		}
	}
	return r;
}
#endif /* MTA_USE_CPML */


syntax highlighted by Code2HTML, v. 0.9.1