OpenSolaris_b135/cmd/cron/cron.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2009 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	*/

#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 <libzoneinfo.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	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 or at-job file. Resubmit it."
#define	BADPROJID	"can't set project id for your job."
#define	CANTCDHOME	"can't change directory to %s.\
\nYour commands will not be executed."
#define	CANTEXECSH	"unable to exec the shell, %s, for one of your \
commands."
#define	CANT_STR_LEN (sizeof (CANTEXECSH) > sizeof (CANTCDHOME) ? \
	sizeof (CANTEXECSH) : sizeof (CANTCDHOME))
#define	NOREAD		"can't read your crontab file.  Resubmit it."
#define	BADTYPE		"crontab or at-job file 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	ERR_NOTREG	4	/* error not a regular file */

#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];

static struct message msgbuf;

struct shared {
	int count;			/* usage count */
	void (*free)(void *obj);	/* routine that will free obj */
	void *obj;			/* object */
};

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	*/
			struct shared *tz;	/* timezone of this event */
			struct shared *home;	/* directory for this event */
			struct shared *shell;	/* shell for this event */
		} 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;

static 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 */
	struct runinfo *next;
}	*rthead;

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]	= ENV_HOME;
static char logname[LINE_MAX]	= "LOGNAME=";
static char tzone[LINE_MAX]	= ENV_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 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 ignore_msg(char *, char *, struct event *);
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(int);
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 void contract_set_template(void);
static void 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 *);

static struct shared *create_shared_str(char *str);
static struct shared *dup_shared(struct shared *obj);
static void rel_shared(struct shared *obj);
static void *get_obj(struct shared *obj);
/*
 * 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 reset_needed; /* set to 1 when cron(1M) needs to re-initialize */

static int		refresh;
static sigset_t		defmask, sigmask;

