/*
** 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