/* ** 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 #include "sm/socket.h" #include "sm/filio.h" #include "sm/wait.h" #include "sm/time.h" #include #include #include #include #include #include "sm/fcntl.h" #include #include #include "sm/pwd.h" #include "sm/signal.h" #include #include #include "sm/string.h" #include "sm/syslog.h" #include #include "sm/sysexits.h" #include "sm/misc.h" #include #ifdef LOGIN_CAP #include /* 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 */