/*
 * 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(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 int 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);

static int valid_entry(char *, int);
static struct usr *create_ulist(char *, int);
static void init_cronevent(char *, int);
static void init_atevent(char *, time_t, int, int);
static void update_atevent(struct usr *, char *, time_t, int);

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_needed is set to 1 whenever el_add() finds out that a cron
	 * job is scheduled to be run before the time when cron(1M) daemon
	 * initialized.
	 * Other cases where a reset is needed is when ex() finds that the
	 * event to be executed is being run at the wrong time, or when idle()
	 * determines that time was reset.
	 * We immediately return to the top of the while (TRUE) loop in
	 * main() where the event list is cleared and rebuilt, and reset_needed
	 * is set back to 0.
	 */
	reset_needed = 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);

	/* setup THAW handler */
	act.sa_handler = thaw_handler;
	act.sa_flags = 0;
	(void) sigemptyset(&act.sa_mask);
	(void) sigaction(SIGTHAW, &act, NULL);

	/* setup CHLD handler */
	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(&defmask);
	(void) sigemptyset(&sigmask);
	(void) sigaddset(&sigmask, SIGCLD);
	(void) sigaddset(&sigmask, SIGTHAW);
	(void) sigprocmask(SIG_BLOCK, &sigmask, NULL);

	t_old = init_time;
	last_time = t_old;
	for (;;) {		/* MAIN LOOP */
		t = time(NULL);
		if ((t_old > t) || (t-last_time > CUSHION) || reset_needed) {
			reset_needed = 0;
			/*
			 * the time was set backwards or forward or
			 * refresh is requested.
			 */
			if (refresh)
				msg("re-scheduling jobs");
			else
				msg("time was reset, re-initializing");
			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;
			/*
			 * reset_needed might have been set in the functions
			 * call path from initialize()
			 */
			if (reset_needed) {
				continue;
			}
		}
		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) {
			/*
			 * reset_needed may be set in the functions call path
			 * from idle()
			 */
			if (idle(ne_time) || reset_needed) {
				reset_needed = 1;
				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 */

		/*
		 * reset_needed may be set in the functions call path
		 * from ex()
		 */
		if (ex(next_event) || reset_needed) {
			reset_needed = 1;
			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

			switch (el_add(next_event, next_event->time,
			    (next_event->u)->ctid)) {
			case -1:
				ignore_msg("main", "cron", next_event);
				break;
			case -2: /* event time lower than init time */
				reset_needed = 1;
				break;
			}
			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
	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);
	}

	init_time = time(NULL);
	el_init(8, init_time, (time_t)(60*60*24), 10);

	init_time = time(NULL);
	el_init(8, init_time, (time_t)(60*60*24), 10);

	/*
	 * 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(firstpass);
	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(int first)
{
	DIR		*dir;
	struct dirent	*dp;
	char		*ptr;
	int		jobtype;
	time_t		tim;


	if (chdir(CRONDIR) == -1)
		crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG);
	cwd = CRON;
	if ((dir = opendir(".")) == NULL)
		crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG);
	while ((dp = readdir(dir)) != NULL) {
		if (!valid_entry(dp->d_name, CRONEVENT))
			continue;
		init_cronevent(dp->d_name, first);
	}
	(void) closedir(dir);

	if (chdir(ATDIR) == -1) {
		msg("cannot chdir to at directory");
		return;
	}
	if ((dir = opendir(".")) == NULL) {
		msg("cannot read at at directory");
		return;
	}
	cwd = AT;
	while ((dp = readdir(dir)) != NULL) {
		if (!valid_entry(dp->d_name, ATEVENT))
			continue;
		ptr = dp->d_name;
		if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
			continue;
		ptr++;
		if (!isalpha(*ptr))
			continue;
		jobtype = *ptr - 'a';
		if (jobtype >= NQUEUE) {
			cron_unlink(dp->d_name);
			continue;
		}
		init_atevent(dp->d_name, tim, jobtype, first);
	}
	(void) closedir(dir);
}

static int
valid_entry(char *name, int type)
{
	struct stat	buf;

	if (strcmp(name, ".") == 0 ||
	    strcmp(name, "..") == 0)
		return (0);

	/* skip over ancillary file names */
	if (audit_cron_is_anc_name(name))
		return (0);

	if (stat(name, &buf)) {
		mail(name, BADSTAT, ERR_UNIXERR);
		cron_unlink(name);
		return (0);
	}
	if (!S_ISREG(buf.st_mode)) {
		mail(name, BADTYPE, ERR_NOTREG);
		cron_unlink(name);
		return (0);
	}
	if (type == ATEVENT) {
		if (!(buf.st_mode & ISUID)) {
			cron_unlink(name);
			return (0);
		}
	}
	return (1);
}

struct usr *
create_ulist(char *name, int type)
{
	struct usr	*u;

	u = xcalloc(1, sizeof (struct usr));
	u->name = xstrdup(name);
	if (type == CRONEVENT) {
		u->ctexists = TRUE;
		u->ctid = ecid++;
	} else {
		u->ctexists = FALSE;
		u->ctid = 0;
	}
	u->uid = (uid_t)-1;
	u->gid = (uid_t)-1;
	u->nextusr = uhead;
	uhead = u;
	return (u);
}

void
init_cronevent(char *name, int first)
{
	struct usr	*u;

	if (first) {
		u = create_ulist(name, CRONEVENT);
		readcron(u, 0);
	} else {
		if ((u = find_usr(name)) == NULL) {
			u = create_ulist(name, CRONEVENT);
			readcron(u, 0);
		} else {
			u->ctexists = TRUE;
			rm_ctevents(u);
			el_remove(u->ctid, 0);
			readcron(u, 0);
		}
	}
}

void
init_atevent(char *name, time_t tim, int jobtype, int first)
{
	struct usr	*u;

	if (first) {
		u = create_ulist(name, ATEVENT);
		add_atevent(u, name, tim, jobtype);
	} else {
		if ((u = find_usr(name)) == NULL) {
			u = create_ulist(name, ATEVENT);
			add_atevent(u, name, tim, jobtype);
		} else {
			update_atevent(u, name, tim, jobtype);
		}
	}
}

