2350 lines
54 KiB
C
2350 lines
54 KiB
C
/* Copyright (c) 1984 AT&T */
|
|
/* All Rights Reserved */
|
|
|
|
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
|
|
/* The copyright notice above does not evidence any */
|
|
/* actual or intended publication of such source code. */
|
|
|
|
#ident "$Revision: 1.53 $"
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <dirent.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <ctype.h>
|
|
#include <signal.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <paths.h>
|
|
#include <sat.h>
|
|
#include <clearance.h>
|
|
#include <sys/mac.h>
|
|
#include <sys/capability.h>
|
|
#include <stdarg.h>
|
|
#ifdef CRONPROJ
|
|
#include <proj.h>
|
|
#endif /* CRONPROJ */
|
|
#include "cron.h"
|
|
#include "elm.h"
|
|
#include "funcs.h"
|
|
#include <syslog.h>
|
|
|
|
|
|
#define MAIL "/bin/mail" /* mail program to use */
|
|
#define MAIL_HDR "To: %s\nSubject: cron <%s@%s> %s\n\n"
|
|
|
|
#define TMPINFILE "/tmp/crinXXXXXX" /* file to put stdin in for cmd */
|
|
#define TMPDIR "/tmp"
|
|
#define PFX "crout"
|
|
|
|
#define INMODE S_IRUSR /* mode for stdin file */
|
|
#define FIFOMODE (S_IRUSR | S_IWUSR) /* mode for stdout file */
|
|
#define ISUID (S_ISUID | S_ISGID) /* mode for verifing at jobs */
|
|
|
|
#define INFINITY 2147483647L /* upper bound on time */
|
|
#define MAXFUDGE 7 /* must be a prime */
|
|
#define CUSHION 120L
|
|
#define MAXRUN 25 /* max total jobs allowed in system */
|
|
#define REALMINRUN 1 /* minimum number of jobs allowed */
|
|
#define REALMAXRUN 250 /* maximum number of jobs allowed */
|
|
#define ZOMB 100 /* proc slot used for mailing output */
|
|
|
|
#define JOBF 'j'
|
|
#define NICEF 'n'
|
|
#define USERF 'u'
|
|
#define WAITF 'w'
|
|
|
|
#define BCHAR '>'
|
|
#define ECHAR '<'
|
|
|
|
#define DEFAULT 0
|
|
#define LOAD 1
|
|
|
|
#define BADCD "can't change directory to the crontab directory."
|
|
#define NOREADDIR "can't read the crontab directory."
|
|
|
|
#define BADJOBOPEN "unable to read your at job."
|
|
#define BADSTAT "can't access your crontab file. Resubmit it."
|
|
#define CANTCDHOME "can't change directory to your home dir (%s)\nYour commands will not be executed."
|
|
#define CANTEXECSH "unable to exec the shell for one of your commands: %s"
|
|
#define EOLN "unexpected end of line"
|
|
#define NOREAD "can't read your crontab file. Resubmit it."
|
|
#define NOSTDIN "unable to create a standard input file for one of your crontab commands.\nThat command was not executed."
|
|
#define OUTOFBOUND "number too large or too small for field"
|
|
#define STDOUTERR "one of your commands generated output or errors, but cron was unable to mail you this output.\nRemember to redirect standard output and standard error for each of your commands."
|
|
#define STDATMSG "at job completed."
|
|
#define UNEXPECT "unexpected symbol found"
|
|
#define DIDFORK didfork
|
|
#define NOFORK !didfork
|
|
|
|
|
|
#define DEBUG_TIME() (debug_time = time(0), \
|
|
(void)cftime(debug_tstring, "%T",(&debug_time)), \
|
|
debug_tstring)
|
|
static time_t debug_time;
|
|
static char debug_tstring[100];
|
|
|
|
struct event {
|
|
time_t time; /* time of the event */
|
|
short etype; /* what type of event; 0=cron, 1=at */
|
|
char *cmd; /* command for cron, job name for at */
|
|
struct usr *u; /* ptr to the owner (usr) of this event */
|
|
struct event *link; /* ptr to another event for this user */
|
|
int sm; /* if true, send mail always */
|
|
union {
|
|
struct { /* for crontab events */
|
|
char *minute; /* (these */
|
|
char *hour; /* fields */
|
|
char *daymon; /* are */
|
|
char *month; /* from */
|
|
char *dayweek; /* crontab) */
|
|
char *input; /* ptr to stdin */
|
|
} ct;
|
|
struct { /* for at events */
|
|
short exists; /* for revising at events */
|
|
int eventid; /* for el_remove-ing at events */
|
|
} at;
|
|
} of;
|
|
};
|
|
|
|
struct usr {
|
|
char *name; /* name of user (e.g. "root") */
|
|
char *home; /* home directory for user */
|
|
uid_t uid; /* user id */
|
|
gid_t gid; /* group id */
|
|
mac_t mac; /* MAC label to run at, if appropriate */
|
|
#ifdef ATLIMIT
|
|
int aruncnt; /* counter for running jobs per uid */
|
|
#endif
|
|
#ifdef CRONLIMIT
|
|
int cruncnt; /* counter for running cron jobs per uid */
|
|
#endif
|
|
int ctid; /* for el_remove-ing crontab events */
|
|
short ctexists; /* for revising crontab events */
|
|
struct event *ctevents; /* list of this usr's crontab events */
|
|
struct event *atevents; /* list of this usr's at events */
|
|
struct usr *nextusr; /* ptr to next user */
|
|
int refcnt; /* reference count */
|
|
};
|
|
|
|
/*
|
|
* These macros are used to manage references to a user structure
|
|
*/
|
|
#define USR_HOLD(u) ((u)->refcnt++)
|
|
#define USR_RELE(u) { \
|
|
if (--(u)->refcnt <= 0) { \
|
|
free(u->name); \
|
|
free(u->home); \
|
|
mac_free(u->mac); \
|
|
free(u); \
|
|
} \
|
|
}
|
|
|
|
struct queue
|
|
{
|
|
int njob; /* limit */
|
|
int nice; /* nice for execution */
|
|
int nwait; /* wait time to next execution attempt */
|
|
int nrun; /* number running */
|
|
}
|
|
qd = {100, 2, 60}, /* default values for queue defs */
|
|
qt[NQUEUE];
|
|
|
|
struct queue qq;
|
|
int wait_time = 60;
|
|
|
|
|
|
/*
|
|
* The runinfo table is allocated based on the maximum number of jobs
|
|
* that can be simultaneously invoked from cron. The default is
|
|
* MAXRUN. The user can specify a different number using the '-j'
|
|
* option.
|
|
*
|
|
* It should be noted that the number chosen for the highest '-j'
|
|
* value is arbitrary -- the current algorithms for traversing the
|
|
* run table are array-based and inherently inefficient.
|
|
*/
|
|
struct runinfo
|
|
{
|
|
pid_t pid;
|
|
short que;
|
|
short etype; /* 0=cron 1=at */
|
|
struct usr *rusr; /* pointer to usr struct */
|
|
char *cmd; /* command running */
|
|
int sm; /* send mail */
|
|
int outfd; /* descriptor where stdout & stderr go */
|
|
time_t start_time;
|
|
time_t end_time;
|
|
} *rt;
|
|
|
|
short didfork = 0; /* flag to see if I'm process group leader */
|
|
int msgfd; /* file descriptor for fifo queue */
|
|
int ecid = 1; /* for giving event classes distinguishable id names
|
|
for el_remove'ing them. MUST be initialized to 1 */
|
|
int delayed; /* is job being rescheduled or did it run first time */
|
|
int cwd; /* current working directory */
|
|
int running; /* zero when no jobs are executing */
|
|
char *atdir = ATDIR;
|
|
struct event *next_event; /* the next event to execute */
|
|
struct usr *uhead; /* ptr to the list of users */
|
|
struct usr *ulast; /* ptr to last usr table entry */
|
|
time_t init_time;
|
|
|
|
char hostname[MAXHOSTNAMELEN+1]; /* for local hostname */
|
|
|
|
/* audit, capabilities & mandatory access control */
|
|
int audit_enabled; /* set iff sysconf(_SC_AUDIT) > 0 */
|
|
int mac_enabled; /* set iff sysconf(_SC_MAC) > 0 */
|
|
mac_t mac_moldy; /* mac label for reading :mac directories */
|
|
cap_t ecap; /* empty capability set */
|
|
size_t ncap = 2; /* number of entries in capset */
|
|
cap_value_t capset[10] = {CAP_SETGID, CAP_SETUID};
|
|
|
|
/* user's default environment for the shell */
|
|
char homedir[5+PATH_MAX]="HOME=";
|
|
char path[5+PATH_MAX]="PATH=";
|
|
char shell[6+PATH_MAX]="SHELL=";
|
|
#define ENV_SIZE 64
|
|
char logname[8+ENV_SIZE]="LOGNAME=";
|
|
char env_user[6+ENV_SIZE]="USER=";
|
|
char tzone[256]="TZ=";
|
|
char *envinit[]={
|
|
homedir,
|
|
logname,
|
|
env_user,
|
|
path,
|
|
shell,
|
|
tzone,
|
|
0};
|
|
unsigned int secfudge; /* delay after the minute tick by this; this
|
|
lack of punctuality reduces etherstorms */
|
|
int reinit = 0;
|
|
int maxrun = MAXRUN;
|
|
|
|
extern char **environ;
|
|
|
|
/* at job pathname generation */
|
|
char *at_cmd_create(const struct event *);
|
|
|
|
/* abort processing on error */
|
|
void crabort(const char *, int);
|
|
|
|
/* MAC utility functions */
|
|
mac_t mac_swap(mac_t);
|
|
void mac_restore(mac_t);
|
|
mac_t getflabel(const char *);
|
|
int setflabel(const char *, const char *);
|
|
|
|
/* signal handlers */
|
|
void cronend(int);
|
|
void cronhup(int);
|
|
void timeout(int);
|
|
|
|
/* creation/deletion of at & cron jobs */
|
|
void del_atjob(const char *, const char *, mac_t);
|
|
void mod_atjob(const char *, mac_t);
|
|
void add_atevent(struct usr *, const char *, time_t, short, int);
|
|
void del_ctab(const char *, mac_t);
|
|
void mod_ctab(const char *, mac_t);
|
|
void rm_ctevents(struct usr *);
|
|
|
|
/* directory scanning for at & cron jobs */
|
|
void dscan(DIR *, void (*)(const char *, mac_t));
|
|
|
|
/* moldy subdirectory handling for at & cron jobs */
|
|
void read_cron_mac(const char *, mac_t);
|
|
void read_at_mac(const char *, mac_t);
|
|
|
|
/* job execution */
|
|
void ex(const struct event *);
|
|
|
|
/* crontab file parser */
|
|
void readcron(struct usr *);
|
|
char *next_field(int, int, const struct usr *);
|
|
time_t next_time(const struct event *);
|
|
int next_ge(int, const char *);
|
|
|
|
/* queue definitions file parser */
|
|
void quedefs(int);
|
|
void parsqdef(const char *);
|
|
|
|
/* job loop */
|
|
void msg_wait(time_t, int);
|
|
|
|
/* job scheduling */
|
|
void resched(int);
|
|
|
|
/* job reaping & cleanup */
|
|
void idle(time_t);
|
|
void cleanup(struct runinfo *, int);
|
|
|
|
/* job search & match */
|
|
struct usr *find_usr(const char *, mac_t);
|
|
|
|
/* initialization */
|
|
void initialize(int);
|
|
void read_dirs(void);
|
|
int maxfds(void);
|
|
|
|
/* logging & job notification*/
|
|
void logit(char, struct runinfo *, int);
|
|
void mail(const char *, const char *, const char *, int);
|
|
void msg(const char *, ...);
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
time_t t, t_old;
|
|
time_t last_time;
|
|
time_t ne_time; /* amt of time until next event execution */
|
|
time_t lastmtime = 0L;
|
|
struct usr *u, *u2;
|
|
struct event *e, *e2, *eprev;
|
|
struct stat buf;
|
|
int c;
|
|
pid_t rfork = 0;
|
|
struct sigaction act;
|
|
|
|
/* initialization for systems supporting MAC */
|
|
if (mac_enabled = (sysconf(_SC_MAC) > 0)) {
|
|
mac_t omac = mac_get_proc();
|
|
if (omac == NULL) {
|
|
fprintf(stderr, "unable to get process MAC label\n");
|
|
exit(1);
|
|
}
|
|
mac_moldy = mac_set_moldy(omac);
|
|
mac_free(omac);
|
|
if (mac_moldy == NULL) {
|
|
fprintf(stderr, "unable to get moldy MAC label\n");
|
|
exit(1);
|
|
}
|
|
atdir = MACATDIR;
|
|
|
|
capset[ncap++] = CAP_MAC_RELABEL_OPEN;
|
|
capset[ncap++] = CAP_MAC_RELABEL_SUBJ;
|
|
capset[ncap++] = CAP_MAC_READ;
|
|
capset[ncap++] = CAP_MAC_MLD;
|
|
}
|
|
|
|
/* initialization for systems supporting audit */
|
|
if (audit_enabled = (sysconf(_SC_AUDIT) > 0)) {
|
|
capset[ncap++] = CAP_AUDIT_CONTROL;
|
|
capset[ncap++] = CAP_AUDIT_WRITE;
|
|
}
|
|
|
|
/* check privileges of caller */
|
|
if (cap_envp(0, ncap, capset) == -1) {
|
|
fprintf(stderr, "insufficient privilege\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* increase number of open files to system max */
|
|
if (maxfds() == -1) {
|
|
fprintf(stderr, "unable to increase fd limit\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* create empty capability set for children */
|
|
if ((ecap = cap_init()) == NULL) {
|
|
perror("cap_init");
|
|
exit(1);
|
|
}
|
|
|
|
for (c = 1; c < argc; c++) {
|
|
if (!strcmp(argv[c], "nofork")) {
|
|
rfork++;
|
|
continue;
|
|
} else if (!strcmp(argv[c], "-j")) {
|
|
c++;
|
|
if (c >= argc) {
|
|
fprintf(stderr, "-j missing argument, ignored\n");
|
|
continue;
|
|
}
|
|
sscanf(argv[c], "%d", &maxrun);
|
|
if (maxrun < REALMINRUN || maxrun > REALMAXRUN) {
|
|
fprintf(stderr,
|
|
"-j must be between %d and %d\n",
|
|
REALMINRUN, REALMAXRUN);
|
|
exit(1);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Unknown argument '%s' ignored\n",
|
|
argv[c]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocate runtable
|
|
*/
|
|
rt = calloc(maxrun, sizeof(struct runinfo));
|
|
if (!rt) {
|
|
fprintf(stderr, "Unable to allocate runtable of size %d\n",
|
|
maxrun);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* We use LOG_NDELAY here because there may a out-of-memory
|
|
* condition later which may require a syslog entry. So, we want
|
|
* to make sure we can open a direct path to the syslogd daemon.
|
|
*/
|
|
openlog("cron", LOG_PID|LOG_NDELAY|LOG_PERROR, LOG_DAEMON);
|
|
|
|
if (!rfork) {
|
|
begin:
|
|
if (rfork = fork()) {
|
|
if (rfork == -1) {
|
|
(void)sleep(30);
|
|
goto begin;
|
|
}
|
|
exit(0);
|
|
}
|
|
didfork++;
|
|
setpgrp(); /* detach cron from console */
|
|
}
|
|
|
|
|
|
umask(022);
|
|
secfudge = (unsigned)gethostid() % MAXFUDGE;
|
|
|
|
/* set up signal handling */
|
|
act.sa_flags = 0;
|
|
(void) sigemptyset(&act.sa_mask);
|
|
act.sa_handler = SIG_IGN;
|
|
(void) sigaction(SIGINT, &act, (struct sigaction *) NULL);
|
|
(void) sigaction(SIGQUIT, &act, (struct sigaction *) NULL);
|
|
act.sa_handler = cronhup;
|
|
(void) sigaction(SIGHUP, &act, (struct sigaction *) NULL);
|
|
act.sa_handler = cronend;
|
|
(void) sigaction(SIGTERM, &act, (struct sigaction *) NULL);
|
|
act.sa_handler = timeout;
|
|
(void) sigaction(SIGALRM, &act, (struct sigaction *) NULL);
|
|
|
|
if (gethostname(hostname, MAXHOSTNAMELEN) < 0)
|
|
strcpy(hostname, "UNKNOWN");
|
|
|
|
initialize(1);
|
|
quedefs(DEFAULT); /* load default queue definitions */
|
|
msg("*** cron started *** pid = %d", (int) getpid());
|
|
t_old = time(0);
|
|
last_time = t_old;
|
|
for (;;) { /* MAIN LOOP */
|
|
t = time(0);
|
|
if (reinit || t_old > t || t-last_time > CUSHION+MAXFUDGE) {
|
|
reinit = 0;
|
|
/* the time was set backwards or forward */
|
|
el_delete();
|
|
u = uhead;
|
|
while (u!=NULL) {
|
|
rm_ctevents(u);
|
|
e = u->atevents;
|
|
while (e!=NULL) {
|
|
free(e->cmd);
|
|
e2 = e->link;
|
|
free(e);
|
|
e = e2;
|
|
}
|
|
u2 = u->nextusr;
|
|
USR_RELE(u);
|
|
u = u2;
|
|
}
|
|
close(msgfd);
|
|
initialize(0);
|
|
t = time(0);
|
|
}
|
|
t_old = t;
|
|
if (next_event == NULL) {
|
|
if (el_empty()) ne_time = INFINITY;
|
|
else {
|
|
next_event = (struct event *) el_first();
|
|
ne_time = next_event->time - t;
|
|
}
|
|
} else {
|
|
ne_time = next_event->time - t;
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"next_time=%ld %s",
|
|
next_event->time,ctime(&next_event->time));
|
|
#endif
|
|
}
|
|
if (ne_time > 0) {
|
|
idle(ne_time);
|
|
if (!next_event || reinit) {
|
|
last_time = INFINITY;
|
|
continue;
|
|
}
|
|
}
|
|
if (stat(QUEDEFS,&buf)) {
|
|
if (lastmtime != -1)
|
|
msg("cannot stat %s", QUEDEFS);
|
|
lastmtime = -1;
|
|
} else {
|
|
if(lastmtime != buf.st_mtime) {
|
|
quedefs(LOAD);
|
|
lastmtime = buf.st_mtime;
|
|
}
|
|
}
|
|
last_time = next_event->time; /* save execution time */
|
|
ex(next_event);
|
|
switch(next_event->etype) {
|
|
/* add cronevent back into the main event list */
|
|
case CRONEVENT:
|
|
if(delayed) {
|
|
delayed = 0;
|
|
break;
|
|
}
|
|
next_event->time = next_time(next_event);
|
|
el_add( next_event,next_event->time,
|
|
(next_event->u)->ctid );
|
|
break;
|
|
/* remove at or batch job from system */
|
|
default:
|
|
eprev=NULL;
|
|
e=(next_event->u)->atevents;
|
|
while (e != NULL)
|
|
if (e == next_event) {
|
|
if (eprev == NULL)
|
|
(e->u)->atevents = e->link;
|
|
else eprev->link = e->link;
|
|
free(e->cmd);
|
|
free(e);
|
|
break;
|
|
}
|
|
else {
|
|
eprev = e;
|
|
e = e->link;
|
|
}
|
|
break;
|
|
}
|
|
next_event = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
cronhup(int sig)
|
|
{
|
|
fflush(stderr);
|
|
if (freopen(ACCTFILE,"a",stdout) == NULL) {
|
|
msg("cannot open %s", ACCTFILE);
|
|
} else {
|
|
close(fileno(stderr));
|
|
dup2(1,2);
|
|
msg("*** cron log restarted *** pid=%d", (int) getpid());
|
|
}
|
|
reinit = 1;
|
|
}
|
|
|
|
|
|
void
|
|
initialize(int firstpass)
|
|
{
|
|
static int flag = 0;
|
|
char *tz;
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"%s: in initialize\n",DEBUG_TIME());
|
|
#endif
|
|
init_time = time((long *) 0);
|
|
el_init(8,init_time,(long)(60*60*24),10);
|
|
if(firstpass)
|
|
if(access(FIFO,R_OK|W_OK) == -1) {
|
|
if(errno == ENOENT) {
|
|
if(mknod(FIFO,S_IFIFO|FIFOMODE,0)!=0)
|
|
crabort("cannot create fifo queue",1);
|
|
if (setflabel(FIFO, FIFOLBL) == -1)
|
|
crabort("cannot set fifo label", 1);
|
|
}
|
|
else {
|
|
if(NOFORK) {
|
|
/* didn't fork... init(1M) is waiting */
|
|
(void)sleep(60);
|
|
}
|
|
perror("FIFO");
|
|
crabort("cannot access fifo queue",1);
|
|
}
|
|
}
|
|
else {
|
|
if(NOFORK) {
|
|
/* didn't fork... init(1M) is waiting
|
|
* the wait is painful, but we don't
|
|
* want init respawning this quickly */
|
|
(void)sleep(60);
|
|
}
|
|
crabort("cannot start cron; FIFO exists",0);
|
|
}
|
|
if((msgfd = open(FIFO, O_RDWR)) < 0) {
|
|
perror("! open");
|
|
crabort("cannot open fifo queue",1);
|
|
}
|
|
if (set_cloexec(msgfd) == -1) {
|
|
perror("FIFO close-on-exec");
|
|
crabort("cannot set close-on-exec for fifo queue", 1);
|
|
}
|
|
/* check for possible NULL or overflow */
|
|
if ((tz = getenv("TZ")) == NULL)
|
|
crabort("no TZ evironment variable", 1);
|
|
if (strlen(tz) + 4 > sizeof(tzone))
|
|
crabort("TZ evironment variable too long", 1);
|
|
(void)sprintf(tzone,"TZ=%s",tz);
|
|
(void)sprintf(path,"PATH=%s",_PATH_ROOTPATH);
|
|
(void)sprintf(shell,"SHELL=%s",_PATH_BSHELL);
|
|
|
|
/* read directories, create users list,
|
|
and add events to the main event list */
|
|
uhead = NULL;
|
|
read_dirs();
|
|
next_event = NULL;
|
|
if(flag)
|
|
return;
|
|
if(freopen(ACCTFILE,"a",stdout) == NULL)
|
|
fprintf(stderr,"cannot open %s\n",ACCTFILE);
|
|
close(fileno(stderr));
|
|
dup(1);
|
|
/* this must be done to make popen work....i dont know why */
|
|
freopen("/dev/null","r",stdin);
|
|
flag = 1;
|
|
}
|
|
|
|
|
|
void
|
|
read_dirs(void)
|
|
{
|
|
DIR *dirp;
|
|
mac_t omac;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"%s: Read Directories\n",DEBUG_TIME());
|
|
#endif
|
|
if (chdir(CRONDIR) == -1)
|
|
crabort(BADCD, 1);
|
|
cwd = CRON;
|
|
dirp = opendir(".");
|
|
if (dirp != NULL) {
|
|
dscan(dirp, mod_ctab);
|
|
closedir(dirp);
|
|
} else {
|
|
crabort(NOREADDIR, 1);
|
|
}
|
|
if (mac_enabled) {
|
|
omac = mac_swap(mac_moldy);
|
|
if (chdir(MACCRONDIR) == -1)
|
|
crabort(BADCD, 1);
|
|
cwd = TRIX_CRON;
|
|
dirp = opendir(".");
|
|
if (dirp != NULL) {
|
|
dscan(dirp, read_cron_mac);
|
|
closedir(dirp);
|
|
} else {
|
|
mac_restore(omac);
|
|
crabort(NOREADDIR, 1);
|
|
}
|
|
mac_restore(omac);
|
|
}
|
|
omac = mac_swap(mac_moldy);
|
|
if (chdir(atdir) == -1) {
|
|
mac_restore(omac);
|
|
msg("Cannot chdir to at directory");
|
|
return;
|
|
}
|
|
cwd = (mac_enabled ? TRIX_AT : AT);
|
|
dirp = opendir(".");
|
|
if (dirp != NULL) {
|
|
dscan(dirp, mac_enabled ? read_at_mac : mod_atjob);
|
|
closedir(dirp);
|
|
} else {
|
|
msg("cannot chdir to at directory");
|
|
}
|
|
mac_restore(omac);
|
|
}
|
|
|
|
void
|
|
dscan(DIR *df, void (*fp)(const char *, mac_t))
|
|
{
|
|
struct dirent *dp;
|
|
mac_t label;
|
|
|
|
seekdir(df, (long) 0);
|
|
for (dp = readdir(df); dp != NULL; dp = readdir(df)) {
|
|
/* ignore filenames beginning with `:', such as :mac */
|
|
if (mac_enabled && dp->d_name[0] == ':')
|
|
continue;
|
|
|
|
/* ignore `.' and `..' */
|
|
if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
|
|
continue;
|
|
|
|
label = getflabel(dp->d_name);
|
|
(*fp)(dp->d_name, label);
|
|
mac_free(label);
|
|
}
|
|
}
|
|
|
|
void
|
|
read_mac(const char *name, mac_t label, void (*fp)(const char *, mac_t))
|
|
{
|
|
DIR *dirp;
|
|
mac_t omac;
|
|
|
|
if (*name == '.' || *name == ':' || *name == '_')
|
|
return;
|
|
omac = mac_swap(label);
|
|
if ((dirp = opendir(name)) == NULL) {
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
if (chdir(name) == -1) {
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
dscan(dirp, fp);
|
|
closedir(dirp);
|
|
mac_restore(omac);
|
|
switch(cwd) {
|
|
case CRON:
|
|
(void) chdir(CRONDIR);
|
|
break;
|
|
case TRIX_CRON:
|
|
(void) chdir(MACCRONDIR);
|
|
break;
|
|
case AT:
|
|
case TRIX_AT:
|
|
(void) chdir(atdir);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
read_cron_mac(const char *name, mac_t label)
|
|
{
|
|
read_mac(name, label, mod_ctab);
|
|
}
|
|
|
|
void
|
|
read_at_mac(const char *name, mac_t label)
|
|
{
|
|
read_mac(name, label, mod_atjob);
|
|
}
|
|
|
|
void
|
|
mod_ctab(const char *name, mac_t label)
|
|
{
|
|
struct clearance *clp;
|
|
struct passwd *pw;
|
|
struct stat buf;
|
|
struct usr *u;
|
|
char namebuf[PATH_MAX];
|
|
mac_t omac;
|
|
|
|
if((pw=getpwnam(name)) == NULL)
|
|
return;
|
|
if (mac_enabled) {
|
|
if ((clp = sgi_getclearancebyname(name)) == NULL)
|
|
return;
|
|
if (mac_clearedlbl(clp, label) != MAC_CLEARED)
|
|
return;
|
|
}
|
|
strcat(strcat(strcpy(namebuf, !mac_enabled || cwd == CRON ? CRONDIR : MACCRONDIR), "/"), name);
|
|
omac = mac_swap(label);
|
|
if(stat(namebuf,&buf)) {
|
|
mail(name,BADSTAT,NULL,2);
|
|
unlink(namebuf);
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
if((u=find_usr(name, label)) == NULL) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"new user (%s) with a crontab\n",name);
|
|
#endif
|
|
u = (struct usr *) xmalloc(sizeof(struct usr));
|
|
u->name = xmalloc(strlen(name)+1);
|
|
strcpy(u->name,name);
|
|
u->home = xmalloc(strlen(pw->pw_dir)+1);
|
|
strcpy(u->home,pw->pw_dir);
|
|
u->uid = pw->pw_uid;
|
|
u->gid = pw->pw_gid;
|
|
if (mac_enabled) {
|
|
u->mac = mac_dup(label);
|
|
if (u->mac == NULL) {
|
|
free(u->home);
|
|
free(u->name);
|
|
free(u);
|
|
unlink(namebuf);
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
} else {
|
|
u->mac = NULL;
|
|
}
|
|
u->ctexists = TRUE;
|
|
u->ctid = ecid++;
|
|
u->ctevents = NULL;
|
|
u->atevents = NULL;
|
|
#ifdef ATLIMIT
|
|
u->aruncnt = 0;
|
|
#endif
|
|
#ifdef CRONLIMIT
|
|
u->cruncnt = 0;
|
|
#endif
|
|
u->nextusr = uhead;
|
|
u->refcnt = 1;
|
|
uhead = u;
|
|
readcron(u);
|
|
} else {
|
|
u->uid = pw->pw_uid;
|
|
u->gid = pw->pw_gid;
|
|
if(strcmp(u->home,pw->pw_dir) != 0) {
|
|
free(u->home);
|
|
u->home = xmalloc(strlen(pw->pw_dir)+1);
|
|
strcpy(u->home,pw->pw_dir);
|
|
}
|
|
u->ctexists = TRUE;
|
|
if(u->ctid == 0) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"%s now has a crontab\n",u->name);
|
|
#endif
|
|
/* user didnt have a crontab last time */
|
|
u->ctid = ecid++;
|
|
u->ctevents = NULL;
|
|
readcron(u);
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"%s has revised his crontab\n",u->name);
|
|
#endif
|
|
rm_ctevents(u);
|
|
el_remove(u->ctid,0);
|
|
readcron(u);
|
|
}
|
|
mac_restore(omac);
|
|
}
|
|
|
|
|
|
void
|
|
mod_atjob(const char *name, mac_t label)
|
|
{
|
|
time_t tim;
|
|
struct passwd *pw;
|
|
struct stat buf;
|
|
struct usr *u;
|
|
struct event *e;
|
|
char namebuf[PATH_MAX], *ptr;
|
|
short atqtype;
|
|
int smval;
|
|
mac_t omac;
|
|
|
|
errno = 0;
|
|
tim = (time_t) strtol(name, &ptr, 10);
|
|
if (ptr == name || errno == ERANGE || *ptr != '.')
|
|
return;
|
|
ptr++;
|
|
if ( ! (isascii(*ptr) && islower(*ptr)) )
|
|
return;
|
|
atqtype = *ptr - 'a';
|
|
smval = (*++ptr == 'm');
|
|
|
|
strcat(strcat(strcpy(namebuf, atdir), "/"), name);
|
|
omac = mac_swap(label);
|
|
if(stat(namebuf,&buf) || atqtype >= NQUEUE) {
|
|
unlink(namebuf);
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
if((buf.st_mode & ISUID) != ISUID) {
|
|
unlink(namebuf);
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
if((pw=getpwuid(buf.st_uid)) == NULL) {
|
|
unlink(namebuf);
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
if((u=find_usr(pw->pw_name, label)) == NULL) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"new user (%s) with an at job = %s\n",pw->pw_name,name);
|
|
#endif
|
|
u = (struct usr *) xmalloc(sizeof(struct usr));
|
|
u->name = xmalloc(strlen(pw->pw_name)+1);
|
|
strcpy(u->name,pw->pw_name);
|
|
u->home = xmalloc(strlen(pw->pw_dir)+1);
|
|
strcpy(u->home,pw->pw_dir);
|
|
u->uid = pw->pw_uid;
|
|
if (mac_enabled) {
|
|
u->mac = mac_dup(label);
|
|
if (u->mac == NULL) {
|
|
free(u->home);
|
|
free(u->name);
|
|
free(u);
|
|
unlink(namebuf);
|
|
mac_restore(omac);
|
|
return;
|
|
}
|
|
} else {
|
|
u->mac = NULL;
|
|
}
|
|
/* If the user has executed a newgrp, have the
|
|
* at job run with that group id.
|
|
*/
|
|
u->gid = buf.st_gid;
|
|
u->ctexists = FALSE;
|
|
u->ctid = 0;
|
|
u->ctevents = NULL;
|
|
u->atevents = NULL;
|
|
#ifdef ATLIMIT
|
|
u->aruncnt = 0;
|
|
#endif
|
|
#ifdef CRONLIMIT
|
|
u->cruncnt = 0;
|
|
#endif
|
|
u->nextusr = uhead;
|
|
u->refcnt = 1;
|
|
uhead = u;
|
|
add_atevent(u,name,tim,atqtype,smval);
|
|
} else {
|
|
u->uid = pw->pw_uid;
|
|
/* If the user has executed a newgrp, have the
|
|
* at job run with that group id.
|
|
*/
|
|
u->gid = buf.st_gid;
|
|
if(strcmp(u->home,pw->pw_dir) != 0) {
|
|
free(u->home);
|
|
u->home = xmalloc(strlen(pw->pw_dir)+1);
|
|
strcpy(u->home,pw->pw_dir);
|
|
}
|
|
e = u->atevents;
|
|
while(e != NULL)
|
|
if(strcmp(e->cmd,name) == 0) {
|
|
e->of.at.exists = TRUE;
|
|
break;
|
|
} else
|
|
e = e->link;
|
|
if (e == NULL) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"%s has a new at job = %s\n",u->name,name);
|
|
#endif
|
|
add_atevent(u,name,tim,atqtype,smval);
|
|
}
|
|
}
|
|
mac_restore(omac);
|
|
}
|
|
|
|
|
|
void
|
|
add_atevent(struct usr *u, const char *job, time_t tim, short atqtype, int sm)
|
|
{
|
|
struct event *e;
|
|
|
|
e=(struct event *) xmalloc(sizeof(struct event));
|
|
e->etype = atqtype;
|
|
e->cmd = xmalloc(strlen(job)+1);
|
|
e->sm = sm;
|
|
strcpy(e->cmd,job);
|
|
e->u = u;
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"add_atevent: user=%s, job=%s, time=%ld\n",
|
|
u->name,e->cmd, e->time);
|
|
#endif
|
|
e->link = u->atevents;
|
|
u->atevents = e;
|
|
e->of.at.exists = TRUE;
|
|
e->of.at.eventid = ecid++;
|
|
if(tim < init_time) /* old job */
|
|
e->time = init_time;
|
|
else
|
|
e->time = tim;
|
|
el_add(e, e->time, e->of.at.eventid);
|
|
}
|
|
|
|
|
|
char line[CTLINESIZE]; /* holds a line from a crontab file */
|
|
int cursor; /* cursor for the above line */
|
|
|
|
void
|
|
readcron(struct usr *u)
|
|
{
|
|
/* readcron reads in a crontab file for a user (u).
|
|
The list of events for user u is built, and
|
|
u->events is made to point to this list.
|
|
Each event is also entered into the main event list. */
|
|
|
|
FILE *cf; /* cf will be a user's crontab file */
|
|
struct event *e;
|
|
int start, i;
|
|
char namebuf[PATH_MAX];
|
|
|
|
/* read the crontab file */
|
|
strcat(strcat(strcpy(namebuf, !mac_enabled || cwd == CRON ? CRONDIR : MACCRONDIR), "/"), u->name);
|
|
if ((cf=fopen(namebuf,"r")) == NULL) {
|
|
mail(u->name,NOREAD,NULL,2);
|
|
return;
|
|
}
|
|
while (fgets(line,CTLINESIZE,cf) != NULL) {
|
|
/* process a line of a crontab file */
|
|
cursor = 0;
|
|
while(line[cursor] == ' ' || line[cursor] == '\t')
|
|
cursor++;
|
|
/* commented line OR empty line - ariel */
|
|
if(line[cursor] == '#' || line[cursor] == '\n')
|
|
continue;
|
|
e = (struct event *) xmalloc(sizeof(struct event));
|
|
e->etype = CRONEVENT;
|
|
e->of.ct.minute = NULL;
|
|
e->of.ct.hour = NULL;
|
|
e->of.ct.daymon = NULL;
|
|
e->of.ct.month = NULL;
|
|
e->of.ct.dayweek = NULL;
|
|
if ((e->of.ct.minute=next_field(0,59,u)) == NULL) goto badline;
|
|
if ((e->of.ct.hour=next_field(0,23,u)) == NULL) goto badline;
|
|
if ((e->of.ct.daymon=next_field(1,31,u)) == NULL) goto badline;
|
|
if ((e->of.ct.month=next_field(1,12,u)) == NULL) goto badline;
|
|
if ((e->of.ct.dayweek=next_field(0,6,u)) == NULL) goto badline;
|
|
if (line[++cursor] == '\0') {
|
|
mail(u->name,EOLN,NULL,1);
|
|
goto badline;
|
|
}
|
|
/* get the command to execute */
|
|
/* skip leading white space */
|
|
while (line[cursor] == ' ' || line[cursor] == '\t')
|
|
cursor++;
|
|
start = cursor;
|
|
again:
|
|
while ((line[cursor]!='%')&&(line[cursor]!='\n')
|
|
&&(line[cursor]!='\0') && (line[cursor]!='\\')) cursor++;
|
|
if(line[cursor] == '\\') {
|
|
if (line[++cursor]=='%') {
|
|
for (i=cursor; (line[i]!='\n')&&(line[i]!='\0');
|
|
i++) {
|
|
line[i-1] = line[i];
|
|
}
|
|
line[i-1] = '\0';
|
|
}
|
|
else
|
|
cursor++;
|
|
goto again;
|
|
}
|
|
e->cmd = xmalloc(cursor-start+1);
|
|
strncpy(e->cmd,line+start,cursor-start);
|
|
e->cmd[cursor-start] = '\0';
|
|
/* see if there is any standard input */
|
|
if (line[cursor] == '%') {
|
|
e->of.ct.input = xmalloc(strlen(line)-cursor+1);
|
|
strcpy(e->of.ct.input,line+cursor+1);
|
|
for (i=0; i<strlen(e->of.ct.input); i++)
|
|
if (e->of.ct.input[i] == '%') e->of.ct.input[i] = '\n';
|
|
}
|
|
else e->of.ct.input = NULL;
|
|
/* have the event point to it's owner */
|
|
e->u = u;
|
|
|
|
/* insert this event at the front of this user's event list */
|
|
e->link = u->ctevents;
|
|
u->ctevents = e;
|
|
|
|
/* set the time for the first occurance of this event */
|
|
e->time = next_time(e);
|
|
e->sm = 0;
|
|
|
|
/* finally, add this event to the main event list */
|
|
el_add(e,e->time,u->ctid);
|
|
#ifdef DEBUG
|
|
(void)cftime(debug_tstring, "%D %T", &e->time);
|
|
fprintf(stderr,"inserting cron event \"%s\" at %ld=%s\n",
|
|
e->cmd,e->time, debug_tstring);
|
|
#endif
|
|
continue;
|
|
|
|
badline:
|
|
free(e->of.ct.minute);
|
|
free(e->of.ct.hour);
|
|
free(e->of.ct.daymon);
|
|
free(e->of.ct.month);
|
|
free(e->of.ct.dayweek);
|
|
free(e);
|
|
}
|
|
|
|
fclose(cf);
|
|
}
|
|
|
|
|
|
/* mail a user a message.
|
|
* The common mail messages are the output of commands. The
|
|
* processes which mail those messages are reaped like normal user
|
|
* processes. The mail messages sent here are exceptional.
|
|
* For example, the crontab command should notice invalid commands.
|
|
*/
|
|
void
|
|
mail(const char *usrname, const char *mmsg, const char *arg, int format)
|
|
{
|
|
FILE *mpipe;
|
|
char *temp, *i;
|
|
struct passwd *ruser_ids;
|
|
pid_t fork_val;
|
|
|
|
#ifdef TESTING
|
|
return;
|
|
#endif
|
|
fork_val = fork();
|
|
if (fork_val == -1) {
|
|
msg("failed to fork for mail message");
|
|
|
|
} else if (fork_val != 0) {
|
|
(void)alarm(2);
|
|
if (0 > waitpid(fork_val, (int *) NULL, 0))
|
|
running++;
|
|
(void)alarm(0);
|
|
|
|
} else {
|
|
if ((ruser_ids = getpwnam(usrname)) == 0) {
|
|
msg("failed to find username \"%s\"for mail message",
|
|
usrname);
|
|
exit(0);
|
|
}
|
|
if (setuid(ruser_ids->pw_uid) == -1)
|
|
exit(1);
|
|
if (cap_set_proc(ecap) == -1)
|
|
exit(1);
|
|
temp = xmalloc(strlen(MAIL)+strlen(usrname)+2);
|
|
(void)strcat(strcat(strcpy(temp,MAIL)," "),usrname);
|
|
mpipe = popen(temp,"w");
|
|
free(temp);
|
|
if (mpipe == NULL) {
|
|
msg("failed to create pipe for mail message");
|
|
} else {
|
|
/* separate msg from sendmail headers */
|
|
if (format == 3)
|
|
fprintf(mpipe, "To: %s\nSubject: cron output\n\n", usrname);
|
|
else
|
|
fprintf(mpipe, "To: %s\nSubject: cron error\n\n", usrname);
|
|
if (format == 1) {
|
|
i = strrchr(line,'\n');
|
|
if (i != NULL) *i = ' ';
|
|
fprintf(mpipe,
|
|
"Your crontab file has an error in the following entry:\n\t%s\n\t%s\nThis entry has been ignored.\n",
|
|
line,mmsg);
|
|
} else if (arg) {
|
|
fprintf(mpipe, "Cron: ");
|
|
fprintf(mpipe, mmsg, arg);
|
|
} else {
|
|
fprintf(mpipe, "Cron: %s\n",mmsg);
|
|
}
|
|
}
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
|
|
char *
|
|
next_field(int lower, int upper, const struct usr *u)
|
|
{
|
|
/* next_field returns a pointer to a string which holds
|
|
the next field of a line of a crontab file. If (numbers
|
|
in this field are out of range (lower..upper), or there
|
|
is a syntax error) then NULL is returned, and a mail message
|
|
is sent to the user telling him which line the error was in. */
|
|
|
|
char *s;
|
|
int num1, num2, start;
|
|
|
|
while ((line[cursor]==' ') || (line[cursor]=='\t')) cursor++;
|
|
start = cursor;
|
|
if (line[cursor] == '\0') {
|
|
mail(u->name,EOLN,NULL,1);
|
|
return(NULL);
|
|
}
|
|
if (line[cursor] == '*') {
|
|
cursor++;
|
|
if ((line[cursor]!=' ') && (line[cursor]!='\t')) {
|
|
mail(u->name,UNEXPECT,NULL,1);
|
|
return(NULL);
|
|
}
|
|
s = xmalloc(2);
|
|
strcpy(s,"*");
|
|
return(s);
|
|
}
|
|
for (;;) {
|
|
if (!isdigit(line[cursor])) {
|
|
mail(u->name,UNEXPECT,NULL,1);
|
|
return(NULL);
|
|
}
|
|
num1 = 0;
|
|
do {
|
|
num1 = num1*10 + (line[cursor]-'0');
|
|
} while (isdigit(line[++cursor]));
|
|
if ((num1<lower) || (num1>upper)) {
|
|
mail(u->name,OUTOFBOUND,NULL,1);
|
|
return(NULL);
|
|
}
|
|
if (line[cursor]=='-') {
|
|
if (!isdigit(line[++cursor])) {
|
|
mail(u->name,UNEXPECT,NULL,1);
|
|
return(NULL);
|
|
}
|
|
num2 = 0;
|
|
do {
|
|
num2 = num2*10 + (line[cursor]-'0');
|
|
} while (isdigit(line[++cursor]));
|
|
if ((num2<lower) || (num2>upper)) {
|
|
mail(u->name,OUTOFBOUND,NULL,1);
|
|
return(NULL);
|
|
}
|
|
}
|
|
if ((line[cursor]==' ') || (line[cursor]=='\t')) break;
|
|
if (line[cursor]=='\0') {
|
|
mail(u->name,EOLN,NULL,1);
|
|
return(NULL);
|
|
}
|
|
if (line[cursor++]!=',') {
|
|
mail(u->name,UNEXPECT,NULL,1);
|
|
return(NULL);
|
|
}
|
|
}
|
|
s = xmalloc(cursor-start+1);
|
|
strncpy(s,line+start,cursor-start);
|
|
s[cursor-start] = '\0';
|
|
return(s);
|
|
}
|
|
|
|
|
|
time_t
|
|
next_time(const struct event *e)
|
|
{
|
|
/* returns the integer time for the next occurance of event e.
|
|
the following fields have ranges as indicated:
|
|
PRGM | min hour day of month mon day of week
|
|
------|-------------------------------------------------------
|
|
cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday)
|
|
time | 0-59 0-23 1-31 0-11 0-6 (0=sunday)
|
|
NOTE: this routine is hard to understand. */
|
|
|
|
struct tm *tm, otm, *ntm;
|
|
int tm_mon, tm_mday, tm_wday, wday, m, min, h, hr, carry, day, days,
|
|
d1, day1, carry1, d2, day2, carry2, daysahead,
|
|
mon, yr, db, wd, today;
|
|
time_t t;
|
|
|
|
t = time(0);
|
|
again:
|
|
tm = localtime(&t);
|
|
otm = *tm;
|
|
|
|
tm_mon = next_ge(tm->tm_mon+1,e->of.ct.month) - 1; /* 0-11 */
|
|
tm_mday = next_ge(tm->tm_mday,e->of.ct.daymon); /* 1-31 */
|
|
tm_wday = next_ge(tm->tm_wday,e->of.ct.dayweek); /* 0-6 */
|
|
today = TRUE;
|
|
if ( (strcmp(e->of.ct.daymon,"*")==0 && tm->tm_wday!=tm_wday)
|
|
|| (strcmp(e->of.ct.dayweek,"*")==0 && tm->tm_mday!=tm_mday)
|
|
|| (tm->tm_mday!=tm_mday && tm->tm_wday!=tm_wday)
|
|
|| (tm->tm_mon!=tm_mon)) today = FALSE;
|
|
|
|
m = tm->tm_min+1;
|
|
if ((tm->tm_hour + 1) <= next_ge(tm->tm_hour%24, e->of.ct.hour)) {
|
|
m = 0;
|
|
}
|
|
min = next_ge(m%60,e->of.ct.minute);
|
|
carry = (min < m) ? 1:0;
|
|
h = tm->tm_hour+carry;
|
|
hr = next_ge(h%24,e->of.ct.hour);
|
|
carry = (hr < h) ? 1:0;
|
|
if ((!carry) && today) {
|
|
/* this event must occur today */
|
|
if (tm->tm_min>min)
|
|
t +=(time_t)(hr-tm->tm_hour-1)*CR_HOUR +
|
|
(time_t)(60-tm->tm_min+min)*CR_MINUTE;
|
|
else t += (time_t)(hr-tm->tm_hour)*CR_HOUR +
|
|
(time_t)(min-tm->tm_min)*CR_MINUTE;
|
|
t = t - (long)tm->tm_sec;
|
|
goto dstfix;
|
|
}
|
|
|
|
min = next_ge(0,e->of.ct.minute);
|
|
hr = next_ge(0,e->of.ct.hour);
|
|
|
|
/* calculate the date of the next occurance of this event,
|
|
which will be on a different day than the current day. */
|
|
|
|
/* check monthly day specification */
|
|
d1 = tm->tm_mday+1;
|
|
day1 = next_ge((d1-1)%days_in_mon(tm->tm_mon,tm->tm_year)+1,e->of.ct.daymon);
|
|
carry1 = (day1 < d1) ? 1:0;
|
|
|
|
/* check weekly day specification */
|
|
d2 = tm->tm_wday+1;
|
|
wday = next_ge(d2%7,e->of.ct.dayweek);
|
|
if (wday < d2) daysahead = 7 - d2 + wday;
|
|
else daysahead = wday - d2;
|
|
day2 = (d1+daysahead-1)%days_in_mon(tm->tm_mon,tm->tm_year)+1;
|
|
carry2 = (day2 < d1) ? 1:0;
|
|
|
|
/* based on their respective specifications,
|
|
day1, and day2 give the day of the month
|
|
for the next occurance of this event. */
|
|
|
|
if ((strcmp(e->of.ct.daymon,"*")==0) && (strcmp(e->of.ct.dayweek,"*")!=0)) {
|
|
day1 = day2;
|
|
carry1 = carry2;
|
|
}
|
|
if ((strcmp(e->of.ct.daymon,"*")!=0) && (strcmp(e->of.ct.dayweek,"*")==0)) {
|
|
day2 = day1;
|
|
carry2 = carry1;
|
|
}
|
|
|
|
yr = tm->tm_year;
|
|
if ((carry1 && carry2) || (tm->tm_mon != tm_mon)) {
|
|
/* event does not occur in this month */
|
|
m = tm->tm_mon+1;
|
|
mon = next_ge(m%12+1,e->of.ct.month)-1; /* 0..11 */
|
|
carry = (mon < m) ? 1:0;
|
|
yr += carry;
|
|
/* recompute day1 and day2 */
|
|
day1 = next_ge(1,e->of.ct.daymon);
|
|
db = days_btwn(tm->tm_mon,tm->tm_mday,tm->tm_year,mon,1,yr) + 1;
|
|
wd = (tm->tm_wday+db)%7;
|
|
/* wd is the day of the week of the first of month mon */
|
|
wday = next_ge(wd,e->of.ct.dayweek);
|
|
if (wday < wd) day2 = 1 + 7 - wd + wday;
|
|
else day2 = 1 + wday - wd;
|
|
if ((strcmp(e->of.ct.daymon,"*")!=0) && (strcmp(e->of.ct.dayweek,"*")==0))
|
|
day2 = day1;
|
|
if ((strcmp(e->of.ct.daymon,"*")==0) && (strcmp(e->of.ct.dayweek,"*")!=0))
|
|
day1 = day2;
|
|
day = (day1 < day2) ? day1:day2;
|
|
}
|
|
else { /* event occurs in this month */
|
|
mon = tm->tm_mon;
|
|
if (!carry1 && !carry2) day = (day1 < day2) ? day1 : day2;
|
|
else if (!carry1) day = day1;
|
|
else day = day2;
|
|
}
|
|
|
|
/* now that we have the min,hr,day,mon,yr of the next
|
|
event, figure out what time that turns out to be. */
|
|
|
|
days = days_btwn(tm->tm_mon,tm->tm_mday,tm->tm_year,mon,day,yr);
|
|
t += (time_t)(23-tm->tm_hour)*CR_HOUR + (time_t)(60-tm->tm_min)*CR_MINUTE
|
|
+ (time_t)hr*CR_HOUR + (time_t)min*CR_MINUTE + (time_t)days*CR_DAY;
|
|
t = t-(long)tm->tm_sec;
|
|
dstfix:
|
|
ntm = localtime(&t);
|
|
if (otm.tm_isdst == ntm->tm_isdst)
|
|
return(t);
|
|
else if (otm.tm_isdst && !ntm->tm_isdst) {
|
|
/* current time in dst, new is not */
|
|
return(t - altzone + timezone);
|
|
} else {
|
|
/* current time not in dst, new is */
|
|
t = t - timezone + altzone;
|
|
|
|
/* Now, if we time happened to be in the missing hour..
|
|
* we skip this time and try again
|
|
*/
|
|
ntm = localtime(&t);
|
|
if (ntm->tm_isdst == 0) {
|
|
/* in the witching hour */
|
|
t = t - altzone + timezone;
|
|
goto again;
|
|
}
|
|
return(t);
|
|
}
|
|
}
|
|
|
|
int
|
|
next_ge(int current, const char *list)
|
|
{
|
|
/* list is a character field as in a crontab file;
|
|
for example: "40,20,50-10"
|
|
next_ge returns the next number in the list that is
|
|
greater than or equal to current.
|
|
if no numbers of list are >= current, the smallest
|
|
element of list is returned.
|
|
NOTE: current must be in the appropriate range. */
|
|
|
|
const char *cursor = list;
|
|
char *ptr;
|
|
const int DUMMY = 100;
|
|
int n, n2, min = DUMMY, min_gt = DUMMY;
|
|
|
|
if (strcmp(list, "*") == 0)
|
|
return(current);
|
|
|
|
for (;;) {
|
|
errno = 0;
|
|
n = (int) strtol(cursor, &ptr, 10);
|
|
if (ptr == cursor || errno == ERANGE || n == current)
|
|
return(current);
|
|
cursor = ptr;
|
|
if (n < min)
|
|
min = n;
|
|
if (n > current && n < min_gt)
|
|
min_gt = n;
|
|
if (*cursor == '-') {
|
|
errno = 0;
|
|
n2 = (int) strtol(++cursor, &ptr, 10);
|
|
if (ptr == cursor || errno == ERANGE)
|
|
return(current);
|
|
cursor = ptr;
|
|
if (n2 > n) {
|
|
if (current > n && current <= n2)
|
|
return(current);
|
|
} else {
|
|
if (current > n || current <= n2)
|
|
return(current);
|
|
}
|
|
}
|
|
if (*cursor++ == '\0')
|
|
break;
|
|
}
|
|
return (min_gt != DUMMY ? min_gt : min);
|
|
}
|
|
|
|
void
|
|
del_atjob(const char *name, const char *usrname, mac_t label)
|
|
{
|
|
struct event *e, *eprev;
|
|
struct usr *u;
|
|
|
|
if((u = find_usr(usrname, label)) == NULL)
|
|
return;
|
|
e = u->atevents;
|
|
eprev = NULL;
|
|
while(e != NULL)
|
|
if(strcmp(name,e->cmd) == 0) {
|
|
if(next_event == e)
|
|
next_event = NULL;
|
|
if(eprev == NULL)
|
|
u->atevents = e->link;
|
|
else
|
|
eprev->link = e->link;
|
|
el_remove(e->of.at.eventid, 1);
|
|
free(e->cmd);
|
|
free(e);
|
|
break;
|
|
} else {
|
|
eprev = e;
|
|
e = e->link;
|
|
}
|
|
if(!u->ctexists && u->atevents == NULL) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"%s removed from usr list\n",usrname);
|
|
#endif
|
|
if(ulast == NULL)
|
|
uhead = u->nextusr;
|
|
else
|
|
ulast->nextusr = u->nextusr;
|
|
USR_RELE(u);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
del_ctab(const char *name, mac_t label)
|
|
{
|
|
|
|
struct usr *u;
|
|
|
|
if((u = find_usr(name, label)) == NULL)
|
|
return;
|
|
rm_ctevents(u);
|
|
el_remove(u->ctid, 0);
|
|
u->ctid = 0;
|
|
u->ctexists = 0;
|
|
if(u->atevents == NULL) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr,"%s removed from usr list\n",name);
|
|
#endif
|
|
if(ulast == NULL)
|
|
uhead = u->nextusr;
|
|
else
|
|
ulast->nextusr = u->nextusr;
|
|
USR_RELE(u);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
rm_ctevents(struct usr *u)
|
|
{
|
|
struct event *e2, *e3;
|
|
|
|
/* see if the next event (to be run by cron)
|
|
is a cronevent owned by this user. */
|
|
if ( (next_event!=NULL) &&
|
|
(next_event->etype==CRONEVENT) &&
|
|
(next_event->u==u) )
|
|
next_event = NULL;
|
|
e2 = u->ctevents;
|
|
while (e2 != NULL) {
|
|
free(e2->cmd);
|
|
free(e2->of.ct.minute);
|
|
free(e2->of.ct.hour);
|
|
free(e2->of.ct.daymon);
|
|
free(e2->of.ct.month);
|
|
free(e2->of.ct.dayweek);
|
|
free(e2->of.ct.input);
|
|
e3 = e2->link;
|
|
free(e2);
|
|
e2 = e3;
|
|
}
|
|
u->ctevents = NULL;
|
|
}
|
|
|
|
|
|
struct usr *
|
|
find_usr(const char *uname, mac_t label)
|
|
{
|
|
struct usr *u;
|
|
|
|
u = uhead;
|
|
ulast = NULL;
|
|
while (u != NULL) {
|
|
if (strcmp(u->name,uname) == 0)
|
|
if (!mac_enabled || mac_equal(label, u->mac) > 0)
|
|
return(u);
|
|
ulast = u;
|
|
u = u->nextusr;
|
|
}
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
void
|
|
ex(const struct event *e)
|
|
{
|
|
int i, fd, sp_flag;
|
|
pid_t rfork;
|
|
char *at_cmdfile, *cron_infile;
|
|
struct stat buf;
|
|
struct queue *qp;
|
|
struct runinfo *rp;
|
|
uid_t satid = e->u->uid;
|
|
sigset_t set, oset;
|
|
|
|
qp = &qt[e->etype]; /* set pointer to queue defs */
|
|
|
|
if(qp->nrun >= qp->njob) {
|
|
if (e->etype != CRONEVENT)
|
|
msg("%c queue max run limit reached", e->etype + 'a');
|
|
resched(qp->nwait);
|
|
return;
|
|
}
|
|
for(rp=rt; rp < rt+maxrun; rp++) {
|
|
if(rp->pid == 0)
|
|
break;
|
|
}
|
|
if(rp >= rt+maxrun) {
|
|
msg("maxrun (%d) procs reached", maxrun);
|
|
resched(qp->nwait);
|
|
return;
|
|
}
|
|
#ifdef ATLIMIT
|
|
if((e->u)->uid != 0 && (e->u)->aruncnt >= ATLIMIT) {
|
|
msg("ATLIMIT (%d) reached for uid %d", ATLIMIT,
|
|
(int) (e->u)->uid);
|
|
resched(qp->nwait);
|
|
return;
|
|
}
|
|
#endif
|
|
#ifdef CRONLIMIT
|
|
if((e->u)->uid != 0 && (e->u)->cruncnt >= CRONLIMIT) {
|
|
msg("CRONLIMIT (%d) reached for uid %d", CRONLIMIT,
|
|
(int) (e->u)->uid);
|
|
resched(qp->nwait);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if ((cron_infile = tempnam(TMPDIR, PFX)) == NULL) {
|
|
resched(wait_time);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Don't allow signals between creating the cron tmpfile
|
|
* and deleting it. Otherwise a spurious signal could
|
|
* occur after the file is created and before it is deleted,
|
|
* which would cause cron to not properly clean up after itself.
|
|
*/
|
|
(void) sigfillset(&set);
|
|
(void) sigprocmask(SIG_BLOCK, &set, &oset);
|
|
rp->outfd = open(cron_infile, O_CREAT|O_TRUNC|O_RDWR|O_APPEND, 0);
|
|
if (rp->outfd != -1) {
|
|
(void) unlink(cron_infile);
|
|
} else {
|
|
rp->outfd = open("/dev/null", O_RDWR, (mode_t) 0);
|
|
}
|
|
(void) sigprocmask(SIG_SETMASK, &oset, (sigset_t *) 0);
|
|
free(cron_infile);
|
|
if (rp->outfd == -1 || set_cloexec(rp->outfd) == -1) {
|
|
if (rp->outfd != -1)
|
|
close(rp->outfd);
|
|
resched(wait_time);
|
|
return;
|
|
}
|
|
|
|
if((rfork = fork()) == -1) {
|
|
close(rp->outfd);
|
|
msg("cannot fork");
|
|
resched(wait_time);
|
|
(void)sleep(30);
|
|
return;
|
|
}
|
|
if(rfork) { /* parent process */
|
|
++qp->nrun;
|
|
++running;
|
|
rp->pid = rfork;
|
|
rp->que = e->etype;
|
|
rp->sm = e->sm;
|
|
#ifdef ATLIMIT
|
|
if(e->etype != CRONEVENT)
|
|
(e->u)->aruncnt++;
|
|
#endif
|
|
#if ATLIMIT && CRONLIMIT
|
|
else
|
|
(e->u)->cruncnt++;
|
|
#else
|
|
#ifdef CRONLIMIT
|
|
if(e->etype == CRONEVENT)
|
|
(e->u)->cruncnt++;
|
|
#endif
|
|
#endif
|
|
rp->rusr = (e->u);
|
|
|
|
/*
|
|
* Place a hold on the user structure. This prevents
|
|
* it from being deleted out from under us while we
|
|
* wait for completion. The reference will be freed
|
|
* in idle() when we return from wait().
|
|
*/
|
|
USR_HOLD(rp->rusr);
|
|
|
|
logit((char)BCHAR,rp,0);
|
|
return;
|
|
}
|
|
|
|
(void)close(0);
|
|
|
|
/* restore rlimit signals */
|
|
signal(SIGXCPU, SIG_DFL);
|
|
signal(SIGXFSZ, SIG_DFL);
|
|
|
|
/*
|
|
* Set the MAC label, should that be appropriate.
|
|
* Do it prior to reading an at job so that the moldy
|
|
* subdirectory is handled properly.
|
|
*/
|
|
mac_free(mac_swap(e->u->mac));
|
|
|
|
if (e->etype != CRONEVENT ) {
|
|
char *cp, *ptr;
|
|
/* open jobfile as stdin to shell */
|
|
at_cmdfile = at_cmd_create(e);
|
|
if (stat(at_cmdfile,&buf)) exit(1);
|
|
if (cp = strchr(at_cmdfile, '+')) {
|
|
errno = 0;
|
|
satid = (uid_t) strtol(++cp, &ptr, 10);
|
|
if (ptr == cp || errno == ERANGE || *ptr != '\0') {
|
|
unlink(at_cmdfile);
|
|
exit(1);
|
|
}
|
|
}
|
|
if ((buf.st_mode & ISUID) != ISUID) {
|
|
/* if setuid bit off, original owner has
|
|
given this file to someone else */
|
|
unlink(at_cmdfile);
|
|
exit(1);
|
|
}
|
|
if (open(at_cmdfile, O_RDONLY) != 0) {
|
|
mail((e->u)->name,BADJOBOPEN,NULL,2);
|
|
unlink(at_cmdfile);
|
|
exit(1);
|
|
}
|
|
unlink(at_cmdfile);
|
|
free(at_cmdfile);
|
|
}
|
|
|
|
/*
|
|
* Audit what happens henceforth on the user's behalf.
|
|
*/
|
|
if (audit_enabled) {
|
|
/* Audit initiation of the job */
|
|
if (satsetid(satid) == 0) {
|
|
(void) ia_audit("CRON", e->u->name, 1, "New Session");
|
|
} else {
|
|
(void) ia_audit("CRON", e->u->name, 0, "No Session");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
#ifdef CRONPROJ
|
|
/* Fire up a new array session */
|
|
if (newarraysess() == -1) {
|
|
exit(1);
|
|
}
|
|
{
|
|
/* Set up the correct project ID */
|
|
prid_t prid = getdfltprojuser(e->u->name);
|
|
if (prid != (prid_t) -1 && setprid(prid) == -1)
|
|
exit(1);
|
|
}
|
|
#endif /* CRONPROJ */
|
|
/*
|
|
* set correct user and group identification and initialize
|
|
* the supplementary group access list
|
|
*/
|
|
(void) initgroups(e->u->name, e->u->gid); /* ok if it fails */
|
|
if (setgid(e->u->gid) == -1 || setuid(e->u->uid) == -1)
|
|
exit(1);
|
|
|
|
/* At this point relinquish all capability */
|
|
if (cap_set_proc(ecap) == -1)
|
|
exit(1);
|
|
|
|
sp_flag = FALSE;
|
|
if (e->etype == CRONEVENT) {
|
|
/* check for standard input to command */
|
|
if (e->of.ct.input != NULL) {
|
|
char infile[sizeof(TMPINFILE)];
|
|
|
|
strcpy(infile, TMPINFILE);
|
|
cron_infile = mktemp(infile);
|
|
(void) sigprocmask(SIG_BLOCK, &set, &oset);
|
|
fd = open(cron_infile, O_CREAT|O_TRUNC|O_RDWR, 0);
|
|
if (fd != 0) {
|
|
if (fd != -1)
|
|
(void) unlink(cron_infile);
|
|
(void) sigprocmask(SIG_SETMASK, &oset,
|
|
(sigset_t *) 0);
|
|
mail((e->u)->name, NOSTDIN, NULL, 2);
|
|
exit(1);
|
|
}
|
|
(void) unlink(cron_infile);
|
|
(void) sigprocmask(SIG_SETMASK, &oset, (sigset_t *) 0);
|
|
if (write(fd, e->of.ct.input, strlen(e->of.ct.input))
|
|
!= strlen(e->of.ct.input)) {
|
|
mail((e->u)->name, NOSTDIN, NULL, 2);
|
|
exit(1);
|
|
}
|
|
(void) lseek(fd, (off_t) 0, SEEK_SET);
|
|
} else if (open("/dev/null",O_RDONLY)==-1) {
|
|
open("/",O_RDONLY);
|
|
sp_flag = TRUE;
|
|
}
|
|
}
|
|
|
|
/* redirect stdout and stderr for the shell */
|
|
fflush(stderr);
|
|
fflush(stdout);
|
|
close(1); /* redirect stdout */
|
|
dup(rp->outfd);
|
|
close(2); /* redirect stderr */
|
|
dup(rp->outfd);
|
|
|
|
/* separate msg from sendmail headers */
|
|
fprintf(stdout, MAIL_HDR, e->u->name,
|
|
e->u->name, hostname, e->cmd);
|
|
fflush(stdout);
|
|
if (sp_flag) close(0);
|
|
|
|
strcat(homedir,(e->u)->home);
|
|
strcat(logname,(e->u)->name);
|
|
strcat(env_user,(e->u)->name);
|
|
environ = envinit;
|
|
if (chdir((e->u)->home) == -1) {
|
|
mail((e->u)->name,CANTCDHOME,(e->u)->home,2);
|
|
exit(1);
|
|
}
|
|
#ifdef TESTING
|
|
exit(1);
|
|
#endif
|
|
if((e->u)->uid != 0)
|
|
nice(qp->nice);
|
|
if (e->etype == CRONEVENT)
|
|
execl(SHELL,"sh","-c",e->cmd,0);
|
|
else /* type == ATEVENT */
|
|
execl(SHELL,"sh",0);
|
|
mail((e->u)->name,CANTEXECSH,strerror(errno),2);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
void
|
|
idle(time_t t)
|
|
{
|
|
time_t now, t_old;
|
|
pid_t pid;
|
|
int prc;
|
|
struct runinfo *rp;
|
|
|
|
/*
|
|
* Guard against time being set backwards. If this occurs,
|
|
* no events will fire until that difference has been made up.
|
|
*/
|
|
now = time(0);
|
|
t_old = now;
|
|
while(t > 0L) {
|
|
if (reinit)
|
|
return;
|
|
if (running) {
|
|
if(t > wait_time)
|
|
t = wait_time;
|
|
else
|
|
t += secfudge;
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "%s: alarm(%ld:%02ld) in idle;",
|
|
DEBUG_TIME(), t/60, t%60);
|
|
#endif
|
|
fflush(stderr); /* get normal logging out */
|
|
(void)alarm(t);
|
|
pid = wait(&prc);
|
|
t = (time_t) alarm(0);
|
|
#ifdef DEBUG
|
|
fprintf(stderr,
|
|
"\t%s: wait(&%x)=%d with %ld:%02ld left\n",
|
|
DEBUG_TIME(), prc, pid, t/60,t%60);
|
|
#endif
|
|
now = time(0);
|
|
if (now < t_old) {
|
|
msg("Time went backwards");
|
|
reinit = 1;
|
|
if (pid == -1)
|
|
return;
|
|
}
|
|
t_old = now;
|
|
if (next_event)
|
|
t = next_event->time - now;
|
|
if (pid == -1) {
|
|
msg_wait(t, 0);
|
|
} else {
|
|
for(rp=rt;rp < rt+maxrun; rp++)
|
|
if(rp->pid == pid)
|
|
break;
|
|
if(rp >= rt+maxrun) {
|
|
msg("unexpected pid returned %d (ignored)", (int) pid);
|
|
/* incremented in mail() */
|
|
running--;
|
|
} else {
|
|
struct usr *u = rp->rusr;
|
|
if(rp->que == ZOMB) {
|
|
running--;
|
|
rp->pid = 0;
|
|
close(rp->outfd);
|
|
USR_RELE(u);
|
|
} else {
|
|
cleanup(rp,prc);
|
|
if (rp->pid == 0)
|
|
USR_RELE(u);
|
|
msg_wait(0, 1);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
msg_wait(t, 0);
|
|
}
|
|
now = time(0);
|
|
if (now < t_old) {
|
|
msg("Time went backwards");
|
|
reinit = 1;
|
|
}
|
|
if (!next_event)
|
|
return;
|
|
t_old = now;
|
|
t = next_event->time - now;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* print_summary
|
|
* print a summary of the command that was run to ease debugging
|
|
*/
|
|
|
|
void
|
|
print_summary(FILE * fp, struct runinfo *rip, int rc)
|
|
{
|
|
char start_time[30];
|
|
char end_time[30];
|
|
char * cron_or_at;
|
|
|
|
cron_or_at = (rip->etype == CRONEVENT) ? "cron" : "at";
|
|
|
|
fprintf(fp,
|
|
"\n\n*************************** %s ****************************\n"
|
|
"The above message is the standard output and standard error\n"
|
|
"of the following %s job:\n\n"
|
|
"command: %s%s\n"
|
|
"user@host: %s@%s\n",
|
|
cron_or_at, cron_or_at,
|
|
rip->cmd,
|
|
((rip->etype == CRONEVENT) ?
|
|
"" :
|
|
" (at temporary file)"),
|
|
rip->rusr->name, hostname);
|
|
|
|
if (rip->etype == CRONEVENT) {
|
|
fprintf(fp,
|
|
"crontab: %s/%s\n"
|
|
"homedir: %s\n",
|
|
CRONDIR, rip->rusr->name,
|
|
rip->rusr->home);
|
|
}
|
|
fprintf(fp,
|
|
"started: %s"
|
|
"ended: %s",
|
|
ctime_r((const time_t *)&rip->start_time, start_time),
|
|
ctime_r((const time_t *)&rip->end_time, end_time));
|
|
|
|
if (WIFEXITED(rc) && WEXITSTATUS(rc) != 0)
|
|
fprintf(fp, "exit-code: %d\n", WEXITSTATUS(rc));
|
|
else if (WIFSIGNALED(rc))
|
|
fprintf(fp, "signaled: %d\n", WTERMSIG(rc));
|
|
else if (WIFSTOPPED(rc))
|
|
fprintf(fp, "stopped: %d\n", WSTOPSIG(rc));
|
|
fflush(fp);
|
|
}
|
|
|
|
void
|
|
cleanup(struct runinfo *pr, int rc)
|
|
{
|
|
FILE *fp;
|
|
struct usr *p;
|
|
struct stat buf;
|
|
mac_t omac;
|
|
|
|
logit((char)ECHAR,pr,rc);
|
|
--qt[pr->que].nrun;
|
|
pr->pid = 0;
|
|
--running;
|
|
p = pr->rusr;
|
|
#ifdef ATLIMIT
|
|
if(pr->que != CRONEVENT)
|
|
--p->aruncnt;
|
|
#endif
|
|
#if ATLIMIT && CRONLIMIT
|
|
else
|
|
--p->cruncnt;
|
|
#else
|
|
#ifdef CRONLIMIT
|
|
if(pr->que == CRONEVENT)
|
|
--p->cruncnt;
|
|
#endif
|
|
#endif
|
|
if (fstat(pr->outfd, &buf) == -1) {
|
|
close(pr->outfd);
|
|
return;
|
|
}
|
|
|
|
omac = mac_swap(p->mac);
|
|
/*
|
|
* Did this file get any output from cmd?
|
|
* each %s is 2 chars (2*4=8) terminating \0 is 1
|
|
*/
|
|
if (buf.st_size > sizeof(MAIL_HDR) - 9 + 2 * strlen(p->name) +
|
|
strlen(hostname) + strlen(pr->cmd)) {
|
|
|
|
/* mail user stdout and stderr */
|
|
pr->que = ZOMB;
|
|
pr->pid = fork();
|
|
|
|
/* child: mail job output to user as that user */
|
|
if (pr->pid == 0) {
|
|
/* set uid to user's uid */
|
|
if (setuid(p->uid) == -1)
|
|
exit(127);
|
|
|
|
/* relinquish all capability */
|
|
if (cap_set_proc(ecap) == -1)
|
|
exit(127);
|
|
|
|
/* open stream on job output file */
|
|
if ((fp = fdopen(pr->outfd, "r+")) == NULL) {
|
|
mail(p->name, STDOUTERR, NULL, 2);
|
|
exit(127);
|
|
}
|
|
|
|
/* append job summary to user output */
|
|
print_summary(fp, pr, rc);
|
|
|
|
/* move file offset to beginning */
|
|
rewind(fp);
|
|
|
|
/* dup output file onto stdin */
|
|
close(0);
|
|
if (dup(pr->outfd) != 0)
|
|
exit(127);
|
|
fclose(fp);
|
|
|
|
/* send mail */
|
|
execl(MAIL, "mail", p->name, (char *) 0);
|
|
exit(127);
|
|
}
|
|
|
|
if (pr->pid != -1) {
|
|
/* parent: indicate running job */
|
|
running++;
|
|
} else {
|
|
/* fork failed: clean up job slot */
|
|
pr->pid = 0;
|
|
close(pr->outfd);
|
|
}
|
|
} else {
|
|
/* No output from command */
|
|
if (pr->sm) /* user asked to send a mail */
|
|
mail(p->name, STDATMSG, NULL, 3);
|
|
|
|
close(pr->outfd);
|
|
}
|
|
mac_restore(omac);
|
|
}
|
|
|
|
|
|
void
|
|
msg_wait(time_t t, int multi)
|
|
{
|
|
struct message msgbuf;
|
|
struct stat msgstat;
|
|
int cnt, msgs, i;
|
|
mac_t lbl;
|
|
|
|
if(fstat(msgfd,&msgstat) != 0)
|
|
crabort("cannot stat fifo queue",1);
|
|
if(msgstat.st_size == 0 && running)
|
|
return;
|
|
|
|
if (t > 1) t += secfudge;
|
|
else t = 1;
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "%s: alarm(%ld:%02ld) in msg_wait;",
|
|
DEBUG_TIME(), t/60, t%60);
|
|
#endif
|
|
fflush(stderr); /* get normal logging out */
|
|
|
|
/* read in each entry */
|
|
if (!(msgs = msgstat.st_size / sizeof(msgbuf)) && !multi)
|
|
msgs = 1;
|
|
for (i = 0; i < msgs; i++) {
|
|
msgbuf.etype = 0;
|
|
(void)alarm(t);
|
|
cnt = read(msgfd, &msgbuf, sizeof(msgbuf));
|
|
#ifdef DEBUG
|
|
t = (time_t) alarm(0);
|
|
fprintf(stderr, "\t%s: read()=%ld with %ld:%02ld left\n",
|
|
DEBUG_TIME(), (unsigned long) cnt, t/60, t%60);
|
|
#else
|
|
(void)alarm(0);
|
|
#endif
|
|
if (cnt != sizeof(msgbuf))
|
|
return;
|
|
|
|
lbl = NULL;
|
|
switch(msgbuf.etype) {
|
|
case TRIX_AT:
|
|
lbl = mac_from_text(msgbuf.label);
|
|
if (lbl == NULL)
|
|
break;
|
|
case AT:
|
|
switch(msgbuf.action)
|
|
{
|
|
case ADD:
|
|
mod_atjob(msgbuf.fname, lbl);
|
|
break;
|
|
case DELETE:
|
|
del_atjob(msgbuf.fname,
|
|
msgbuf.logname, lbl);
|
|
break;
|
|
default:
|
|
msg("message received - bad action");
|
|
break;
|
|
}
|
|
mac_free(lbl);
|
|
break;
|
|
case TRIX_CRON:
|
|
lbl = mac_from_text(msgbuf.label);
|
|
if (lbl == NULL)
|
|
break;
|
|
case CRON:
|
|
switch(msgbuf.action)
|
|
{
|
|
case ADD:
|
|
mod_ctab(msgbuf.fname, lbl);
|
|
break;
|
|
case DELETE:
|
|
del_ctab(msgbuf.fname, lbl);
|
|
break;
|
|
default:
|
|
msg("message received - bad action");
|
|
break;
|
|
}
|
|
mac_free(lbl);
|
|
break;
|
|
default:
|
|
msg("message received - bad event");
|
|
break;
|
|
}
|
|
if (next_event != NULL) {
|
|
if (next_event->etype == CRONEVENT)
|
|
el_add(next_event,next_event->time,
|
|
(next_event->u)->ctid);
|
|
else /* etype == ATEVENT */
|
|
el_add(next_event,next_event->time,
|
|
next_event->of.at.eventid);
|
|
next_event = NULL;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
timeout(int sig)
|
|
{
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
cronend(int sig)
|
|
{
|
|
msg("SIGTERM");
|
|
if(unlink(FIFO) < 0) /* FIFO vanishes when cron finishes */
|
|
perror("cron could not unlink FIFO");
|
|
msg("******* CRON ABORTED ********");
|
|
exit(1);
|
|
}
|
|
|
|
void
|
|
crabort(const char *mssg, int clean)
|
|
{
|
|
/* crabort handles exits out of cron */
|
|
int c;
|
|
|
|
if(clean) {
|
|
if(unlink(FIFO) < 0) /* FIFO vanishes when cron finishes */
|
|
perror("cron could not unlink FIFO");
|
|
}
|
|
/* write error msg to console */
|
|
if ((c=open(_PATH_CONSOLE,O_WRONLY))>=0) {
|
|
write(c,"cron aborted: ",14);
|
|
write(c,mssg,strlen(mssg));
|
|
write(c,"\n",1);
|
|
close(c);
|
|
}
|
|
msg(mssg);
|
|
msg("******* CRON ABORTED ********");
|
|
exit(1);
|
|
}
|
|
|
|
void
|
|
msg(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
fprintf(stderr,"! ");
|
|
va_start(ap, fmt);
|
|
fprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
fprintf(stderr," %s\n", DEBUG_TIME());
|
|
fflush(stdout);
|
|
}
|
|
|
|
|
|
void
|
|
logit(char cc, struct runinfo *rp, int rc)
|
|
{
|
|
time_t t;
|
|
|
|
t = time(0);
|
|
if(cc == BCHAR) {
|
|
fprintf(stderr,"%c CMD: %s\n",cc, next_event->cmd);
|
|
free(rp->cmd);
|
|
rp->cmd = xmalloc(strlen(next_event->cmd) + 1);
|
|
strcpy(rp->cmd, next_event->cmd);
|
|
rp->etype = next_event->etype;
|
|
rp->start_time = t;
|
|
} else if (cc == ECHAR) {
|
|
rp->end_time = t;
|
|
}
|
|
fprintf(stderr,"%c %.8s %d %c %.24s",
|
|
cc,(rp->rusr)->name, rp->pid, QUE(rp->que),ctime(&t));
|
|
if (WIFSIGNALED(rc))
|
|
fprintf(stderr, " ts=%d", WTERMSIG(rc));
|
|
if (WIFSTOPPED(rc))
|
|
fprintf(stderr, " ss=%d", WSTOPSIG(rc));
|
|
if (WIFEXITED(rc) && WEXITSTATUS(rc) != 0)
|
|
fprintf(stderr, " rc=%d", WEXITSTATUS(rc));
|
|
putchar('\n');
|
|
fflush(stdout);
|
|
}
|
|
|
|
|
|
void
|
|
resched(int delay)
|
|
{
|
|
time_t nt;
|
|
|
|
/* run job at a later time */
|
|
nt = next_event->time + delay;
|
|
if(next_event->etype == CRONEVENT) {
|
|
next_event->time = next_time(next_event);
|
|
if(nt < next_event->time)
|
|
next_event->time = nt;
|
|
el_add(next_event,next_event->time,(next_event->u)->ctid);
|
|
delayed = 1;
|
|
msg("rescheduling a cron job");
|
|
return;
|
|
}
|
|
add_atevent(next_event->u, next_event->cmd, nt, next_event->etype,
|
|
next_event->sm);
|
|
msg("rescheduling at job");
|
|
}
|
|
|
|
#define QBUFSIZ 80
|
|
|
|
void
|
|
quedefs(int action)
|
|
{
|
|
int i, j;
|
|
char name[MAXNAMLEN+1];
|
|
char qbuf[QBUFSIZ];
|
|
FILE *fp;
|
|
|
|
/* set up default queue definitions */
|
|
for(i=0;i<NQUEUE;i++) {
|
|
qt[i].njob = qd.njob;
|
|
qt[i].nice = qd.nice;
|
|
qt[i].nwait = qd.nwait;
|
|
}
|
|
if(action == DEFAULT)
|
|
return;
|
|
if((fp = fopen(QUEDEFS,"r")) == NULL) {
|
|
msg("cannot open %s; using default queue definitions",
|
|
QUEDEFS);
|
|
return;
|
|
}
|
|
while(fgets(qbuf, QBUFSIZ, fp) != NULL) {
|
|
if((j=qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.')
|
|
continue;
|
|
i = 0;
|
|
while(qbuf[i] != NULL) {
|
|
name[i] = qbuf[i];
|
|
i++;
|
|
}
|
|
/* Append a NULL at the end of name as otherwise DEFAULT
|
|
values aren't getting picked up. It uses the old values
|
|
in the buffer which may be the previous QUEUE's info. or
|
|
possibly garbage.
|
|
*/
|
|
name[i] = NULL;
|
|
|
|
parsqdef(&name[2]);
|
|
qt[j].njob = qq.njob;
|
|
qt[j].nice = qq.nice;
|
|
qt[j].nwait = qq.nwait;
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
|
|
void
|
|
parsqdef(const char *name)
|
|
{
|
|
int i;
|
|
|
|
qq = qd;
|
|
while(*name) {
|
|
i = 0;
|
|
while(isdigit(*name)) {
|
|
i *= 10;
|
|
i += *name++ - '0';
|
|
}
|
|
switch(*name++) {
|
|
case JOBF:
|
|
qq.njob = i;
|
|
break;
|
|
case NICEF:
|
|
qq.nice = i;
|
|
break;
|
|
case WAITF:
|
|
qq.nwait = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
char *
|
|
at_cmd_create(const struct event *e)
|
|
{
|
|
char *cp = xmalloc(strlen(atdir) + strlen(e->cmd) + 2);
|
|
|
|
sprintf(cp, "%s/%s", atdir, e->cmd);
|
|
return (cp);
|
|
}
|
|
|
|
mac_t
|
|
mac_swap(mac_t mac)
|
|
{
|
|
mac_t omac = NULL;
|
|
|
|
if (mac_enabled && mac != NULL) {
|
|
if ((omac = mac_get_proc()) != NULL) {
|
|
if (mac_set_proc(mac) == -1) {
|
|
mac_free(omac);
|
|
omac = NULL;
|
|
}
|
|
}
|
|
}
|
|
return(omac);
|
|
}
|
|
|
|
void
|
|
mac_restore(mac_t mac)
|
|
{
|
|
if (mac_enabled && mac != NULL) {
|
|
(void) mac_set_proc(mac);
|
|
mac_free(mac);
|
|
}
|
|
}
|
|
|
|
mac_t
|
|
getflabel(const char *file)
|
|
{
|
|
return(mac_enabled ? mac_get_file(file) : NULL);
|
|
}
|
|
|
|
int
|
|
setflabel(const char *file, const char *lbl_name)
|
|
{
|
|
mac_t label;
|
|
int r = 0;
|
|
|
|
if (mac_enabled) {
|
|
if ((label = mac_from_text(lbl_name)) != NULL) {
|
|
r = mac_set_file(file, label);
|
|
mac_free(label);
|
|
} else {
|
|
r = -1;
|
|
}
|
|
}
|
|
return(r);
|
|
}
|
|
|
|
int
|
|
maxfds(void)
|
|
{
|
|
struct rlimit lim;
|
|
|
|
if (getrlimit(RLIMIT_NOFILE, &lim) == -1)
|
|
return(-1);
|
|
if (lim.rlim_cur == lim.rlim_max)
|
|
return(0);
|
|
lim.rlim_cur = lim.rlim_max;
|
|
return(setrlimit(RLIMIT_NOFILE, &lim));
|
|
}
|