#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ctype.h>
#include <signal.h>
#include <strings.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/stropts.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utmpx.h>
#include <memory.h>
#include "msgs.h"
#include "extern.h"
#include <sac.h>
#include "misc.h"
#include "structs.h"
#include <security/pam_appl.h>
#define RESP 1
#define DEATH 2
static struct sigaction Sigpoll;
static struct sigaction Sigcld;
static struct sigaction Sigalrm;
static sigset_t Origmask;
void usage(void);
void initialize(void);
void startpms(void);
void readutmpx(void);
int startpm(struct sactab *);
void cleanutx(struct sactab *);
void account(struct sactab *, pid_t);
void startit(struct sactab *);
char **mkargv(struct sactab *);
void pollpms(int);
void reap(int);
void pollfail(struct sactab *, int);
void readpipe(void);
int validstate(uchar_t);
int mk_cmd_pipe(void);
void startpoll(void);
int
main(int argc, char *argv[])
{
int c;
struct sigaction sigact;
(void) sigprocmask(SIG_SETMASK, NULL, &Origmask);
if (argc == 1)
usage();
(void) setpgrp();
while ((c = getopt(argc, argv, "t:")) != -1) {
switch (c) {
case 't':
if (Stime != 0)
usage();
Stime = atoi(optarg);
if (Stime <= 0)
usage();
break;
case '?':
usage();
}
}
if (optind < argc)
usage();
initialize();
sigact.sa_flags = 0;
sigact.sa_handler = pollpms;
(void) sigemptyset(&sigact.sa_mask);
(void) sigaddset(&sigact.sa_mask, SIGALRM);
(void) sigaction(SIGALRM, &sigact, &Sigalrm);
pollpms(SIGALRM);
for (;;)
readpipe();
}
void
usage()
{
FILE *fp;
fp = fopen("/dev/console", "w");
if (fp)
(void) fprintf(fp, "SAC: Usage: sac -t sanity_interval\n");
exit(1);
}
void
initialize()
{
int ret;
struct sigaction sigact;
openlog();
log("*** SAC starting ***");
#ifdef DEBUG
opendebug();
log("Debugging turned on");
#endif
if (chdir(HOME) < 0)
error(E_CHDIR, EXIT);
if ((ret = doconfig(-1, SYSCONFIG, 0)) != 0) {
if (ret == -1)
error(E_SYSCONF, EXIT);
else {
(void) sprintf(Scratch,
"Error in _sysconfig: line %d", ret);
log(Scratch);
error(E_BADSYSCONF, EXIT);
}
}
sigact.sa_flags = 0;
sigact.sa_handler = reap;
(void) sigemptyset(&sigact.sa_mask);
(void) sigaddset(&sigact.sa_mask, SIGCLD);
(void) sigaction(SIGCLD, &sigact, &Sigcld);
if (access("_sacpipe", 0) != 0) {
(void) umask(0);
if (mknod("_sacpipe", S_IFIFO | 0600, 0) < 0)
error(E_NOPIPE, EXIT);
}
Sfd = open("_sacpipe", O_RDWR);
if (Sfd < 0)
error(E_NOPIPE, EXIT);
Cfd = mk_cmd_pipe();
read_table(FALSE);
startpoll();
startpms();
}
void
startpms()
{
struct sactab *sp;
int rflag;
pid_t checklock();
rflag = 0;
for (sp = Sactab; sp; sp = sp->sc_next) {
if (checklock(sp)) {
rflag = 1;
sp->sc_sstate = sp->sc_pstate = UNKNOWN;
sp->sc_ok = 1;
sp->sc_exit = 0;
(void) sprintf(Scratch, "%s/_pmpipe", sp->sc_tag);
sp->sc_fd = open(Scratch, O_RDWR);
if (sp->sc_fd < 0) {
(void) sprintf(Scratch, "Could not open "
"_pmpipe for port monitor <%s>",
sp->sc_tag);
log(Scratch);
(void) sendsig(sp, SIGTERM);
sp->sc_ok = 0;
}
}
}
if (rflag) {
readutmpx();
log("SAC in recovery");
return;
}
for (sp = Sactab; sp; sp = sp->sc_next) {
if (sp->sc_flags & X_FLAG) {
continue;
}
(void) startpm(sp);
}
}
void
readutmpx()
{
struct sactab *sp;
struct sactab *savesp;
struct utmpx *uxp;
setutxent();
while (uxp = getutxent()) {
if (uxp->ut_type != LOGIN_PROCESS)
continue;
if (uxp->ut_user[sizeof (uxp->ut_user) - 1] == '\0') {
sp = findpm(uxp->ut_user);
if (sp && (sp->sc_sstate == UNKNOWN)) {
(void) memcpy(sp->sc_utid, uxp->ut_id, IDLEN);
sp->sc_pid = uxp->ut_pid;
}
} else {
savesp = NULL;
for (sp = Sactab; sp; sp = sp->sc_next) {
if (strncmp(uxp->ut_user, sp->sc_tag,
sizeof (uxp->ut_user)) == 0) {
if (savesp) {
savesp = NULL;
(void) sprintf(Scratch,
"ambiguous utmpx entry "
"<%.8s>", sp->sc_tag);
log(Scratch);
break;
} else {
savesp = sp;
}
}
}
if (savesp && (savesp->sc_sstate == UNKNOWN)) {
(void) memcpy(savesp->sc_utid, uxp->ut_id,
IDLEN);
savesp->sc_pid = uxp->ut_pid;
}
}
}
endutxent();
}
int
startpm(struct sactab *sp)
{
sigset_t cset;
sigset_t tset;
pid_t pid;
pid_t checklock();
#ifdef DEBUG
debug("in startpm");
#endif
if (checklock(sp)) {
(void) sprintf(Scratch,
"could not start <%s> - _pid file locked", sp->sc_tag);
log(Scratch);
return (-1);
}
(void) sprintf(Scratch, "%s/_pmpipe", sp->sc_tag);
if (access(Scratch, 0) != 0) {
(void) umask(0);
if (mknod(Scratch, S_IFIFO | 0600, 0) < 0) {
(void) sprintf(Scratch, "Could not create _pmpipe "
"for port monitor <%s>, errno is %d",
sp->sc_tag, errno);
log(Scratch);
return (-2);
}
}
sp->sc_fd = open(Scratch, O_RDWR);
if (sp->sc_fd < 0) {
(void) sprintf(Scratch, "Could not open _pmpipe for port "
"monitor <%s>, errno is %d", sp->sc_tag, errno);
log(Scratch);
return (-2);
}
(void) sigprocmask(SIG_SETMASK, NULL, &cset);
tset = cset;
(void) sigaddset(&tset, SIGCLD);
(void) sigprocmask(SIG_SETMASK, &tset, NULL);
if ((pid = fork()) < 0) {
(void) sprintf(Scratch,
"Could not fork port monitor <%s>", sp->sc_tag);
log(Scratch);
return (-2);
} else if (!pid) {
startit(sp);
}
cleanutx(sp);
account(sp, pid);
sp->sc_pstate = STARTING;
if (sp->sc_lstate == NOTRUNNING)
sp->sc_sstate = (sp->sc_flags & D_FLAG) ? DISABLED : ENABLED;
else
sp->sc_sstate = sp->sc_lstate;
sp->sc_ok = 1;
sp->sc_exit = 0;
sp->sc_pid = pid;
(void) sigprocmask(SIG_SETMASK, &cset, NULL);
return (0);
}
void
cleanutx(struct sactab *sp)
{
int i;
int zerocheck;
char buf[SIZE];
pam_handle_t *pamh;
struct utmpx ut;
struct utmpx *up;
int pid;
char user[sizeof (up->ut_user) + 1];
char ttyn[sizeof (up->ut_line) + 1];
char rhost[sizeof (up->ut_host) + 1];
zerocheck = 0;
for (i = 0; i < IDLEN; ++i) {
zerocheck += sp->sc_utid[i];
}
if (zerocheck == 0)
return;
pid = sp->sc_pid;
setutxent();
while (up = getutxent()) {
if (up->ut_pid == pid) {
if (up->ut_type == DEAD_PROCESS) {
break;
}
strncpy(user, up->ut_user, sizeof (up->ut_user));
user[sizeof (up->ut_user)] = '\0';
strncpy(ttyn, up->ut_line, sizeof (up->ut_line));
ttyn[sizeof (up->ut_line)] = '\0';
strncpy(rhost, up->ut_host, sizeof (up->ut_host));
rhost[sizeof (up->ut_host)] = '\0';
if ((pam_start("sac", user, NULL, &pamh)) ==
PAM_SUCCESS) {
(void) pam_set_item(pamh, PAM_TTY, ttyn);
(void) pam_set_item(pamh, PAM_RHOST, rhost);
(void) pam_close_session(pamh, 0);
pam_end(pamh, PAM_SUCCESS);
}
up->ut_type = DEAD_PROCESS;
up->ut_exit.e_termination = WTERMSIG(sp->sc_exit);
up->ut_exit.e_exit = WEXITSTATUS(sp->sc_exit);
(void) memcpy(up->ut_id, sp->sc_utid,
sizeof (up->ut_id));
(void) time(&up->ut_tv.tv_sec);
if (modutx(up) == NULL) {
(void) pututxline(up);
updwtmpx("wtmpx", up);
}
break;
}
}
endutxent();
}
void
account(struct sactab *sp, pid_t pid)
{
struct utmpx utmpx;
struct utmpx *up = &utmpx;
(void) memset(up, '\0', sizeof (utmpx));
(void) strncpy(up->ut_user, sp->sc_tag, sizeof (up->ut_user));
up->ut_pid = pid;
up->ut_type = LOGIN_PROCESS;
up->ut_id[0] = 'P';
up->ut_id[1] = 'M';
up->ut_id[2] = SC_WILDC;
up->ut_id[3] = SC_WILDC;
(void) time(&up->ut_xtime);
if (makeutx(up) == NULL) {
log("Could not create utmpx entry");
(void) memset(sp->sc_utid, '\0', IDLEN);
} else {
(void) memcpy(sp->sc_utid, up->ut_id, IDLEN);
}
}
void
startit(struct sactab *sp)
{
static char istate[SIZE];
static char pmtag[SIZE];
char **argvp;
int i;
long ndesc;
int ret;
sigset_t cset;
sigset_t tset;
if (chdir(sp->sc_tag) < 0) {
(void) sprintf(Scratch,
"Cannot chdir to <%s/%s>, port monitor not started",
HOME, sp->sc_tag);
log(Scratch);
exit(1);
}
(void) sigprocmask(SIG_SETMASK, NULL, &cset);
tset = cset;
(void) sigaddset(&tset, SIGCLD);
(void) sigprocmask(SIG_SETMASK, &tset, NULL);
if ((ret = doconfig(-1, "_config", 0)) != 0) {
if (ret == -1) {
(void) sprintf(Scratch,
"system error in _config script for <%s>",
sp->sc_tag);
log(Scratch);
exit(1);
} else {
(void) sprintf(Scratch,
"Error in _config script for <%s>: line %d",
sp->sc_tag, ret);
log(Scratch);
exit(1);
}
}
if (sp->sc_lstate == NOTRUNNING)
(void) sprintf(istate, "ISTATE=%s",
(sp->sc_flags & D_FLAG) ? "disabled" : "enabled");
else
(void) sprintf(istate, "ISTATE=%s",
(sp->sc_lstate == DISABLED) ? "disabled" : "enabled");
if (putenv(istate)) {
(void) sprintf(Scratch,
"can't expand port monitor <%s> environment",
sp->sc_tag);
log(Scratch);
exit(1);
}
(void) sprintf(pmtag, "PMTAG=%s", sp->sc_tag);
if (putenv(pmtag)) {
(void) sprintf(Scratch,
"can't expand port monitor <%s> environment",
sp->sc_tag);
log(Scratch);
exit(1);
}
argvp = mkargv(sp);
(void) sprintf(Scratch, "starting port monitor <%s>", sp->sc_tag);
log(Scratch);
ndesc = ulimit(4, 0L);
for (i = 0; i < ndesc; i++)
(void) fcntl(i, F_SETFD, 1);
(void) sigaction(SIGPOLL, &Sigpoll, NULL);
(void) sigaction(SIGCLD, &Sigcld, NULL);
(void) sigaction(SIGALRM, &Sigalrm, NULL);
(void) sigprocmask(SIG_SETMASK, &Origmask, NULL);
(void) execve(argvp[0], argvp, environ);
(void) sprintf(Scratch, "exec of port monitor <%s> failed", sp->sc_tag);
log(Scratch);
exit(1);
}
#define NARGS 50
static char *newargv[NARGS];
static char *delim = " \t'\"";
char **
mkargv(struct sactab *sp)
{
char **argvp = newargv;
char *p = sp->sc_cmd;
char delch;
char *savep;
char *tp;
*argvp = 0;
savep = p;
while (p && *p) {
if (p = strpbrk(p, delim)) {
switch (*p) {
case ' ':
case '\t':
*p++ = '\0';
*argvp++ = savep;
while (isspace(*p))
p++;
savep = p;
break;
case '"':
case '\'':
delch = *p;
savep = ++p;
tp = p;
for (;;) {
if (*p == '\0') {
(void) sprintf(Scratch,
"invalid command line, "
"non-terminated string for "
"port monitor %s",
sp->sc_tag);
log(Scratch);
exit(1);
}
if (*p == delch) {
if (*(tp - 1) == '\\') {
*(tp - 1) = *p;
p++;
} else {
*tp = 0;
*argvp++ = savep;
p++;
while (isspace(*p))
p++;
savep = p;
break;
}
} else {
*tp++ = *p++;
}
}
break;
default:
log("Internal error in parse routine");
exit(1);
}
}
else
*argvp++ = savep;
}
*argvp = 0;
return (newargv);
}
void
pollpms(int signal __unused)
{
struct sactab *sp;
struct sacmsg sacmsg;
#ifdef DEBUG
debug("alarm went off");
#endif
for (sp = Sactab; sp; sp = sp->sc_next) {
if (sp->sc_pstate == NOTRUNNING || sp->sc_pstate == FAILED) {
continue;
}
if (sp->sc_ok == 0) {
pollfail(sp, RESP);
continue;
}
if (sp->sc_sstate == sp->sc_pstate) {
sacmsg.sc_type = SC_STATUS;
sacmsg.sc_size = 0;
} else {
switch (sp->sc_sstate) {
case ENABLED:
sacmsg.sc_type = SC_ENABLE;
sacmsg.sc_size = 0;
break;
case DISABLED:
sacmsg.sc_type = SC_DISABLE;
sacmsg.sc_size = 0;
break;
case STARTING:
case STOPPING:
case NOTRUNNING:
case FAILED:
case UNKNOWN:
sacmsg.sc_type = SC_STATUS;
sacmsg.sc_size = 0;
break;
default:
error(E_BADSTATE, EXIT);
}
}
sendpmmsg(sp, &sacmsg);
sp->sc_ok = 0;
}
(void) alarm(Stime);
}
void
reap(int signo)
{
struct sactab *sp;
pid_t pid;
int status;
pid = wait(&status);
for (sp = Sactab; sp; sp = sp->sc_next) {
if (sp->sc_pid == pid)
break;
}
if (sp == NULL) {
return;
}
sp->sc_exit = status;
if (sp->sc_pstate != NOTRUNNING && sp->sc_pstate != FAILED)
pollfail(sp, DEATH);
}
void
pollfail(struct sactab *sp, int reason)
{
char buf[SIZE];
sigset_t cset;
sigset_t tset;
#ifdef DEBUG
debug("in pollfail");
#endif
cleanutx(sp);
if (sp->sc_pstate == STOPPING) {
(void) sprintf(buf, "<%s> has stopped", sp->sc_tag);
log(buf);
sp->sc_pstate = NOTRUNNING;
sp->sc_lstate = NOTRUNNING;
(void) close(sp->sc_fd);
} else {
(void) sigprocmask(SIG_SETMASK, NULL, &cset);
tset = cset;
(void) sigaddset(&tset, SIGCLD);
(void) sigprocmask(SIG_SETMASK, &tset, NULL);
(void) sendsig(sp, SIGKILL);
if (sp->sc_rscnt < sp->sc_rsmax) {
if (reason == RESP)
(void) sprintf(buf, "<%s> stopped responding "
"to sanity polls - trying to restart",
sp->sc_tag);
else
(void) sprintf(buf,
"<%s> has died - trying to restart",
sp->sc_tag);
log(buf);
sp->sc_rscnt++;
(void) close(sp->sc_fd);
(void) startpm(sp);
} else {
sp->sc_sstate = sp->sc_pstate = FAILED;
(void) close(sp->sc_fd);
(void) sprintf(buf, "<%s> has FAILED", sp->sc_tag);
log(buf);
}
}
(void) sigprocmask(SIG_SETMASK, &cset, NULL);
}
void
readpipe()
{
struct pmmsg pmmsg;
struct pmmsg *pp = &pmmsg;
struct sactab *sp;
int ret;
for (;;) {
if (read(Sfd, pp, sizeof (pmmsg)) < 0) {
if (errno != EINTR)
error(E_BADREAD, EXIT);
continue;
}
while (pp->pm_size) {
ret = read(Sfd, Scratch, (pp->pm_size > SIZE) ?
(unsigned)SIZE : (unsigned)pp->pm_size);
if (ret < 0) {
if (errno != EINTR)
error(E_BADREAD, EXIT);
continue;
}
else
pp->pm_size -= ret;
}
sp = findpm(pp->pm_tag);
if (sp == NULL) {
log("message from unknown process");
continue;
}
switch (pp->pm_type) {
case PM_UNKNOWN:
(void) sprintf(Scratch,
"port monitor <%s> didn't recognize message",
sp->sc_tag);
log(Scratch);
case PM_STATUS:
if (!validstate(pp->pm_state)) {
pp->pm_state = UNKNOWN;
(void) sprintf(Scratch, "port monitor <%s> "
"reporting invalid state", sp->sc_tag);
log(Scratch);
}
if (sp->sc_sstate == sp->sc_pstate) {
if (sp->sc_sstate == UNKNOWN) {
sp->sc_sstate = pp->pm_state;
sp->sc_pstate = pp->pm_state;
if (pp->pm_state == ENABLED ||
pp->pm_state == DISABLED)
sp->sc_lstate = pp->pm_state;
}
if (pp->pm_state != sp->sc_pstate) {
sp->sc_pstate = pp->pm_state;
}
} else if (sp->sc_sstate == pp->pm_state) {
(void) sprintf(Scratch, "port monitor <%s> "
"changed state from %s to %s",
sp->sc_tag, pstate(sp->sc_pstate),
pstate(pp->pm_state));
log(Scratch);
sp->sc_pstate = pp->pm_state;
} else if (sp->sc_pstate != pp->pm_state) {
if (sp->sc_pstate != STOPPING)
sp->sc_pstate = pp->pm_state;
}
break;
default:
(void) sprintf(Scratch, "port monitor <%s> sent an "
"invalid message - ignoring it", sp->sc_tag);
log(Scratch);
break;
}
sp->sc_ok = 1;
sp->sc_maxclass = pp->pm_maxclass;
}
}
int
validstate(uchar_t state)
{
switch (state) {
case PM_ENABLED:
case PM_DISABLED:
case PM_STARTING:
case PM_STOPPING:
return (1);
default:
return (0);
}
}
int
mk_cmd_pipe()
{
int fds[2];
int fd;
(void) unlink(CMDPIPE);
fd = open(CMDPIPE, O_RDWR | O_CREAT, 0600);
if (fd < 0)
error(E_CMDPIPE, EXIT);
close(fd);
if (pipe(fds) < 0)
error(E_PIPE, EXIT);
if (fattach(fds[0], CMDPIPE) < 0)
error(E_FATTACH, EXIT);
return (fds[1]);
}
void
startpoll()
{
struct sigaction sigact;
if (ioctl(Cfd, I_SETSIG, S_INPUT) < 0)
error(E_SETSIG, EXIT);
sigact.sa_flags = 0;
sigact.sa_handler = sigpoll;
(void) sigemptyset(&sigact.sa_mask);
(void) sigaddset(&sigact.sa_mask, SIGPOLL);
(void) sigaction(SIGPOLL, &sigact, &Sigpoll);
}