static void
mod_ctab(char *name, time_t reftime)
{
	struct	passwd	*pw;
	struct	stat	buf;
	struct	usr	*u;
	char	namebuf[LINE_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 = create_ulist(name, CRONEVENT);
		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;
		readcron(u, reftime);
	} else {
		u->uid = pw->pw_uid;
		u->gid = pw->pw_gid;
		if (u->home != NULL) {
			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);
			}
		} else {
			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;
	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 = create_ulist(pw->pw_name, ATEVENT);
		u->home = xstrdup(pw->pw_dir);
		u->uid = pw->pw_uid;
		u->gid = pw->pw_gid;
		add_atevent(u, name, tim, jobtype);
	} else {
		u->uid = pw->pw_uid;
		u->gid = pw->pw_gid;
		free(u->home);
		u->home = xstrdup(pw->pw_dir);
		update_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
	if (el_add(e, e->time, e->of.at.eventid) < 0) {
		ignore_msg("add_atevent", "at", e);
	}
}

void
update_atevent(struct usr *u, char *name, time_t tim, int jobtype)
{
	struct event *e;

	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 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;
	struct shared *tz = NULL;
	struct shared *home = NULL;
	struct shared *shell = NULL;
	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) {
		char *tmp;
		/* 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;

		if (strncmp(&line[cursor], ENV_TZ,
		    strlen(ENV_TZ)) == 0) {
			if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
				*tmp = NULL;
			}

			if (!isvalid_tz(&line[cursor + strlen(ENV_TZ)], NULL,
			    _VTZ_ALL)) {
				cte_add(lineno, line);
				break;
			}
			if (tz == NULL || strcmp(&line[cursor], get_obj(tz))) {
				rel_shared(tz);
				tz = create_shared_str(&line[cursor]);
			}
			continue;
		}

		if (strncmp(&line[cursor], ENV_HOME,
		    strlen(ENV_HOME)) == 0) {
			if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
				*tmp = NULL;
			}
			if (home == NULL ||
			    strcmp(&line[cursor], get_obj(home))) {
				rel_shared(home);
				home = create_shared_str(
				    &line[cursor + strlen(ENV_HOME)]);
			}
			continue;
		}

		if (strncmp(&line[cursor], ENV_SHELL,
		    strlen(ENV_SHELL)) == 0) {
			if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
				*tmp = NULL;
			}
			if (shell == NULL ||
			    strcmp(&line[cursor], get_obj(shell))) {
				rel_shared(shell);
				shell = create_shared_str(&line[cursor]);
			}
			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;
		}
		/* set the timezone of this entry */
		e->of.ct.tz = dup_shared(tz);
		/* set the shell of this entry */
		e->of.ct.shell = dup_shared(shell);
		/* set the home of this entry */
		e->of.ct.home = dup_shared(home);
		/* 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	*/
		switch (el_add(e, e->time, u->ctid)) {
		case -1:
			ignore_msg("readcron", "cron", e);
			break;
		case -2: /* event time lower than init time */
			reset_needed = 1;
			break;
		}
		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);
	rel_shared(tz);
	rel_shared(shell);
	rel_shared(home);
}

/*
 * 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
tz_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;
	int d1, day1, carry1, d2, day2, carry2, daysahead, mon, yr, db, wd;
	int 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*/
}

static time_t
next_time(struct event *e, time_t tflag)
{
	if (e->of.ct.tz != NULL) {
		time_t ret;

		(void) putenv((char *)get_obj(e->of.ct.tz));
		tzset();
		ret = tz_next_time(e, tflag);
		(void) putenv(tzone);
		tzset();
		return (ret);
	} else {
		return (tz_next_time(e, tflag));
	}
}

