summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cron/cron.c
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/cron/cron.c
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/cron/cron.c')
-rw-r--r--usr/src/cmd/cron/cron.c3312
1 files changed, 3312 insertions, 0 deletions
diff --git a/usr/src/cmd/cron/cron.c b/usr/src/cmd/cron/cron.c
new file mode 100644
index 0000000000..f29c1917ef
--- /dev/null
+++ b/usr/src/cmd/cron/cron.c
@@ -0,0 +1,3312 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+
+/* Copyright (c) 1987, 1988 Microsoft Corporation */
+/* All Rights Reserved */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef lint
+/* make lint happy */
+#define __EXTENSIONS__
+#endif
+
+#include <sys/contract/process.h>
+#include <sys/ctfs.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/task.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+
+#include <security/pam_appl.h>
+
+#include <alloca.h>
+#include <ctype.h>
+#include <deflt.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <libcontract.h>
+#include <libcontract_priv.h>
+#include <limits.h>
+#include <locale.h>
+#include <poll.h>
+#include <project.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stropts.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "cron.h"
+
+/*
+ * #define DEBUG
+ */
+
+#define MAIL "/usr/bin/mail" /* mail program to use */
+#define CONSOLE "/dev/console" /* where messages go when cron dies */
+
+#define TMPINFILE "/tmp/crinXXXXXX" /* file to put stdin in for cmd */
+#define TMPDIR "/tmp"
+#define PFX "crout"
+#define TMPOUTFILE "/tmp/croutXXXXXX" /* file to place stdout, stderr */
+
+#define INMODE 00400 /* mode for stdin file */
+#define OUTMODE 00600 /* mode for stdout file */
+#define ISUID S_ISUID /* mode for verifing at jobs */
+
+#define INFINITY 2147483647L /* upper bound on time */
+#define CUSHION 180L
+#define MAXRUN 100 /* max total jobs allowed in system */
+#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 QBUFSIZ 80
+
+/* Defined actions for crabort() routine */
+#define NO_ACTION 000
+#define REMOVE_FIFO 001
+#define CONSOLE_MSG 002
+
+#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 BADSHELL "because your login shell \
+isn't /usr/bin/sh, you can't use cron."
+
+#define BADSTAT "can't access your crontab file. Resubmit it."
+#define BADPROJID "can't set project id for your job."
+#define CANTCDHOME "can't change directory to your home directory.\
+\nYour commands will not be executed."
+#define CANTEXECSH "unable to exec the shell for one of your commands."
+#define NOREAD "can't read your crontab file. Resubmit it."
+#define BADTYPE "crontab is not a regular file.\n"
+#define NOSTDIN "unable to create a standard input file for \
+one of your crontab commands. \
+\nThat command was not executed."
+
+#define NOTALLOWED "you are not authorized to use cron. Sorry."
+#define STDERRMSG "\n\n********************************************\
+*****\nCron: The previous message is the \
+standard output and standard error \
+\nof one of your cron commands.\n"
+
+#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 CLOCK_DRIFT "clock time drifted backwards after event!\n"
+#define PIDERR "unexpected pid returned %d (ignored)"
+#define CRONTABERR "Subject: Your crontab file has an error in it\n\n"
+#define CRONOUT "Subject: Output from \"cron\" command\n\n"
+#define MALLOCERR "out of space, cannot create new string\n"
+
+#define DIDFORK didfork
+#define NOFORK !didfork
+
+#define MAILBUFLEN (8*1024)
+#define LINELIMIT 80
+#define MAILBINITFREE (MAILBUFLEN - (sizeof (cte_intro) - 1) \
+ - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1)
+
+#define ERR_CRONTABENT 0 /* error in crontab file entry */
+#define ERR_UNIXERR 1 /* error in some system call */
+#define ERR_CANTEXECCRON 2 /* error setting up "cron" job environment */
+#define ERR_CANTEXECAT 3 /* error setting up "at" job environment */
+
+#define PROJECT "project="
+
+#define MAX_LOST_CONTRACTS 2048 /* reset if this many failed abandons */
+
+#define FORMAT "%a %b %e %H:%M:%S %Y"
+static char timebuf[80];
+
+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 */
+ 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 */
+ int aruncnt; /* counter for running jobs per uid */
+ int cruncnt; /* counter for running cron jobs per uid */
+ 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 */
+
+static 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];
+static struct queue qq;
+
+struct runinfo
+{
+ pid_t pid;
+ short que;
+ struct usr *rusr; /* pointer to usr struct */
+ char *outfile; /* file where stdout & stderr are trapped */
+ short jobtype; /* what type of event: 0=cron, 1=at */
+ char *jobname; /* command for "cron", jobname for "at" */
+ int mailwhendone; /* 1 = send mail even if no ouptut */
+} rt[MAXRUN];
+
+static struct miscpid {
+ pid_t pid;
+ struct miscpid *next;
+} *miscpid_head;
+
+static pid_t cron_pid; /* own pid */
+static char didfork = 0; /* flag to see if I'm process group leader */
+static int msgfd; /* file descriptor for fifo queue */
+static int ecid = 1; /* event class id for el_remove(); MUST be set to 1 */
+static int delayed; /* is job being rescheduled or did it run first time */
+static int cwd; /* current working directory */
+static struct event *next_event; /* the next event to execute */
+static struct usr *uhead; /* ptr to the list of users */
+
+/* Variables for error handling at reading crontabs. */
+static char cte_intro[] = "Line(s) with errors:\n\n";
+static char cte_trail1[] = "\nMax number of errors encountered.";
+static char cte_trail2[] = " Evaluation of crontab aborted.\n";
+static int cte_free = MAILBINITFREE; /* Free buffer space */
+static char *cte_text = NULL; /* Text buffer pointer */
+static char *cte_lp; /* Next free line in cte_text */
+static int cte_nvalid; /* Valid lines found */
+
+/* user's default environment for the shell */
+#define ROOTPATH "PATH=/usr/sbin:/usr/bin"
+#define NONROOTPATH "PATH=/usr/bin:"
+
+static char *Def_supath = NULL;
+static char *Def_path = NULL;
+static char path[LINE_MAX] = "PATH=";
+static char supath[LINE_MAX] = "PATH=";
+static char homedir[LINE_MAX] = "HOME=";
+static char logname[LINE_MAX] = "LOGNAME=";
+static char tzone[LINE_MAX] = "TZ=";
+static char *envinit[] = {
+ homedir,
+ logname,
+ ROOTPATH,
+ "SHELL=/usr/bin/sh",
+ tzone,
+ NULL
+};
+
+extern char **environ;
+
+#define DEFTZ "GMT"
+static int log = 0;
+static char hzname[10];
+
+static void cronend(int);
+static void thaw_handler(int);
+static void child_handler(int);
+static void child_sigreset(void);
+
+static void dscan(DIR *dir, void (*fp)(char *, time_t));
+static void mod_ctab(char *, time_t);
+static void mod_atjob(char *, time_t);
+static void add_atevent(struct usr *, char *, time_t, int);
+static void rm_ctevents(struct usr *);
+static void cleanup(struct runinfo *rn, int r);
+static void crabort(char *, int);
+static void msg(char *fmt, ...);
+static void logit(int, struct runinfo *, int);
+static void parsqdef(char *);
+static void defaults();
+static void initialize(int);
+static void quedefs(int);
+static int idle(long);
+static struct usr *find_usr(char *);
+static int ex(struct event *e);
+static void read_dirs(void);
+static void mail(char *, char *, int);
+static char *next_field(int, int);
+static void readcron(struct usr *, time_t);
+static int next_ge(int, char *);
+static void free_if_unused(struct usr *);
+static void del_atjob(char *, char *);
+static void del_ctab(char *);
+static void resched(int);
+static int msg_wait(long);
+static struct runinfo *rinfo_get(pid_t);
+static void rinfo_free(struct runinfo *rp);
+static void mail_result(struct usr *p, struct runinfo *pr, size_t filesize);
+static time_t next_time(struct event *, time_t);
+static time_t get_switching_time(int, time_t);
+static time_t xmktime(struct tm *);
+static void process_msg(struct message *, time_t);
+static void reap_child(void);
+static void miscpid_insert(pid_t);
+static int miscpid_delete(pid_t);
+static int contract_set_template(void);
+static int contract_clear_template(void);
+static void contract_abandon_latest(pid_t);
+
+static void cte_init(void);
+static void cte_add(int, char *);
+static void cte_valid(void);
+static int cte_istoomany(void);
+static void cte_sendmail(char *);
+
+static int set_user_cred(const struct usr *, struct project *);
+
+/*
+ * last_time is set immediately prior to exection of an event (via ex())
+ * to indicate the last time an event was executed. This was (surely)
+ * it's original intended use.
+ */
+static time_t last_time, init_time, t_old;
+
+static int accept_sigcld, notifypipe[2];
+static sigset_t defmask, childmask;
+
+/*
+ * BSM hooks
+ */
+extern int audit_cron_session(char *, char *, uid_t, gid_t, char *);
+extern void audit_cron_new_job(char *, int, void *);
+extern void audit_cron_bad_user(char *);
+extern void audit_cron_user_acct_expired(char *);
+extern int audit_cron_create_anc_file(char *, char *, char *, uid_t);
+extern int audit_cron_delete_anc_file(char *, char *);
+extern int audit_cron_is_anc_name(char *);
+extern int audit_cron_mode();
+
+static int cron_conv(int, struct pam_message **,
+ struct pam_response **, void *);
+
+static struct pam_conv pam_conv = {cron_conv, NULL};
+static pam_handle_t *pamh; /* Authentication handle */
+
+/*
+ * Function to help check a user's credentials.
+ */
+
+static int verify_user_cred(const struct usr *u);
+
+/*
+ * Values returned by verify_user_cred and set_user_cred:
+ */
+
+#define VUC_OK 0
+#define VUC_BADUSER 1
+#define VUC_NOTINGROUP 2
+#define VUC_EXPIRED 3
+#define VUC_NEW_AUTH 4
+
+/*
+ * Modes of process_anc_files function
+ */
+#define CRON_ANC_DELETE 1
+#define CRON_ANC_CREATE 0
+
+/*
+ * Functions to remove a user or job completely from the running database.
+ */
+static void clean_out_atjobs(struct usr *u);
+static void clean_out_ctab(struct usr *u);
+static void clean_out_user(struct usr *u);
+static void cron_unlink(char *name);
+static void process_anc_files(int);
+
+/*
+ * functions in elm.c
+ */
+extern void el_init(int, time_t, time_t, int);
+extern void el_add(void *, time_t, int);
+extern void el_remove(int, int);
+extern int el_empty(void);
+extern void *el_first(void);
+extern void el_delete(void);
+
+int
+main(int argc, char *argv[])
+{
+ time_t t;
+ time_t ne_time; /* amt of time until next event execution */
+ time_t newtime, lastmtime = 0L;
+ struct usr *u;
+ struct event *e, *e2, *eprev;
+ struct stat buf;
+ pid_t rfork;
+ struct sigaction act;
+
+ /*
+ * reset is set to 1 via the return from ex() should ex() find
+ * that the event to be executed is being run at the wrong time.
+ * We immediately return to the top of the while (TRUE) loop in
+ * main() where the event list is cleared and rebuilt, and reset
+ * is set back to 0.
+ */
+ int reset = 0;
+
+ /*
+ * Only the privileged user can run this command.
+ */
+ if (getuid() != 0)
+ crabort(NOTALLOWED, 0);
+
+begin:
+ (void) setlocale(LC_ALL, "");
+ /* fork unless 'nofork' is specified */
+ if ((argc <= 1) || (strcmp(argv[1], "nofork"))) {
+ if (rfork = fork()) {
+ if (rfork == (pid_t)-1) {
+ (void) sleep(30);
+ goto begin;
+ }
+ return (0);
+ }
+ didfork++;
+ (void) setpgrp(); /* detach cron from console */
+ }
+
+ (void) umask(022);
+ (void) signal(SIGHUP, SIG_IGN);
+ (void) signal(SIGINT, SIG_IGN);
+ (void) signal(SIGQUIT, SIG_IGN);
+ (void) signal(SIGTERM, cronend);
+
+ defaults();
+ initialize(1);
+ quedefs(DEFAULT); /* load default queue definitions */
+ cron_pid = getpid();
+ msg("*** cron started *** pid = %d", cron_pid);
+ (void) sigset(SIGTHAW, thaw_handler);
+ /*
+ * setup SIGCLD handler/mask
+ */
+ act.sa_handler = child_handler;
+ act.sa_flags = 0;
+ (void) sigemptyset(&act.sa_mask);
+ (void) sigaddset(&act.sa_mask, SIGCLD);
+ (void) sigaction(SIGCLD, &act, NULL);
+ (void) sigemptyset(&childmask);
+ (void) sigaddset(&childmask, SIGCLD);
+ (void) sigprocmask(SIG_BLOCK, &childmask, &defmask);
+
+ if (pipe(notifypipe) != 0) {
+ crabort("cannot create pipe", REMOVE_FIFO|CONSOLE_MSG);
+ }
+ /*
+ * will set O_NONBLOCK, so that the write() from child_handler
+ * never be blocked.
+ */
+ (void) fcntl(notifypipe[0], F_SETFL, O_WRONLY|O_NONBLOCK);
+
+ t_old = time(NULL);
+ last_time = t_old;
+ for (;;) { /* MAIN LOOP */
+ t = time(NULL);
+ if ((t_old > t) || (t-last_time > CUSHION) || reset) {
+ reset = 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;
+ }
+ u->atevents = NULL;
+ u = u->nextusr;
+ }
+ (void) close(msgfd);
+ initialize(0);
+ t = time(NULL);
+ last_time = t;
+ }
+ t_old = t;
+
+ if (next_event == NULL && !el_empty()) {
+ next_event = (struct event *)el_first();
+ }
+ if (next_event == NULL) {
+ ne_time = INFINITY;
+ } else {
+ ne_time = next_event->time - t;
+#ifdef DEBUG
+ cftime(timebuf, "%C", &next_event->time);
+ (void) fprintf(stderr, "next_time=%ld %s\n",
+ next_event->time, timebuf);
+#endif
+ }
+ if (ne_time > 0) {
+ if ((reset = idle(ne_time)) != 0)
+ continue;
+ }
+
+ if (stat(QUEDEFS, &buf)) {
+ msg("cannot stat QUEDEFS file");
+ } else if (lastmtime != buf.st_mtime) {
+ quedefs(LOAD);
+ lastmtime = buf.st_mtime;
+ }
+
+ last_time = next_event->time; /* save execution time */
+
+ if (reset = ex(next_event))
+ continue;
+
+ switch (next_event->etype) {
+ case CRONEVENT:
+ /* add cronevent back into the main event list */
+ if (delayed) {
+ delayed = 0;
+ break;
+ }
+
+ /*
+ * check if time(0)< last_time. if so, then the
+ * system clock has gone backwards. to prevent this
+ * job from being started twice, we reschedule this
+ * job for the >>next time after last_time<<, and
+ * then set next_event->time to this. note that
+ * crontab's resolution is 1 minute.
+ */
+
+ if (last_time > time(NULL)) {
+ msg(CLOCK_DRIFT);
+ /*
+ * bump up to next 30 second
+ * increment
+ * 1 <= newtime <= 30
+ */
+ newtime = 30 - (last_time % 30);
+ newtime += last_time;
+
+ /*
+ * get the next scheduled event,
+ * not the one that we just
+ * kicked off!
+ */
+ next_event->time =
+ next_time(next_event, newtime);
+ t_old = time(NULL);
+ } else {
+ next_event->time =
+ next_time(next_event, (time_t)0);
+ }
+#ifdef DEBUG
+ cftime(timebuf, "%C", &next_event->time);
+ (void) fprintf(stderr,
+ "pushing back cron event %s at %ld (%s)\n",
+ next_event->cmd, next_event->time, timebuf);
+#endif
+
+ el_add(next_event, next_event->time,
+ (next_event->u)->ctid);
+ break;
+ default:
+ /* remove at or batch job from system */
+ if (delayed) {
+ delayed = 0;
+ break;
+ }
+ 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;
+ }
+
+ /*NOTREACHED*/
+}
+
+static void
+initialize(int firstpass)
+{
+#ifdef DEBUG
+ (void) fprintf(stderr, "in initialize\n");
+#endif
+ init_time = time(NULL);
+ el_init(8, init_time, (time_t)(60*60*24), 10);
+ if (firstpass) {
+ /* for mail(1), make sure messages come from root */
+ if (putenv("LOGNAME=root") != 0) {
+ crabort("cannot expand env variable",
+ REMOVE_FIFO|CONSOLE_MSG);
+ }
+ if (access(FIFO, R_OK) == -1) {
+ if (errno == ENOENT) {
+ if (mknod(FIFO, S_IFIFO|0600, 0) != 0)
+ crabort("cannot create fifo queue",
+ REMOVE_FIFO|CONSOLE_MSG);
+ } else {
+ if (NOFORK) {
+ /* didn't fork... init(1M) is waiting */
+ (void) sleep(60);
+ }
+ perror("FIFO");
+ crabort("cannot access fifo queue",
+ REMOVE_FIFO|CONSOLE_MSG);
+ }
+ } else {
+ if (NOFORK) {
+ /* didn't fork... init(1M) is waiting */
+ (void) sleep(60);
+ /*
+ * the wait is painful, but we don't want
+ * init respawning this quickly
+ */
+ }
+ crabort("cannot start cron; FIFO exists", CONSOLE_MSG);
+ }
+ }
+
+ if ((msgfd = open(FIFO, O_RDWR)) < 0) {
+ perror("! open");
+ crabort("cannot open fifo queue", REMOVE_FIFO|CONSOLE_MSG);
+ }
+
+ /*
+ * read directories, create users list, and add events to the
+ * main event list. Only zero user list on firstpass.
+ */
+ if (firstpass)
+ uhead = NULL;
+ read_dirs();
+ next_event = NULL;
+
+ if (!firstpass)
+ return;
+
+ /* stdout is log file */
+ if (freopen(ACCTFILE, "a", stdout) == NULL)
+ (void) fprintf(stderr, "cannot open %s\n", ACCTFILE);
+
+ /* log should be root-only */
+ (void) fchmod(1, S_IRUSR|S_IWUSR);
+
+ /* stderr also goes to ACCTFILE */
+ (void) close(fileno(stderr));
+ (void) dup(1);
+
+ /* null for stdin */
+ (void) freopen("/dev/null", "r", stdin);
+
+ contract_set_template();
+}
+
+static void
+read_dirs()
+{
+ DIR *dir;
+
+ if (chdir(CRONDIR) == -1)
+ crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG);
+ cwd = CRON;
+ if ((dir = opendir(".")) == NULL)
+ crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG);
+ dscan(dir, mod_ctab);
+ (void) closedir(dir);
+ if (chdir(ATDIR) == -1) {
+ msg("cannot chdir to at directory");
+ return;
+ }
+ cwd = AT;
+ if ((dir = opendir(".")) == NULL) {
+ msg("cannot read at at directory");
+ return;
+ }
+ dscan(dir, mod_atjob);
+ (void) closedir(dir);
+}
+
+static void
+dscan(DIR *df, void (*fp)(char *, time_t))
+{
+ struct dirent *dp;
+
+ while ((dp = readdir(df)) != NULL) {
+ if (strcmp(dp->d_name, ".") == 0 ||
+ strcmp(dp->d_name, "..") == 0) {
+ continue;
+ }
+ (*fp)(dp->d_name, 0);
+ }
+}
+
+static void
+mod_ctab(char *name, time_t reftime)
+{
+ struct passwd *pw;
+ struct stat buf;
+ struct usr *u;
+ char namebuf[PATH_MAX];
+ char *pname;
+
+ /* skip over ancillary file names */
+ if (audit_cron_is_anc_name(name))
+ return;
+
+ if ((pw = getpwnam(name)) == NULL) {
+ msg("No such user as %s - cron entries not created", name);
+ return;
+ }
+ if (cwd != CRON) {
+ if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
+ CRONDIR, name) >= sizeof (namebuf)) {
+ msg("Too long path name %s - cron entries not created",
+ namebuf);
+ return;
+ }
+ pname = namebuf;
+ } else {
+ pname = name;
+ }
+ /*
+ * a warning message is given by the crontab command so there is
+ * no need to give one here...... use this code if you only want
+ * users with a login shell of /usr/bin/sh to use cron
+ */
+#ifdef BOURNESHELLONLY
+ if ((strcmp(pw->pw_shell, "") != 0) &&
+ (strcmp(pw->pw_shell, SHELL) != 0)) {
+ mail(name, BADSHELL, ERR_CANTEXECCRON);
+ cron_unlink(pname);
+ return;
+ }
+#endif
+ if (stat(pname, &buf)) {
+ mail(name, BADSTAT, ERR_UNIXERR);
+ cron_unlink(pname);
+ return;
+ }
+ if (!S_ISREG(buf.st_mode)) {
+ mail(name, BADTYPE, ERR_CRONTABENT);
+ return;
+ }
+ if ((u = find_usr(name)) == NULL) {
+#ifdef DEBUG
+ (void) fprintf(stderr, "new user (%s) with a crontab\n", name);
+#endif
+ u = xmalloc(sizeof (struct usr));
+ u->name = xmalloc(strlen(name)+1);
+ (void) strcpy(u->name, name);
+ u->home = xmalloc(strlen(pw->pw_dir)+1);
+ (void) strcpy(u->home, pw->pw_dir);
+ u->uid = pw->pw_uid;
+ u->gid = pw->pw_gid;
+ u->ctexists = TRUE;
+ u->ctid = ecid++;
+ u->ctevents = NULL;
+ u->atevents = NULL;
+ u->aruncnt = 0;
+ u->cruncnt = 0;
+ u->nextusr = uhead;
+ uhead = u;
+ readcron(u, reftime);
+ } 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);
+ (void) strcpy(u->home, pw->pw_dir);
+ }
+ u->ctexists = TRUE;
+ if (u->ctid == 0) {
+#ifdef DEBUG
+ (void) 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, reftime);
+ return;
+ }
+#ifdef DEBUG
+ (void) fprintf(stderr, "%s has revised his crontab\n", u->name);
+#endif
+ rm_ctevents(u);
+ el_remove(u->ctid, 0);
+ readcron(u, reftime);
+ }
+}
+
+/* ARGSUSED */
+static void
+mod_atjob(char *name, time_t reftime)
+{
+ char *ptr;
+ time_t tim;
+ struct passwd *pw;
+ struct stat buf;
+ struct usr *u;
+ struct event *e;
+ char namebuf[PATH_MAX];
+ char *pname;
+ int jobtype;
+
+ ptr = name;
+ if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
+ return;
+ ptr++;
+ if (!isalpha(*ptr))
+ return;
+ jobtype = *ptr - 'a';
+
+ /* check for audit ancillary file */
+ if (audit_cron_is_anc_name(name))
+ return;
+
+ if (cwd != AT) {
+ if (snprintf(namebuf, sizeof (namebuf), "%s/%s", ATDIR, name)
+ >= sizeof (namebuf)) {
+ return;
+ }
+ pname = namebuf;
+ } else {
+ pname = name;
+ }
+ if (stat(pname, &buf) || jobtype >= NQUEUE) {
+ cron_unlink(pname);
+ return;
+ }
+ if (!(buf.st_mode & ISUID) || !S_ISREG(buf.st_mode)) {
+ cron_unlink(pname);
+ return;
+ }
+ if ((pw = getpwuid(buf.st_uid)) == NULL) {
+ cron_unlink(pname);
+ return;
+ }
+ /*
+ * a warning message is given by the at command so there is no
+ * need to give one here......use this code if you only want
+ * users with a login shell of /usr/bin/sh to use cron
+ */
+#ifdef BOURNESHELLONLY
+ if ((strcmp(pw->pw_shell, "") != 0) &&
+ (strcmp(pw->pw_shell, SHELL) != 0)) {
+ mail(pw->pw_name, BADSHELL, ERR_CANTEXECAT);
+ cron_unlink(pname);
+ return;
+ }
+#endif
+ if ((u = find_usr(pw->pw_name)) == NULL) {
+#ifdef DEBUG
+ (void) fprintf(stderr, "new user (%s) with an at job = %s\n",
+ pw->pw_name, name);
+#endif
+ u = xmalloc(sizeof (struct usr));
+ u->name = xmalloc(strlen(pw->pw_name)+1);
+ (void) strcpy(u->name, pw->pw_name);
+ u->home = xmalloc(strlen(pw->pw_dir)+1);
+ (void) strcpy(u->home, pw->pw_dir);
+ u->uid = pw->pw_uid;
+ u->gid = pw->pw_gid;
+ u->ctexists = FALSE;
+ u->ctid = 0;
+ u->ctevents = NULL;
+ u->atevents = NULL;
+ u->aruncnt = 0;
+ u->cruncnt = 0;
+ u->nextusr = uhead;
+ uhead = u;
+ add_atevent(u, name, tim, jobtype);
+ } 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);
+ (void) 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
+ (void) fprintf(stderr, "%s has a new at job = %s\n",
+ u->name, name);
+#endif
+ add_atevent(u, name, tim, jobtype);
+ }
+ }
+}
+
+static void
+add_atevent(struct usr *u, char *job, time_t tim, int jobtype)
+{
+ struct event *e;
+
+ e = xmalloc(sizeof (struct event));
+ e->etype = jobtype;
+ e->cmd = xmalloc(strlen(job)+1);
+ (void) strcpy(e->cmd, job);
+ e->u = u;
+ 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;
+#ifdef DEBUG
+ (void) fprintf(stderr, "add_atevent: user=%s, job=%s, time=%ld\n",
+ u->name, e->cmd, e->time);
+#endif
+ el_add(e, e->time, e->of.at.eventid);
+}
+
+
+static char line[CTLINESIZE]; /* holds a line from a crontab file */
+static int cursor; /* cursor for the above line */
+
+static void
+readcron(struct usr *u, time_t reftime)
+{
+ /*
+ * 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;
+ unsigned int i;
+ char namebuf[PATH_MAX];
+ char *pname;
+ int lineno = 0;
+
+ /* read the crontab file */
+ cte_init(); /* Init error handling */
+ if (cwd != CRON) {
+ if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
+ CRONDIR, u->name) >= sizeof (namebuf)) {
+ return;
+ }
+ pname = namebuf;
+ } else {
+ pname = u->name;
+ }
+ if ((cf = fopen(pname, "r")) == NULL) {
+ mail(u->name, NOREAD, ERR_UNIXERR);
+ return;
+ }
+ while (fgets(line, CTLINESIZE, cf) != NULL) {
+ /* process a line of a crontab file */
+ lineno++;
+ if (cte_istoomany())
+ break;
+ cursor = 0;
+ while (line[cursor] == ' ' || line[cursor] == '\t')
+ cursor++;
+ if (line[cursor] == '#' || line[cursor] == '\n')
+ continue;
+ e = xmalloc(sizeof (struct event));
+ e->etype = CRONEVENT;
+ if (!(((e->of.ct.minute = next_field(0, 59)) != NULL) &&
+ ((e->of.ct.hour = next_field(0, 23)) != NULL) &&
+ ((e->of.ct.daymon = next_field(1, 31)) != NULL) &&
+ ((e->of.ct.month = next_field(1, 12)) != NULL) &&
+ ((e->of.ct.dayweek = next_field(0, 6)) != NULL))) {
+ free(e);
+ cte_add(lineno, line);
+ continue;
+ }
+ while (line[cursor] == ' ' || line[cursor] == '\t')
+ cursor++;
+ if (line[cursor] == '\n' || line[cursor] == '\0')
+ continue;
+ /* get the command to execute */
+ start = cursor;
+again:
+ while ((line[cursor] != '%') &&
+ (line[cursor] != '\n') &&
+ (line[cursor] != '\0') &&
+ (line[cursor] != '\\'))
+ cursor++;
+ if (line[cursor] == '\\') {
+ cursor += 2;
+ goto again;
+ }
+ e->cmd = xmalloc(cursor-start+1);
+ (void) 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);
+ (void) 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, reftime);
+ /* finally, add this event to the main event list */
+ el_add(e, e->time, u->ctid);
+ cte_valid();
+#ifdef DEBUG
+ cftime(timebuf, "%C", &e->time);
+ (void) fprintf(stderr, "inserting cron event %s at %ld (%s)\n",
+ e->cmd, e->time, timebuf);
+#endif
+ }
+ cte_sendmail(u->name); /* mail errors if any to user */
+ (void) fclose(cf);
+}
+
+/*
+ * Below are the functions for handling of errors in crontabs. Concept is to
+ * collect faulty lines and send one email at the end of the crontab
+ * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
+ * of crontab is aborted. Otherwise reading of crontab is continued to the end
+ * of the file but no further error logging appears.
+ */
+static void
+cte_init()
+{
+ if (cte_text == NULL)
+ cte_text = xmalloc(MAILBUFLEN);
+ (void) strlcpy(cte_text, cte_intro, MAILBUFLEN);
+ cte_lp = cte_text + sizeof (cte_intro) - 1;
+ cte_free = MAILBINITFREE;
+ cte_nvalid = 0;
+}
+
+static void
+cte_add(int lineno, char *ctline)
+{
+ int len;
+ char *p;
+
+ if (cte_free >= LINELIMIT) {
+ (void) sprintf(cte_lp, "%4d: ", lineno);
+ (void) strlcat(cte_lp, ctline, LINELIMIT - 1);
+ len = strlen(cte_lp);
+ if (cte_lp[len - 1] != '\n') {
+ cte_lp[len++] = '\n';
+ cte_lp[len] = '\0';
+ }
+ for (p = cte_lp; *p; p++) {
+ if (isprint(*p) || *p == '\n' || *p == '\t')
+ continue;
+ *p = '.';
+ }
+ cte_lp += len;
+ cte_free -= len;
+ if (cte_free < LINELIMIT) {
+ size_t buflen = MAILBUFLEN - (cte_lp - cte_text);
+ (void) strlcpy(cte_lp, cte_trail1, buflen);
+ if (cte_nvalid == 0)
+ (void) strlcat(cte_lp, cte_trail2, buflen);
+ }
+ }
+}
+
+static void
+cte_valid()
+{
+ cte_nvalid++;
+}
+
+static int
+cte_istoomany()
+{
+ /*
+ * Return TRUE only if all lines are faulty. So evaluation of
+ * a crontab is not aborted if at least one valid line was found.
+ */
+ return (cte_nvalid == 0 && cte_free < LINELIMIT);
+}
+
+static void
+cte_sendmail(char *username)
+{
+ if (cte_free < MAILBINITFREE)
+ mail(username, cte_text, ERR_CRONTABENT);
+}
+
+/*
+ * Send mail with error message to a user
+ */
+static void
+mail(char *usrname, char *mesg, int format)
+{
+ /* mail mails a user a message. */
+ FILE *pipe;
+ char *temp;
+ struct passwd *ruser_ids;
+ pid_t fork_val;
+ int saveerrno = errno;
+ struct utsname name;
+
+#ifdef TESTING
+ return;
+#endif
+ (void) uname(&name);
+ if ((fork_val = fork()) == (pid_t)-1) {
+ msg("cron cannot fork\n");
+ return;
+ }
+ if (fork_val == 0) {
+ child_sigreset();
+ contract_clear_template();
+ if ((ruser_ids = getpwnam(usrname)) == NULL)
+ exit(0);
+ (void) setuid(ruser_ids->pw_uid);
+ temp = xmalloc(strlen(MAIL)+strlen(usrname)+2);
+ (void) sprintf(temp, "%s %s", MAIL, usrname);
+ pipe = popen(temp, "w");
+ if (pipe != NULL) {
+ (void) fprintf(pipe, "To: %s\n", usrname);
+ switch (format) {
+ case ERR_CRONTABENT:
+ (void) fprintf(pipe, CRONTABERR);
+ (void) fprintf(pipe, "Your \"crontab\" on %s\n",
+ name.nodename);
+ (void) fprintf(pipe, mesg);
+ (void) fprintf(pipe,
+ "\nEntries or crontab have been ignored\n");
+ break;
+ case ERR_UNIXERR:
+ (void) fprintf(pipe, "Subject: %s\n\n", mesg);
+ (void) fprintf(pipe,
+ "The error on %s was \"%s\"\n",
+ name.nodename, errmsg(saveerrno));
+ break;
+
+ case ERR_CANTEXECCRON:
+ (void) fprintf(pipe,
+ "Subject: Couldn't run your \"cron\" job\n\n");
+ (void) fprintf(pipe,
+ "Your \"cron\" job on %s ", name.nodename);
+ (void) fprintf(pipe, "couldn't be run\n");
+ (void) fprintf(pipe, "%s\n", mesg);
+ (void) fprintf(pipe,
+ "The error was \"%s\"\n", errmsg(saveerrno));
+ break;
+
+ case ERR_CANTEXECAT:
+ (void) fprintf(pipe,
+ "Subject: Couldn't run your \"at\" job\n\n");
+ (void) fprintf(pipe, "Your \"at\" job on %s ",
+ name.nodename);
+ (void) fprintf(pipe, "couldn't be run\n");
+ (void) fprintf(pipe, "%s\n", mesg);
+ (void) fprintf(pipe,
+ "The error was \"%s\"\n", errmsg(saveerrno));
+ break;
+
+ default:
+ break;
+ }
+ (void) pclose(pipe);
+ }
+ free(temp);
+ exit(0);
+ }
+
+ contract_abandon_latest(fork_val);
+
+ if (cron_pid == getpid()) {
+ miscpid_insert(fork_val);
+ }
+}
+
+static char *
+next_field(int lower, int upper)
+{
+ /*
+ * 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 num, num2, start;
+
+ while ((line[cursor] == ' ') || (line[cursor] == '\t'))
+ cursor++;
+ start = cursor;
+ if (line[cursor] == '\0') {
+ return (NULL);
+ }
+ if (line[cursor] == '*') {
+ cursor++;
+ if ((line[cursor] != ' ') && (line[cursor] != '\t'))
+ return (NULL);
+ s = xmalloc(2);
+ (void) strcpy(s, "*");
+ return (s);
+ }
+ for (;;) {
+ if (!isdigit(line[cursor]))
+ return (NULL);
+ num = 0;
+ do {
+ num = num*10 + (line[cursor]-'0');
+ } while (isdigit(line[++cursor]));
+ if ((num < lower) || (num > upper))
+ return (NULL);
+ if (line[cursor] == '-') {
+ if (!isdigit(line[++cursor]))
+ return (NULL);
+ num2 = 0;
+ do {
+ num2 = num2*10 + (line[cursor]-'0');
+ } while (isdigit(line[++cursor]));
+ if ((num2 < lower) || (num2 > upper))
+ return (NULL);
+ }
+ if ((line[cursor] == ' ') || (line[cursor] == '\t'))
+ break;
+ if (line[cursor] == '\0')
+ return (NULL);
+ if (line[cursor++] != ',')
+ return (NULL);
+ }
+ s = xmalloc(cursor-start+1);
+ (void) strncpy(s, line+start, cursor-start);
+ s[cursor-start] = '\0';
+ return (s);
+}
+
+#define tm_cmp(t1, t2) (\
+ (t1)->tm_year == (t2)->tm_year && \
+ (t1)->tm_mon == (t2)->tm_mon && \
+ (t1)->tm_mday == (t2)->tm_mday && \
+ (t1)->tm_hour == (t2)->tm_hour && \
+ (t1)->tm_min == (t2)->tm_min)
+
+#define tm_setup(tp, yr, mon, dy, hr, min, dst) \
+ (tp)->tm_year = yr; \
+ (tp)->tm_mon = mon; \
+ (tp)->tm_mday = dy; \
+ (tp)->tm_hour = hr; \
+ (tp)->tm_min = min; \
+ (tp)->tm_isdst = dst; \
+ (tp)->tm_sec = 0; \
+ (tp)->tm_wday = 0; \
+ (tp)->tm_yday = 0;
+
+/*
+ * modification for bugid 1104537. the second argument to next_time is
+ * now the value of time(2) to be used. if this is 0, then use the
+ * current time. otherwise, the second argument is the time from which to
+ * calculate things. this is useful to correct situations where you've
+ * gone backwards in time (I.e. the system's internal clock is correcting
+ * itself backwards).
+ */
+
+static time_t
+next_time(struct event *e, time_t tflag)
+{
+ /*
+ * 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, ref_tm, tmp, tmp1, tmp2;
+ 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, ref_t, t1, t2, zone_start;
+ int fallback;
+ extern int days_btwn(int, int, int, int, int, int);
+
+ if (tflag == 0) {
+ t = time(NULL); /* original way of doing things */
+ } else {
+ t = tflag;
+ }
+
+ tm = &ref_tm; /* use a local variable and call localtime_r() */
+ ref_t = t; /* keep a copy of the reference time */
+
+recalc:
+ fallback = 0;
+
+ (void) localtime_r(&t, tm);
+
+ if (daylight) {
+ tmp = *tm;
+ tmp.tm_isdst = (tm->tm_isdst > 0 ? 0 : 1);
+ t1 = xmktime(&tmp);
+ /*
+ * see if we will have timezone switch over, and clock will
+ * fall back. zone_start will hold the time when it happens
+ * (ie time of PST -> PDT switch over).
+ */
+ if (tm->tm_isdst != tmp.tm_isdst &&
+ (t1 - t) == (timezone - altzone) &&
+ tm_cmp(tm, &tmp)) {
+ zone_start = get_switching_time(tmp.tm_isdst, t);
+ fallback = 1;
+ }
+ }
+
+ 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 + (t == ref_t ? 1 : 0);
+ if ((tm->tm_hour + 1) <= next_ge(tm->tm_hour, 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 == 0 && today) {
+ /* this event must occur today */
+ tm_setup(&tmp, tm->tm_year, tm->tm_mon, tm->tm_mday,
+ hr, min, tm->tm_isdst);
+ tmp1 = tmp;
+ if ((t1 = xmktime(&tmp1)) == (time_t)-1) {
+ return (0);
+ }
+ if (daylight && tmp.tm_isdst != tmp1.tm_isdst) {
+ /* In case we are falling back */
+ if (fallback) {
+ /* we may need to run the job once more. */
+ t = zone_start;
+ goto recalc;
+ }
+
+ /*
+ * In case we are not in falling back period,
+ * calculate the time assuming the DST. If the
+ * date/time is not altered by mktime, it is the
+ * time to execute the job.
+ */
+ tmp2 = tmp;
+ tmp2.tm_isdst = tmp1.tm_isdst;
+ if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
+ return (0);
+ }
+ if (tmp1.tm_isdst == tmp2.tm_isdst &&
+ tm_cmp(&tmp, &tmp2)) {
+ /*
+ * We got a valid time.
+ */
+ return (t1);
+ } else {
+ /*
+ * If the date does not match even if
+ * we assume the alternate timezone, then
+ * it must be the invalid time. eg
+ * 2am while switching 1:59am to 3am.
+ * t1 should point the time before the
+ * switching over as we've calculate the
+ * time with assuming alternate zone.
+ */
+ if (tmp1.tm_isdst != tmp2.tm_isdst) {
+ t = get_switching_time(tmp1.tm_isdst,
+ t1);
+ } else {
+ /* does this really happen? */
+ t = get_switching_time(tmp1.tm_isdst,
+ t1 - abs(timezone - altzone));
+ }
+ if (t == (time_t)-1)
+ return (0);
+ }
+ goto recalc;
+ }
+ if (tm_cmp(&tmp, &tmp1)) {
+ /* got valid time */
+ return (t1);
+ } else {
+ /*
+ * This should never happen, but just in
+ * case, we fall back to the old code.
+ */
+ if (tm->tm_min > min) {
+ t += (time_t)(hr-tm->tm_hour-1) * HOUR +
+ (time_t)(60-tm->tm_min+min) * MINUTE;
+ } else {
+ t += (time_t)(hr-tm->tm_hour) * HOUR +
+ (time_t)(min-tm->tm_min) * MINUTE;
+ }
+ t1 = t;
+ t -= (time_t)tm->tm_sec;
+ (void) localtime_r(&t, &tmp);
+ if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
+ t -= (timezone - altzone);
+ return ((t <= ref_t) ? t1 : t);
+ }
+ }
+
+ /*
+ * Job won't run today, however if we have a switch over within
+ * one hour and we will have one hour time drifting back in this
+ * period, we may need to run the job one more time if the job was
+ * set to run on this hour of clock.
+ */
+ if (fallback) {
+ t = zone_start;
+ goto recalc;
+ }
+
+ 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
+ */
+
+ /* 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.
+ */
+ tm_setup(&tmp, yr, mon, day, hr, min, -1);
+ tmp2 = tmp;
+ if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
+ return (0);
+ }
+ if (tm_cmp(&tmp, &tmp2)) {
+ /*
+ * mktime returns clock for the current time zone. If the
+ * target date was in fallback period, it needs to be adjusted
+ * to the time comes first.
+ * Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
+ * mktime returns the time in PST, but 1:30am in PDT comes
+ * first. So reverse the tm_isdst, and see if we have such
+ * time/date.
+ */
+ if (daylight) {
+ int dst = tmp2.tm_isdst;
+
+ tmp2 = tmp;
+ tmp2.tm_isdst = (dst > 0 ? 0 : 1);
+ if ((t2 = xmktime(&tmp2)) == (time_t)-1) {
+ return (0);
+ }
+ if (tm_cmp(&tmp, &tmp2)) {
+ /*
+ * same time/date found in the opposite zone.
+ * check the clock to see which comes early.
+ */
+ if (t2 > ref_t && t2 < t1) {
+ t1 = t2;
+ }
+ }
+ }
+ return (t1);
+ } else {
+ /*
+ * mktime has set different time/date for the given date.
+ * This means that the next job is scheduled to be run on the
+ * invalid time. There are three possible invalid date/time.
+ * 1. Non existing day of the month. such as April 31th.
+ * 2. Feb 29th in the non-leap year.
+ * 3. Time gap during the DST switch over.
+ */
+ d1 = days_in_mon(mon, yr);
+ if ((mon != 1 && day > d1) || (mon == 1 && day > 29)) {
+ /*
+ * see if we have got a specific date which
+ * is invalid.
+ */
+ if (strcmp(e->of.ct.dayweek, "*") == 0 &&
+ mon == (next_ge((mon+1)%12+1, e->of.ct.month)-1) &&
+ day <= next_ge(1, e->of.ct.daymon)) {
+ /* job never run */
+ return (0);
+ }
+ /*
+ * Since the day has gone invalid, we need to go to
+ * next month, and recalcuate the first occurrence.
+ * eg the cron tab such as:
+ * 0 0 1,15,31 1,2,3,4,5 * /usr/bin....
+ * 2/31 is invalid, so the next job is 3/1.
+ */
+ tmp2 = tmp;
+ tmp2.tm_min = 0;
+ tmp2.tm_hour = 0;
+ tmp2.tm_mday = 1; /* 1st day of the month */
+ if (mon == 11) {
+ tmp2.tm_mon = 0;
+ tmp2.tm_year = yr + 1;
+ } else {
+ tmp2.tm_mon = mon + 1;
+ }
+ if ((t = xmktime(&tmp2)) == (time_t)-1) {
+ return (0);
+ }
+ } else if (mon == 1 && day > d1) {
+ /*
+ * ie 29th in the non-leap year. Forwarding the
+ * clock to Feb 29th 00:00 (March 1st), and recalculate
+ * the next time.
+ */
+ tmp2 = tmp;
+ tmp2.tm_min = 0;
+ tmp2.tm_hour = 0;
+ if ((t = xmktime(&tmp2)) == (time_t)-1) {
+ return (0);
+ }
+ } else if (daylight) {
+ /*
+ * Non existing time, eg 2am PST during summer time
+ * switch.
+ * We need to get the correct isdst which we are
+ * swithing to, by adding time difference to make sure
+ * that t2 is in the zone being switched.
+ */
+ t2 = t1;
+ t2 += abs(timezone - altzone);
+ (void) localtime_r(&t2, &tmp2);
+ zone_start = get_switching_time(tmp2.tm_isdst,
+ t1 - abs(timezone - altzone));
+ if (zone_start == (time_t)-1) {
+ return (0);
+ }
+ t = zone_start;
+ } else {
+ /*
+ * This should never happen, but fall back to the
+ * old code.
+ */
+ days = days_btwn(tm->tm_mon,
+ tm->tm_mday, tm->tm_year, mon, day, yr);
+ t += (time_t)(23-tm->tm_hour)*HOUR
+ + (time_t)(60-tm->tm_min)*MINUTE
+ + (time_t)hr*HOUR + (time_t)min*MINUTE
+ + (time_t)days*DAY;
+ t1 = t;
+ t -= (time_t)tm->tm_sec;
+ (void) localtime_r(&t, &tmp);
+ if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
+ t -= (timezone - altzone);
+ return (t <= ref_t ? t1 : t);
+ }
+ goto recalc;
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * This returns TOD in time_t that zone switch will happen, and this
+ * will be called when clock fallback is about to happen.
+ * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
+ * will fall back to 1:00 PDT. So this function will be called only
+ * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
+ * First goes through the common time differences to see if zone
+ * switch happens at those minutes later. If not, check every minutes
+ * until 6 hours ahead see if it happens(We might have 45minutes
+ * fallback).
+ */
+static time_t
+get_switching_time(int to_dst, time_t t_ref)
+{
+ time_t t, t1;
+ struct tm tmp, tmp1;
+ int hints[] = { 60, 120, 30, 90, 0}; /* minutes */
+ int i;
+
+ (void) localtime_r(&t_ref, &tmp);
+ tmp1 = tmp;
+ tmp1.tm_sec = 0;
+ tmp1.tm_min = 0;
+ if ((t = xmktime(&tmp1)) == (time_t)-1)
+ return ((time_t)-1);
+
+ /* fast path */
+ for (i = 0; hints[i] != 0; i++) {
+ t1 = t + hints[i] * 60;
+ (void) localtime_r(&t1, &tmp1);
+ if (tmp1.tm_isdst == to_dst) {
+ t1--;
+ (void) localtime_r(&t1, &tmp1);
+ if (tmp1.tm_isdst != to_dst) {
+ return (t1 + 1);
+ }
+ }
+ }
+
+ /* ugly, but don't know other than this. */
+ tmp1 = tmp;
+ tmp1.tm_sec = 0;
+ if ((t = xmktime(&tmp1)) == (time_t)-1)
+ return ((time_t)-1);
+ while (t < (t_ref + 6*60*60)) { /* 6 hours should be enough */
+ t += 60; /* at least one minute, I assume */
+ (void) localtime_r(&t, &tmp);
+ if (tmp.tm_isdst == to_dst)
+ return (t);
+ }
+ return ((time_t)-1);
+}
+
+static time_t
+xmktime(struct tm *tmp)
+{
+ time_t ret;
+
+ if ((ret = mktime(tmp)) == (time_t)-1) {
+ if (errno == EOVERFLOW) {
+ return ((time_t)-1);
+ }
+ crabort("internal error: mktime failed",
+ REMOVE_FIFO|CONSOLE_MSG);
+ }
+ return (ret);
+}
+
+#define DUMMY 100
+
+static int
+next_ge(int current, 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.
+ */
+
+ char *ptr;
+ int n, n2, min, min_gt;
+
+ if (strcmp(list, "*") == 0)
+ return (current);
+ ptr = list;
+ min = DUMMY;
+ min_gt = DUMMY;
+ for (;;) {
+ if ((n = (int)num(&ptr)) == current)
+ return (current);
+ if (n < min)
+ min = n;
+ if ((n > current) && (n < min_gt))
+ min_gt = n;
+ if (*ptr == '-') {
+ ptr++;
+ if ((n2 = (int)num(&ptr)) > n) {
+ if ((current > n) && (current <= n2))
+ return (current);
+ } else { /* range that wraps around */
+ if (current > n)
+ return (current);
+ if (current <= n2)
+ return (current);
+ }
+ }
+ if (*ptr == '\0')
+ break;
+ ptr += 1;
+ }
+ if (min_gt != DUMMY)
+ return (min_gt);
+ else
+ return (min);
+}
+
+static void
+free_if_unused(struct usr *u)
+{
+ struct usr *cur, *prev;
+ /*
+ * To make sure a usr structure is idle we must check that
+ * there are no at jobs queued for the user; the user does
+ * not have a crontab, and also that there are no running at
+ * or cron jobs (since the runinfo structure also has a
+ * pointer to the usr structure).
+ */
+ if (!u->ctexists && u->atevents == NULL &&
+ u->cruncnt == 0 && u->aruncnt == 0) {
+#ifdef DEBUG
+ (void) fprintf(stderr, "%s removed from usr list\n", u->name);
+#endif
+ for (cur = uhead, prev = NULL;
+ cur != u;
+ prev = cur, cur = cur->nextusr) {
+ if (cur == NULL) {
+ return;
+ }
+ }
+
+ if (prev == NULL)
+ uhead = u->nextusr;
+ else
+ prev->nextusr = u->nextusr;
+ free(u->name);
+ free(u->home);
+ free(u);
+ }
+}
+
+static void
+del_atjob(char *name, char *usrname)
+{
+
+ struct event *e, *eprev;
+ struct usr *u;
+
+ if ((u = find_usr(usrname)) == 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;
+ }
+ }
+
+ free_if_unused(u);
+}
+
+static void
+del_ctab(char *name)
+{
+
+ struct usr *u;
+
+ if ((u = find_usr(name)) == NULL)
+ return;
+ rm_ctevents(u);
+ el_remove(u->ctid, 0);
+ u->ctid = 0;
+ u->ctexists = 0;
+
+ free_if_unused(u);
+}
+
+static 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);
+ if (e2->of.ct.input != NULL)
+ free(e2->of.ct.input);
+ e3 = e2->link;
+ free(e2);
+ e2 = e3;
+ }
+ u->ctevents = NULL;
+}
+
+
+static struct usr *
+find_usr(char *uname)
+{
+ struct usr *u;
+
+ u = uhead;
+ while (u != NULL) {
+ if (strcmp(u->name, uname) == 0)
+ return (u);
+ u = u->nextusr;
+ }
+ return (NULL);
+}
+
+/*
+ * Execute cron command or at/batch job.
+ * If ever a premature return is added to this function pay attention to
+ * free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
+ */
+static int
+ex(struct event *e)
+{
+ int r;
+ int fd;
+ pid_t rfork;
+ FILE *atcmdfp;
+ char mailvar[4];
+ char *at_cmdfile = NULL;
+ struct stat buf;
+ struct queue *qp;
+ struct runinfo *rp;
+ struct project proj, *pproj = NULL;
+ char mybuf[PROJECT_BUFSZ];
+ char mybuf2[PROJECT_BUFSZ];
+ char *tmpfile;
+ FILE *fptr;
+ time_t dhltime;
+ projid_t projid;
+ int projflag = 0;
+
+ qp = &qt[e->etype]; /* set pointer to queue defs */
+ if (qp->nrun >= qp->njob) {
+ msg("%c queue max run limit reached", e->etype+'a');
+ resched(qp->nwait);
+ return (0);
+ }
+ if ((rp = rinfo_get(0)) == NULL) {
+ msg("MAXRUN (%d) procs reached", MAXRUN);
+ resched(qp->nwait);
+ return (0);
+ }
+
+#ifdef ATLIMIT
+ if ((e->u)->uid != 0 && (e->u)->aruncnt >= ATLIMIT) {
+ msg("ATLIMIT (%d) reached for uid %d",
+ ATLIMIT, (e->u)->uid);
+ rinfo_free(rp);
+ resched(qp->nwait);
+ return (0);
+ }
+#endif
+#ifdef CRONLIMIT
+ if ((e->u)->uid != 0 && (e->u)->cruncnt >= CRONLIMIT) {
+ msg("CRONLIMIT (%d) reached for uid %d",
+ CRONLIMIT, (e->u)->uid);
+ rinfo_free(rp);
+ resched(qp->nwait);
+ return (0);
+ }
+#endif
+ if ((e->u)->uid == 0) { /* set default path */
+ /* path settable in defaults file */
+ envinit[2] = supath;
+ } else {
+ envinit[2] = path;
+ }
+
+ /*
+ * the tempnam() function uses malloc(3C) to allocate space for the
+ * constructed file name, and returns a pointer to this area, which
+ * is assigned to rp->outfile. Here rp->outfile is not overwritten.
+ */
+
+ rp->outfile = tempnam(TMPDIR, PFX);
+ rp->jobtype = e->etype;
+ if (e->etype == CRONEVENT) {
+ rp->jobname = xmalloc(strlen(e->cmd)+1);
+ (void) strcpy(rp->jobname, e->cmd);
+ /* "cron" jobs only produce mail if there's output */
+ rp->mailwhendone = 0;
+ } else {
+ at_cmdfile = xmalloc(strlen(ATDIR)+strlen(e->cmd)+2);
+ (void) sprintf(at_cmdfile, "%s/%s", ATDIR, e->cmd);
+ if ((atcmdfp = fopen(at_cmdfile, "r")) == NULL) {
+ if (errno == ENAMETOOLONG) {
+ if (chdir(ATDIR) == 0)
+ cron_unlink(e->cmd);
+ } else {
+ cron_unlink(at_cmdfile);
+ }
+ mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECAT);
+ free(at_cmdfile);
+ rinfo_free(rp);
+ return (0);
+ }
+ rp->jobname = xmalloc(strlen(at_cmdfile)+1);
+ (void) strcpy(rp->jobname, at_cmdfile);
+
+ /*
+ * Skip over the first two lines.
+ */
+ (void) fscanf(atcmdfp, "%*[^\n]\n");
+ (void) fscanf(atcmdfp, "%*[^\n]\n");
+ if (fscanf(atcmdfp, ": notify by mail: %3s%*[^\n]\n",
+ mailvar) == 1) {
+ /*
+ * Check to see if we should always send mail
+ * to the owner.
+ */
+ rp->mailwhendone = (strcmp(mailvar, "yes") == 0);
+ } else {
+ rp->mailwhendone = 0;
+ }
+
+ if (fscanf(atcmdfp, "\n: project: %d\n", &projid) == 1) {
+ projflag = 1;
+ }
+ (void) fclose(atcmdfp);
+ }
+
+ /*
+ * we make sure that the system time
+ * hasn't drifted backwards. if it has, el_add() is now
+ * called, to make sure that the event queue is back in order,
+ * and we set the delayed flag. cron will pick up the request
+ * later on at the proper time.
+ */
+ dhltime = time(NULL);
+ if ((dhltime - e->time) < 0) {
+ msg("clock time drifted backwards!\n");
+ if (next_event->etype == CRONEVENT) {
+ msg("correcting cron event\n");
+ next_event->time = next_time(next_event, dhltime);
+ el_add(next_event, next_event->time,
+ (next_event->u)->ctid);
+ } else { /* etype == ATEVENT */
+ msg("correcting batch event\n");
+ el_add(next_event, next_event->time,
+ next_event->of.at.eventid);
+ }
+ delayed++;
+ t_old = time(NULL);
+ free(at_cmdfile);
+ rinfo_free(rp);
+ return (0);
+ }
+
+ if ((rfork = fork()) == (pid_t)-1) {
+ reap_child();
+ if ((rfork = fork()) == (pid_t)-1) {
+ msg("cannot fork");
+ free(at_cmdfile);
+ rinfo_free(rp);
+ resched(60);
+ (void) sleep(30);
+ return (0);
+ }
+ }
+ if (rfork) { /* parent process */
+ contract_abandon_latest(rfork);
+
+ ++qp->nrun;
+ rp->pid = rfork;
+ rp->que = e->etype;
+ if (e->etype != CRONEVENT)
+ (e->u)->aruncnt++;
+ else
+ (e->u)->cruncnt++;
+ rp->rusr = (e->u);
+ logit(BCHAR, rp, 0);
+ free(at_cmdfile);
+
+ return (0);
+ }
+
+ child_sigreset();
+ contract_clear_template();
+
+ if (e->etype != CRONEVENT) {
+ /* open jobfile as stdin to shell */
+ if (stat(at_cmdfile, &buf)) {
+ if (errno == ENAMETOOLONG) {
+ if (chdir(ATDIR) == 0)
+ cron_unlink(e->cmd);
+ } else
+ cron_unlink(at_cmdfile);
+ mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
+ exit(1);
+ }
+ if (!(buf.st_mode&ISUID)) {
+ /*
+ * if setuid bit off, original owner has
+ * given this file to someone else
+ */
+ cron_unlink(at_cmdfile);
+ exit(1);
+ }
+ if ((fd = open(at_cmdfile, O_RDONLY)) == -1) {
+ mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
+ cron_unlink(at_cmdfile);
+ exit(1);
+ }
+ if (fd != 0) {
+ (void) dup2(fd, 0);
+ (void) close(fd);
+ }
+ /*
+ * retrieve the project id of the at job and convert it
+ * to a project name. fail if it's not a valid project
+ * or if the user isn't a member of the project.
+ */
+ if (projflag == 1) {
+ if ((pproj = getprojbyid(projid, &proj,
+ (void *)&mybuf, sizeof (mybuf))) == NULL ||
+ !inproj(e->u->name, pproj->pj_name,
+ mybuf2, sizeof (mybuf2))) {
+ cron_unlink(at_cmdfile);
+ mail((e->u)->name, BADPROJID, ERR_CANTEXECAT);
+ exit(1);
+ }
+ }
+ }
+
+ /*
+ * Put process in a new session, and create a new task.
+ */
+ if (setsid() < 0) {
+ msg("setsid failed with errno = %d. job failed (%s)"
+ " for user %s", errno, e->cmd, e->u->name);
+ if (e->etype != CRONEVENT)
+ cron_unlink(at_cmdfile);
+ exit(1);
+ }
+
+ /*
+ * set correct user identification and check his account
+ */
+ r = set_user_cred(e->u, pproj);
+ if (r == VUC_EXPIRED) {
+ msg("user (%s) account is expired", e->u->name);
+ audit_cron_user_acct_expired(e->u->name);
+ clean_out_user(e->u);
+ exit(1);
+ }
+ if (r == VUC_NEW_AUTH) {
+ msg("user (%s) password has expired", e->u->name);
+ audit_cron_user_acct_expired(e->u->name);
+ clean_out_user(e->u);
+ exit(1);
+ }
+ if (r != VUC_OK) {
+ msg("bad user (%s)", e->u->name);
+ audit_cron_bad_user(e->u->name);
+ clean_out_user(e->u);
+ exit(1);
+ }
+ /*
+ * check user and initialize the supplementary group access list.
+ * bugid 1230784: deleted from parent to avoid cron hang. Now
+ * only child handles the call.
+ */
+
+ if (verify_user_cred(e->u) != VUC_OK ||
+ setgid(e->u->gid) == -1 ||
+ initgroups(e->u->name, e->u->gid) == -1) {
+ msg("bad user (%s) or setgid failed (%s)",
+ e->u->name, e->u->name);
+ audit_cron_bad_user(e->u->name);
+ clean_out_user(e->u);
+ exit(1);
+ }
+
+ if (e->etype != CRONEVENT) {
+ r = audit_cron_session(e->u->name, NULL,
+ e->u->uid, e->u->gid,
+ at_cmdfile);
+ cron_unlink(at_cmdfile);
+ } else {
+ r = audit_cron_session(e->u->name, CRONDIR,
+ e->u->uid, e->u->gid,
+ NULL);
+ }
+ if (r != 0) {
+ msg("cron audit problem. job failed (%s) for user %s",
+ e->cmd, e->u->name);
+ exit(1);
+ }
+
+ audit_cron_new_job(e->cmd, e->etype, (void *)e);
+
+ if (setuid(e->u->uid) == -1) {
+ msg("setuid failed (%s)", e->u->name);
+ clean_out_user(e->u);
+ exit(1);
+ }
+
+ if (e->etype == CRONEVENT) {
+ /* check for standard input to command */
+ if (e->of.ct.input != NULL) {
+ if ((tmpfile = strdup(TMPINFILE)) == NULL) {
+ mail((e->u)->name, MALLOCERR,
+ ERR_CANTEXECCRON);
+ exit(1);
+ }
+ if ((fd = mkstemp(tmpfile)) == -1 ||
+ (fptr = fdopen(fd, "w")) == NULL) {
+ mail((e->u)->name, NOSTDIN,
+ ERR_CANTEXECCRON);
+ cron_unlink(tmpfile);
+ free(tmpfile);
+ exit(1);
+ }
+ if ((fwrite(e->of.ct.input, sizeof (char),
+ strlen(e->of.ct.input), fptr)) !=
+ strlen(e->of.ct.input)) {
+ mail((e->u)->name, NOSTDIN, ERR_CANTEXECCRON);
+ cron_unlink(tmpfile);
+ free(tmpfile);
+ (void) close(fd);
+ (void) fclose(fptr);
+ exit(1);
+ }
+ if (fseek(fptr, (off_t)0, SEEK_SET) != -1) {
+ if (fd != 0) {
+ (void) dup2(fd, 0);
+ (void) close(fd);
+ }
+ }
+ cron_unlink(tmpfile);
+ free(tmpfile);
+ (void) fclose(fptr);
+ } else if ((fd = open("/dev/null", O_RDONLY)) > 0) {
+ (void) dup2(fd, 0);
+ (void) close(fd);
+ }
+ }
+
+ /* redirect stdout and stderr for the shell */
+ if ((fd = open(rp->outfile, O_WRONLY|O_CREAT|O_EXCL, OUTMODE)) == 1)
+ fd = open("/dev/null", O_WRONLY);
+
+ if (fd >= 0 && fd != 1)
+ (void) dup2(fd, 1);
+
+ if (fd >= 0 && fd != 2) {
+ (void) dup2(fd, 2);
+ if (fd != 1)
+ (void) close(fd);
+ }
+
+ (void) strlcat(homedir, (e->u)->home, sizeof (homedir));
+ (void) strlcat(logname, (e->u)->name, sizeof (logname));
+ environ = envinit;
+ if (chdir((e->u)->home) == -1) {
+ mail((e->u)->name, CANTCDHOME,
+ e->etype == CRONEVENT ? ERR_CANTEXECCRON :
+ ERR_CANTEXECAT);
+ exit(1);
+ }
+#ifdef TESTING
+ exit(1);
+#endif
+ /*
+ * make sure that all file descriptors EXCEPT 0, 1 and 2
+ * will be closed.
+ */
+ closefrom(3);
+
+ if ((e->u)->uid != 0)
+ (void) nice(qp->nice);
+ if (e->etype == CRONEVENT)
+ (void) execl(SHELL, "sh", "-c", e->cmd, 0);
+ else /* type == ATEVENT */
+ (void) execl(SHELL, "sh", 0);
+ mail((e->u)->name, CANTEXECSH,
+ e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT);
+ exit(1);
+ /*NOTREACHED*/
+}
+
+static int
+idle(long t)
+{
+ time_t now;
+
+ while (t > 0L) {
+
+ if (msg_wait(t) != 0) {
+ /* we need to run next job immediately */
+ return (0);
+ }
+
+ reap_child();
+
+ now = time(NULL);
+ if (last_time > now) {
+ /* clock has been reset */
+ return (1);
+ }
+
+ if (next_event == NULL && !el_empty()) {
+ next_event = (struct event *)el_first();
+ }
+ if (next_event == NULL)
+ t = INFINITY;
+ else
+ t = (long)next_event->time - now;
+ }
+ return (0);
+}
+
+/*
+ * This used to be in the idle(), but moved to the separate function.
+ * This called from various place when cron needs to reap the
+ * child. It includes the situation that cron hit maxrun, and needs
+ * to reschedule the job.
+ */
+static void
+reap_child()
+{
+ pid_t pid;
+ int prc;
+ struct runinfo *rp;
+
+ for (;;) {
+ pid = waitpid((pid_t)-1, &prc, WNOHANG);
+ if (pid <= 0)
+ break;
+#ifdef DEBUG
+ fprintf(stderr,
+ "wait returned %x for process %d\n", prc, pid);
+#endif
+ if ((rp = rinfo_get(pid)) == NULL) {
+ if (miscpid_delete(pid) == 0) {
+ /* not found in anywhere */
+ msg(PIDERR, pid);
+ }
+ } else if (rp->que == ZOMB) {
+ (void) unlink(rp->outfile);
+ rinfo_free(rp);
+ } else {
+ cleanup(rp, prc);
+ }
+ }
+}
+
+static void
+cleanup(struct runinfo *pr, int rc)
+{
+ int nextfork = 1;
+ struct usr *p;
+ struct stat buf;
+
+ logit(ECHAR, pr, rc);
+ --qt[pr->que].nrun;
+ p = pr->rusr;
+ if (pr->que != CRONEVENT)
+ --p->aruncnt;
+ else
+ --p->cruncnt;
+
+ if (!lstat(pr->outfile, &buf)) {
+ if ((buf.st_mode != S_IFLNK) &&
+ (buf.st_size > 0 || pr->mailwhendone)) {
+ /* mail user stdout and stderr */
+ for (;;) {
+ if ((pr->pid = fork()) < 0) {
+ /*
+ * if fork fails try forever in doubling
+ * retry times, up to 16 seconds
+ */
+ (void) sleep(nextfork);
+ if (nextfork < 16)
+ nextfork += nextfork;
+ continue;
+ } else if (pr->pid == 0) {
+ child_sigreset();
+ contract_clear_template();
+
+ mail_result(p, pr, buf.st_size);
+ /* NOTREACHED */
+ } else {
+ contract_abandon_latest(pr->pid);
+ pr->que = ZOMB;
+ break;
+ }
+ }
+ } else {
+ (void) unlink(pr->outfile);
+ rinfo_free(pr);
+ }
+ } else {
+ rinfo_free(pr);
+ }
+
+ free_if_unused(p);
+}
+
+/*
+ * Mail stdout and stderr of a job to user. Get uid for real user and become
+ * that person. We do this so that mail won't come from root since this
+ * could be a security hole. If failure, quit - don't send mail as root.
+ */
+static void
+mail_result(struct usr *p, struct runinfo *pr, size_t filesize)
+{
+ struct passwd *ruser_ids;
+ FILE *mailpipe;
+ FILE *st;
+ struct utsname name;
+ int nbytes;
+ char iobuf[BUFSIZ];
+ char *cmd;
+
+ (void) uname(&name);
+ if ((ruser_ids = getpwnam(p->name)) == NULL)
+ exit(0);
+ (void) setuid(ruser_ids->pw_uid);
+
+ cmd = xmalloc(strlen(MAIL)+strlen(p->name)+2);
+ (void) sprintf(cmd, "%s %s", MAIL, p->name);
+ mailpipe = popen(cmd, "w");
+ contract_abandon_latest(0);
+ free(cmd);
+ if (mailpipe == NULL)
+ exit(127);
+ (void) fprintf(mailpipe, "To: %s\n", p->name);
+ if (pr->jobtype == CRONEVENT) {
+ (void) fprintf(mailpipe, CRONOUT);
+ (void) fprintf(mailpipe, "Your \"cron\" job on %s\n",
+ name.nodename);
+ if (pr->jobname != NULL) {
+ (void) fprintf(mailpipe, "%s\n\n", pr->jobname);
+ }
+ } else {
+ (void) fprintf(mailpipe, "Subject: Output from \"at\" job\n\n");
+ (void) fprintf(mailpipe, "Your \"at\" job on %s\n",
+ name.nodename);
+ if (pr->jobname != NULL) {
+ (void) fprintf(mailpipe, "\"%s\"\n\n", pr->jobname);
+ }
+ }
+ /* Tmp. file is fopen'ed w/ "r", secure open */
+ if (filesize > 0 &&
+ (st = fopen(pr->outfile, "r")) != NULL) {
+ (void) fprintf(mailpipe,
+ "produced the following output:\n\n");
+ while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0)
+ (void) fwrite(iobuf, sizeof (char), nbytes, mailpipe);
+ (void) fclose(st);
+ } else {
+ (void) fprintf(mailpipe, "completed.\n");
+ }
+ (void) pclose(mailpipe);
+ exit(0);
+}
+
+static int
+msg_wait(long tim)
+{
+ struct message msg;
+ int cnt;
+ time_t reftime;
+ struct pollfd pfd[2];
+ int64_t tl;
+ int timeout;
+ static int pending_msg;
+ static time_t pending_reftime;
+
+ if (pending_msg) {
+ process_msg(&msgbuf, pending_reftime);
+ pending_msg = 0;
+ return (0);
+ }
+
+ /*
+ * We are opening the signal mask to receive SIGCLD. The notifypipe
+ * is used to avoid race condition between SIGCLD and poll system
+ * call.
+ * If SIGCLD is delivered in poll(), poll will be interrupted, and
+ * we will return to idle() to reap the dead children.
+ * If SIGCLD is delivered between sigprocmask() below and poll(),
+ * there is no way we can detect the SIGCLD because poll() won't
+ * be interrupted. In such case, the dead children can't be wait'ed
+ * until poll returns by timeout or a new job. To avoid this race
+ * condition, child_handler write to the notifypipe, so that
+ * poll() will be able to return with POLLIN which indicates that
+ * we have received SIGCLD.
+ *
+ * Since the notifypipe is used to just let poll return from
+ * system call, the data in the pipe won't be read. Therefore,
+ * any data in the pipe needs to be flushed before opening signal
+ * mask.
+ *
+ * Note that we can probably re-write this code with pselect()
+ * which can handle this situation easily.
+ */
+ (void) ioctl(notifypipe[0], I_FLUSH, FLUSHW);
+
+ pfd[0].fd = msgfd;
+ pfd[0].events = POLLIN;
+ pfd[1].fd = notifypipe[1];
+ pfd[1].events = POLLIN;
+
+#ifdef CRON_MAXSLEEP
+ /*
+ * CRON_MAXSLEEP can be defined to have cron periodically wake
+ * up, so that cron can detect a change of TOD and adjust the
+ * sleep time accordingly.
+ */
+ tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim;
+#endif
+ tl = (tim == INFINITY) ? -1ll : (int64_t)tim * 1000;
+
+ accept_sigcld = 1;
+ (void) sigprocmask(SIG_UNBLOCK, &childmask, NULL);
+ do {
+ timeout = (tl > INT_MAX ? INT_MAX : (int)tl);
+ tl -= timeout;
+ cnt = poll(pfd, 2, timeout);
+ if (cnt == -1 && errno != EINTR) {
+ perror("! poll");
+ }
+ } while (tl > 0 && cnt == 0);
+ (void) sigprocmask(SIG_BLOCK, &childmask, NULL);
+ accept_sigcld = 0;
+
+ /*
+ * poll timeout or interrupted.
+ */
+ if (cnt <= 0)
+ return (0);
+
+ /*
+ * Not the timeout or new job, but a SIGCLD has been delivered.
+ */
+ if ((pfd[0].revents & POLLIN) == 0)
+ return (0);
+
+ errno = 0;
+ if ((cnt = read(msgfd, &msg, sizeof (msg))) != sizeof (msg)) {
+ if (cnt != -1 || errno != EAGAIN)
+ perror("! read");
+ return (0);
+ }
+ reftime = time(NULL);
+ if (next_event != NULL && reftime >= next_event->time) {
+ /*
+ * we need to run the job before reloading crontab.
+ */
+ (void) memcpy(&msgbuf, &msg, sizeof (msg));
+ pending_msg = 1;
+ pending_reftime = reftime;
+ return (1);
+ }
+ process_msg(&msg, reftime);
+ return (0);
+}
+
+/*
+ * process the message supplied via pipe. This will be called either
+ * immediately after cron read the message from pipe, or idle time
+ * if the message was pending due to the job execution.
+ */
+static void
+process_msg(struct message *pmsg, time_t reftime)
+{
+ if (pmsg->etype == NULL)
+ return;
+
+ switch (pmsg->etype) {
+ case AT:
+ if (pmsg->action == DELETE)
+ del_atjob(pmsg->fname, pmsg->logname);
+ else
+ mod_atjob(pmsg->fname, (time_t)0);
+ break;
+ case CRON:
+ if (pmsg->action == DELETE)
+ del_ctab(pmsg->fname);
+ else
+ mod_ctab(pmsg->fname, reftime);
+ break;
+ default:
+ msg("message received - bad format");
+ 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;
+ }
+ (void) fflush(stdout);
+ pmsg->etype = NULL;
+}
+
+static struct runinfo *
+rinfo_get(pid_t pid)
+{
+ struct runinfo *rp;
+
+ for (rp = rt; rp < rt+MAXRUN; rp++) {
+ if (rp->pid == pid)
+ break;
+ }
+ if (rp >= rt+MAXRUN)
+ return (NULL);
+ else
+ return (rp);
+}
+
+/*
+ * Free memory used for output file name and job name in runinfo structure
+ */
+static void
+rinfo_free(struct runinfo *rp)
+{
+ free(rp->outfile);
+ free(rp->jobname);
+ rp->outfile = rp->jobname = NULL;
+ rp->pid = 0;
+}
+
+/* ARGSUSED */
+static void
+thaw_handler(int sig)
+{
+ ;
+}
+
+
+/* ARGSUSED */
+static void
+cronend(int sig)
+{
+ crabort("SIGTERM", REMOVE_FIFO);
+}
+
+/*ARGSUSED*/
+static void
+child_handler(int sig)
+{
+ /*
+ * Just in case someone changes the signal mask.
+ * we don't want to notify the SIGCLD.
+ */
+ if (accept_sigcld) {
+ (void) write(notifypipe[0], &sig, 1);
+ }
+}
+
+static void
+child_sigreset(void)
+{
+ (void) signal(SIGCLD, SIG_DFL);
+ (void) sigprocmask(SIG_SETMASK, &defmask, NULL);
+}
+
+/*
+ * crabort() - handle exits out of cron
+ */
+static void
+crabort(char *mssg, int action)
+{
+ int c;
+
+ if (action & REMOVE_FIFO) {
+ /* FIFO vanishes when cron finishes */
+ if (unlink(FIFO) < 0)
+ perror("cron could not unlink FIFO");
+ }
+
+ if (action & CONSOLE_MSG) {
+ /* write error msg to console */
+ if ((c = open(CONSOLE, O_WRONLY)) >= 0) {
+ (void) write(c, "cron aborted: ", 14);
+ (void) write(c, mssg, strlen(mssg));
+ (void) write(c, "\n", 1);
+ (void) close(c);
+ }
+ }
+
+ /* always log the message */
+ msg(mssg);
+ msg("******* CRON ABORTED ********");
+ exit(1);
+}
+
+/*
+ * msg() - time-stamped error reporting function
+ */
+/*PRINTFLIKE1*/
+static void
+msg(char *fmt, ...)
+{
+ va_list args;
+ time_t t;
+
+ t = time(NULL);
+
+ (void) fflush(stdout);
+
+ (void) fprintf(stderr, "! ");
+
+ va_start(args, fmt);
+ (void) vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
+ (void) fprintf(stderr, " %s\n", timebuf);
+
+ (void) fflush(stderr);
+}
+
+static void
+logit(int cc, struct runinfo *rp, int rc)
+{
+ time_t t;
+ int ret;
+
+ if (!log)
+ return;
+
+ t = time(NULL);
+ if (cc == BCHAR)
+ (void) printf("%c CMD: %s\n", cc, next_event->cmd);
+ (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
+ (void) printf("%c %.8s %u %c %s",
+ cc, (rp->rusr)->name, rp->pid, QUE(rp->que), timebuf);
+ if ((ret = TSTAT(rc)) != 0)
+ (void) printf(" ts=%d", ret);
+ if ((ret = RCODE(rc)) != 0)
+ (void) printf(" rc=%d", ret);
+ (void) putchar('\n');
+ (void) fflush(stdout);
+}
+
+static 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, (time_t)0);
+ 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);
+ msg("rescheduling at job");
+}
+
+static void
+quedefs(int action)
+{
+ int i;
+ int j;
+ char qbuf[QBUFSIZ];
+ FILE *fd;
+
+ /* 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 ((fd = fopen(QUEDEFS, "r")) == NULL) {
+ msg("cannot open quedefs file");
+ msg("using default queue definitions");
+ return;
+ }
+ while (fgets(qbuf, QBUFSIZ, fd) != NULL) {
+ if ((j = qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.')
+ continue;
+ parsqdef(&qbuf[2]);
+ qt[j].njob = qq.njob;
+ qt[j].nice = qq.nice;
+ qt[j].nwait = qq.nwait;
+ }
+ (void) fclose(fd);
+}
+
+static void
+parsqdef(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;
+ }
+ }
+}
+
+/*
+ * defaults - read defaults from /etc/default/cron
+ */
+static void
+defaults()
+{
+ int flags;
+ char *deflog;
+ char *hz, *tz;
+
+ /*
+ * get HZ value for environment
+ */
+ if ((hz = getenv("HZ")) == (char *)NULL)
+ (void) sprintf(hzname, "HZ=%d", HZ);
+ else
+ (void) snprintf(hzname, sizeof (hzname), "HZ=%s", hz);
+ /*
+ * get TZ value for environment
+ */
+ (void) snprintf(tzone, sizeof (tzone), "TZ=%s",
+ ((tz = getenv("TZ")) != NULL) ? tz : DEFTZ);
+
+ if (defopen(DEFFILE) == 0) {
+ /* ignore case */
+ flags = defcntl(DC_GETFLAGS, 0);
+ TURNOFF(flags, DC_CASE);
+ (void) defcntl(DC_SETFLAGS, flags);
+
+ if (((deflog = defread("CRONLOG=")) == NULL) ||
+ (*deflog == 'N') || (*deflog == 'n'))
+ log = 0;
+ else
+ log = 1;
+ /* fix for 1087611 - allow paths to be set in defaults file */
+ if ((Def_path = defread("PATH=")) != NULL) {
+ (void) strlcat(path, Def_path, LINE_MAX);
+ } else {
+ (void) strlcpy(path, NONROOTPATH, LINE_MAX);
+ }
+ if ((Def_supath = defread("SUPATH=")) != NULL) {
+ (void) strlcat(supath, Def_supath, LINE_MAX);
+ } else {
+ (void) strlcpy(supath, ROOTPATH, LINE_MAX);
+ }
+ (void) defopen(NULL);
+ }
+}
+
+/*
+ * Determine if a user entry for a job is still ok. The method used here
+ * is a lot (about 75x) faster than using setgrent() / getgrent()
+ * endgrent(). It should be safe because we use the sysconf to determine
+ * the max, and it tolerates the max being 0.
+ */
+
+static int
+verify_user_cred(const struct usr *u)
+{
+ struct passwd *pw;
+ size_t numUsrGrps = 0;
+ size_t numOrigGrps = 0;
+ size_t i;
+ int retval;
+
+ /*
+ * Maximum number of groups a user may be in concurrently. This
+ * is a value which we obtain at runtime through a sysconf()
+ * call.
+ */
+
+ static size_t nGroupsMax = (size_t)-1;
+
+ /*
+ * Arrays for cron user's group list, constructed at startup to
+ * be nGroupsMax elements long, used for verifying user
+ * credentials prior to execution.
+ */
+
+ static gid_t *UsrGrps;
+ static gid_t *OrigGrps;
+
+ if (((pw = getpwnam(u->name)) == NULL) ||
+ (pw->pw_uid != u->uid)) {
+ return (VUC_BADUSER);
+ }
+
+ /*
+ * Create the group id lists needed for job credential
+ * verification.
+ */
+
+ if (nGroupsMax == (size_t)-1) {
+ if ((nGroupsMax = sysconf(_SC_NGROUPS_MAX)) > 0) {
+ UsrGrps = xcalloc(nGroupsMax, sizeof (gid_t));
+ OrigGrps = xcalloc(nGroupsMax, sizeof (gid_t));
+ }
+
+#ifdef DEBUG
+ (void) fprintf(stderr, "nGroupsMax = %ld\n", nGroupsMax);
+#endif
+ }
+
+#ifdef DEBUG
+ (void) fprintf(stderr, "verify_user_cred (%s-%d)\n", pw->pw_name,
+ pw->pw_uid);
+ (void) fprintf(stderr, "verify_user_cred: pw->pw_gid = %d, "
+ "u->gid = %d\n", pw->pw_gid, u->gid);
+#endif
+
+ retval = (u->gid == pw->pw_gid) ? VUC_OK : VUC_NOTINGROUP;
+
+ if (nGroupsMax > 0) {
+ numOrigGrps = getgroups(nGroupsMax, OrigGrps);
+
+ (void) initgroups(pw->pw_name, pw->pw_gid);
+ numUsrGrps = getgroups(nGroupsMax, UsrGrps);
+
+ for (i = 0; i < numUsrGrps; i++) {
+ if (UsrGrps[i] == u->gid) {
+ retval = VUC_OK;
+ break;
+ }
+ }
+
+ if (OrigGrps) {
+ (void) setgroups(numOrigGrps, OrigGrps);
+ }
+ }
+
+#ifdef DEBUG
+ (void) fprintf(stderr, "verify_user_cred: VUC = %d\n", retval);
+#endif
+
+ return (retval);
+}
+
+static int
+set_user_cred(const struct usr *u, struct project *pproj)
+{
+ static char *progname = "cron";
+ int r = 0, rval = 0;
+
+ if ((r = pam_start(progname, u->name, &pam_conv, &pamh))
+ != PAM_SUCCESS) {
+#ifdef DEBUG
+ msg("pam_start returns %d\n", r);
+#endif
+ rval = VUC_BADUSER;
+ goto set_eser_cred_exit;
+ }
+
+ r = pam_acct_mgmt(pamh, 0);
+#ifdef DEBUG
+ msg("pam_acc_mgmt returns %d\n", r);
+#endif
+ if (r == PAM_ACCT_EXPIRED) {
+ rval = VUC_EXPIRED;
+ goto set_eser_cred_exit;
+ }
+ if (r == PAM_NEW_AUTHTOK_REQD) {
+ rval = VUC_NEW_AUTH;
+ goto set_eser_cred_exit;
+ }
+ if (r != PAM_SUCCESS) {
+ rval = VUC_BADUSER;
+ goto set_eser_cred_exit;
+ }
+
+ if (pproj != NULL) {
+ size_t sz = sizeof (PROJECT) + strlen(pproj->pj_name);
+ char *buf = alloca(sz);
+
+ (void) snprintf(buf, sz, PROJECT "%s", pproj->pj_name);
+ (void) pam_set_item(pamh, PAM_RESOURCE, buf);
+ }
+
+ r = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+ if (r != PAM_SUCCESS)
+ rval = VUC_BADUSER;
+
+set_eser_cred_exit:
+ (void) pam_end(pamh, r);
+ return (rval);
+}
+
+static void
+clean_out_user(struct usr *u)
+{
+ if (next_event->u == u) {
+ next_event = NULL;
+ }
+
+ clean_out_ctab(u);
+ clean_out_atjobs(u);
+ free_if_unused(u);
+}
+
+static void
+clean_out_atjobs(struct usr *u)
+{
+ struct event *ev, *pv;
+
+ for (pv = NULL, ev = u->atevents;
+ ev != NULL;
+ pv = ev, ev = ev->link, free(pv)) {
+ el_remove(ev->of.at.eventid, 1);
+ if (cwd == AT)
+ cron_unlink(ev->cmd);
+ else {
+ char buf[PATH_MAX];
+ if (strlen(ATDIR) + strlen(ev->cmd) + 2
+ < PATH_MAX) {
+ (void) sprintf(buf, "%s/%s", ATDIR, ev->cmd);
+ cron_unlink(buf);
+ }
+ }
+ free(ev->cmd);
+ }
+
+ u->atevents = NULL;
+}
+
+static void
+clean_out_ctab(struct usr *u)
+{
+ rm_ctevents(u);
+ el_remove(u->ctid, 0);
+ u->ctid = 0;
+ u->ctexists = 0;
+}
+
+static void
+cron_unlink(char *name)
+{
+ int r;
+
+ r = unlink(name);
+ if (r == 0 || (r == -1 && errno == ENOENT)) {
+ (void) audit_cron_delete_anc_file(name, NULL);
+ }
+}
+
+static void
+create_anc_ctab(struct event *e)
+{
+ if (audit_cron_create_anc_file(e->u->name,
+ (cwd == CRON) ? NULL:CRONDIR,
+ e->u->name,
+ e->u->uid) == -1) {
+ process_anc_files(CRON_ANC_DELETE);
+ crabort("cannot create ancillary files for crontabs",
+ REMOVE_FIFO|CONSOLE_MSG);
+ }
+}
+
+static void
+delete_anc_ctab(struct event *e)
+{
+ (void) audit_cron_delete_anc_file(e->u->name,
+ (cwd == CRON) ? NULL:CRONDIR);
+}
+
+static void
+create_anc_atjob(struct event *e)
+{
+ if (!e->of.at.exists)
+ return;
+
+ if (audit_cron_create_anc_file(e->cmd,
+ (cwd == AT) ? NULL:ATDIR,
+ e->u->name,
+ e->u->uid) == -1) {
+ process_anc_files(CRON_ANC_DELETE);
+ crabort("cannot create ancillary files for atjobs",
+ REMOVE_FIFO|CONSOLE_MSG);
+ }
+}
+
+static void
+delete_anc_atjob(struct event *e)
+{
+ if (!e->of.at.exists)
+ return;
+
+ (void) audit_cron_delete_anc_file(e->cmd,
+ (cwd == AT) ? NULL:ATDIR);
+}
+
+
+static void
+process_anc_files(int del)
+{
+ struct usr *u = uhead;
+ struct event *e;
+
+ if (!audit_cron_mode())
+ return;
+
+ for (;;) {
+ if (u->ctexists && u->ctevents != NULL) {
+ e = u->ctevents;
+ for (;;) {
+ if (del)
+ delete_anc_ctab(e);
+ else
+ create_anc_ctab(e);
+ if ((e = e->link) == NULL)
+ break;
+ }
+ }
+
+ if (u->atevents != NULL) {
+ e = u->atevents;
+ for (;;) {
+ if (del)
+ delete_anc_atjob(e);
+ else
+ create_anc_atjob(e);
+ if ((e = e->link) == NULL)
+ break;
+ }
+ }
+
+ if ((u = u->nextusr) == NULL)
+ break;
+ }
+}
+
+/*ARGSUSED*/
+static int
+cron_conv(int num_msg, struct pam_message **msgs,
+ struct pam_response **response, void *appdata_ptr)
+{
+ struct pam_message **m = msgs;
+ int i;
+
+ for (i = 0; i < num_msg; i++) {
+ switch (m[i]->msg_style) {
+ case PAM_ERROR_MSG:
+ case PAM_TEXT_INFO:
+ if (m[i]->msg != NULL) {
+ (void) msg("%s\n", m[i]->msg);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ return (0);
+}
+
+/*
+ * Cron creates process for other than job. Mail process is the
+ * one which rinfo does not cover. Therefore, miscpid will keep
+ * track of the pids executed from cron. Otherwise, we will see
+ * "unexpected pid returned.." messages appear in the log file.
+ */
+static void
+miscpid_insert(pid_t pid)
+{
+ struct miscpid *mp;
+
+ mp = xmalloc(sizeof (*mp));
+ mp->pid = pid;
+ mp->next = miscpid_head;
+ miscpid_head = mp;
+}
+
+static int
+miscpid_delete(pid_t pid)
+{
+ struct miscpid *mp, *omp;
+ int found = 0;
+
+ omp = NULL;
+ for (mp = miscpid_head; mp != NULL; mp = mp->next) {
+ if (mp->pid == pid) {
+ found = 1;
+ break;
+ }
+ omp = mp;
+ }
+ if (found) {
+ if (omp != NULL)
+ omp->next = mp->next;
+ else
+ miscpid_head = NULL;
+ free(mp);
+ }
+ return (found);
+}
+
+/*
+ * Establish contract terms such that all children are in abandoned
+ * process contracts.
+ */
+static int
+contract_set_template()
+{
+ int fd;
+
+ if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
+ crabort("cannot open process contract template",
+ REMOVE_FIFO | CONSOLE_MSG);
+
+ if (ct_pr_tmpl_set_param(fd, 0) ||
+ ct_tmpl_set_informative(fd, 0) ||
+ ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR))
+ crabort("cannot establish contract template terms",
+ REMOVE_FIFO | CONSOLE_MSG);
+
+ if (ct_tmpl_activate(fd))
+ crabort("cannot activate contract template",
+ REMOVE_FIFO | CONSOLE_MSG);
+
+ (void) close(fd);
+}
+
+/*
+ * Clear active process contract template.
+ */
+static int
+contract_clear_template()
+{
+ int fd;
+
+ if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
+ crabort("cannot open process contract template",
+ REMOVE_FIFO | CONSOLE_MSG);
+
+ if (ct_tmpl_clear(fd))
+ crabort("cannot clear contract template",
+ REMOVE_FIFO | CONSOLE_MSG);
+
+ (void) close(fd);
+}
+
+/*
+ * Abandon latest process contract unconditionally. If we have leaked [some
+ * critical amount], exit such that the kernel reaps our contracts.
+ */
+static void
+contract_abandon_latest(pid_t pid)
+{
+ int r;
+ ctid_t id;
+ static uint_t cts_lost;
+
+ if (cts_lost > MAX_LOST_CONTRACTS)
+ crabort("repeated failure to abandon contracts",
+ REMOVE_FIFO | CONSOLE_MSG);
+
+ if (r = contract_latest(&id)) {
+ if (pid == 0)
+ msg("could not obtain latest contract from "
+ "popen(3C): %s", strerror(r));
+ else
+ msg("could not obtain latest contract for "
+ "PID %ld: %s", pid, strerror(r));
+ cts_lost++;
+ return;
+ }
+
+ if (r = contract_abandon_id(id)) {
+ msg("could not abandon latest contract %ld: %s", id,
+ strerror(r));
+ cts_lost++;
+ return;
+ }
+}