/*
 * 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);
		rel_shared(e2->of.ct.tz);
		rel_shared(e2->of.ct.shell);
		rel_shared(e2->of.ct.home);
		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;
	union {
		struct {
			char buf[PROJECT_BUFSZ];
			char buf2[PROJECT_BUFSZ];
		} p;
		char error[CANT_STR_LEN + PATH_MAX];
	} bufs;
	char *tmpfile;
	FILE *fptr;
	time_t dhltime;
	projid_t projid;
	int projflag = 0;
	char *home;
	char *sh;

	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);
	}

	rp = rinfo_get(0); /* allocating a new runinfo struct */

	/*
	 * 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);
			switch (el_add(next_event, next_event->time,
			    (next_event->u)->ctid)) {
			case -1:
				ignore_msg("ex", "cron", next_event);
				break;
			case -2: /* event time lower than init time */
				reset_needed = 1;
				break;
			}
		} else { /* etype == ATEVENT */
			msg("correcting batch event\n");
			if (el_add(next_event, next_event->time,
			    next_event->of.at.eventid) < 0) {
				ignore_msg("ex", "at", next_event);
			}
		}
		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 *)&bufs.p.buf,
			    sizeof (bufs.p.buf))) == NULL ||
			    !inproj(e->u->name, pproj->pj_name,
			    bufs.p.buf2, sizeof (bufs.p.buf2))) {
				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->u)->uid == 0) { /* set default path */
		/* path settable in defaults file */
		envinit[2] = supath;
	} else {
		envinit[2] = path;
	}

	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);
	}

	if (e->etype == CRONEVENT && e->of.ct.home != NULL) {
		home = (char *)get_obj(e->of.ct.home);
	} else {
		home = (e->u)->home;
	}
	(void) strlcat(homedir, home, sizeof (homedir));
	(void) strlcat(logname, (e->u)->name, sizeof (logname));
	environ = envinit;
	if (chdir(home) == -1) {
		snprintf(bufs.error, sizeof (bufs.error), CANTCDHOME, home);
		mail((e->u)->name, bufs.error,
		    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) {
		if (e->of.ct.tz) {
			(void) putenv((char *)get_obj(e->of.ct.tz));
		}
		if (e->of.ct.shell) {
			char *name;

			sh = (char *)get_obj(e->of.ct.shell);
			name = strrchr(sh, '/');
			if (name == NULL)
				name = sh;
			else
				name++;

			(void) putenv(sh);
			sh += strlen(ENV_SHELL);
			(void) execl(sh, name, "-c", e->cmd, 0);
		} else {
			(void) execl(SHELL, "sh", "-c", e->cmd, 0);
			sh = SHELL;
		}
	} else {		/* type == ATEVENT */
		(void) execl(SHELL, "sh", 0);
		sh = SHELL;
	}
	snprintf(bufs.error, sizeof (bufs.error), CANTEXECSH, sh);
	mail((e->u)->name, bufs.error,
	    e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT);
	exit(1);
	/*NOTREACHED*/
}

/*
 * Main idle loop.
 * When timed out to run the job, return 0.
 * If for some reasons we need to reschedule jobs, return 1.
 */
static int
idle(long t)
{
	time_t	now;

	refresh = 0;

	while (t > 0L) {
		if (msg_wait(t) != 0) {
			/* we need to run next job immediately */
			return (0);
		}

		reap_child();

		if (refresh) {
			/* We got THAW or REFRESH message  */
			return (1);
		}

		now = time(NULL);
		if (last_time > now) {
			/* clock has been reset to backward */
			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) == 0) {
		if (!S_ISLNK(buf.st_mode) &&
		    (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");
	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;
	fd_set	fds;
	struct timespec tout, *toutp;
	static int	pending_msg;
	static time_t	pending_reftime;

	if (pending_msg) {
		process_msg(&msgbuf, pending_reftime);
		pending_msg = 0;
		return (0);
	}

	FD_ZERO(&fds);
	FD_SET(msgfd, &fds);

	toutp = NULL;
	if (tim != INFINITY) {
#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 more frequently.
		 */
		tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim;
#endif
		tout.tv_nsec = 0;
		tout.tv_sec = tim;
		toutp = &tout;
	}

	cnt = pselect(msgfd + 1, &fds, NULL, NULL, toutp, &defmask);
	if (cnt == -1 && errno != EINTR)
		perror("! pselect");

	/* pselect timeout or interrupted */
	if (cnt <= 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;
	case REFRESH:
		refresh = 1;
		pmsg->etype = 0;
		return;
	default:
		msg("message received - bad format");
		break;
	}
	if (next_event != NULL) {
		if (next_event->etype == CRONEVENT) {
			switch (el_add(next_event, next_event->time,
			    (next_event->u)->ctid)) {
			case -1:
				ignore_msg("process_msg", "cron", next_event);
				break;
			case -2: /* event time lower than init time */
				reset_needed = 1;
				break;
			}
		} else { /* etype == ATEVENT */
			if (el_add(next_event, next_event->time,
			    next_event->of.at.eventid) < 0) {
				ignore_msg("process_msg", "at", next_event);
			}
		}
		next_event = NULL;
	}
	(void) fflush(stdout);
	pmsg->etype = 0;
}

/*
 * Allocate a new or find an existing runinfo structure
 */
static struct runinfo *
rinfo_get(pid_t pid)
{
	struct runinfo *rp;

	if (pid == 0) {		/* allocate a new entry */
		rp = xcalloc(1, sizeof (struct runinfo));
		rp->next = rthead;	/* link the entry into the list */
		rthead = rp;
		return (rp);
	}
	/* search the list for an existing entry */
	for (rp = rthead; rp != NULL; rp = rp->next) {
		if (rp->pid == pid)
			break;
	}
	return (rp);
}

/*
 * Free a runinfo structure and its associated memory
 */
static void
rinfo_free(struct runinfo *entry)
{
	struct runinfo **rpp;
	struct runinfo *rp;

#ifdef DEBUG
	(void) fprintf(stderr, "freeing job %s\n", entry->jobname);
#endif
	for (rpp = &rthead; (rp = *rpp) != NULL; rpp = &rp->next) {
		if (rp == entry) {
			*rpp = rp->next;	/* unlink the entry */
			free(rp->outfile);
			free(rp->jobname);
			free(rp);
			break;
		}
	}
}

/* ARGSUSED */
static void
thaw_handler(int sig)
{
	refresh = 1;
}


/* ARGSUSED */
static void
cronend(int sig)
{
	crabort("SIGTERM", REMOVE_FIFO);
}

/*ARGSUSED*/
static void
child_handler(int sig)
{
	;
}

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
ignore_msg(char *func_name, char *job_type, struct event *event)
{
	msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)",
	    func_name, job_type,
	    event->u->name ? event->u->name : "unknown",
	    event->cmd ? event->cmd : "unknown",
	    event->time);
}

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;
		switch (el_add(next_event, next_event->time,
		    (next_event->u)->ctid)) {
		case -1:
			ignore_msg("resched", "cron", next_event);
			break;
		case -2: /* event time lower than init time */
			reset_needed = 1;
			break;
		}
		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(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)
		return (VUC_BADUSER);
	if (u->home != NULL) {
		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);
		}
	} else {
		u->home = xmalloc(strlen(pw->pw_dir) + 1);
		(void) strcpy(u->home, pw->pw_dir);
	}
	if (u->uid != pw->pw_uid)
		u->uid = pw->pw_uid;
	if (u->gid != pw->pw_gid)
		u->gid  = pw->pw_gid;

	/*
	 * 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 void
contract_set_template(void)
{
	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 void
contract_clear_template(void)
{
	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)) {
		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;
	}
}

static struct shared *
create_shared(void *obj, void * (*obj_alloc)(void *obj),
	void (*obj_free)(void *))
{
	struct shared *out;

	if ((out = xmalloc(sizeof (struct shared))) == NULL) {
		return (NULL);
	}
	if ((out->obj = obj_alloc(obj)) == NULL) {
		free(out);
		return (NULL);
	}
	out->count = 1;
	out->free = obj_free;

	return (out);
}

static struct shared *
create_shared_str(char *str)
{
	return (create_shared(str, (void *(*)(void *))strdup, free));
}

static struct shared *
dup_shared(struct shared *obj)
{
	if (obj != NULL) {
		obj->count++;
	}
	return (obj);
}

static void
rel_shared(struct shared *obj)
{
	if (obj && (--obj->count) == 0) {
		obj->free(obj->obj);
		free(obj);
	}
}

static void *
get_obj(struct shared *obj)
{
	return (obj->obj);
}