xref: /illumos-gate/usr/src/cmd/cron/cron.c (revision 789d94c2889bedf502063bc22addcabfa798a438)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*	Copyright (c) 1987, 1988 Microsoft Corporation	*/
31 /*	  All Rights Reserved	*/
32 
33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 #ifdef lint
36 /* make lint happy */
37 #define	__EXTENSIONS__
38 #endif
39 
40 #include <sys/contract/process.h>
41 #include <sys/ctfs.h>
42 #include <sys/param.h>
43 #include <sys/resource.h>
44 #include <sys/stat.h>
45 #include <sys/task.h>
46 #include <sys/time.h>
47 #include <sys/types.h>
48 #include <sys/utsname.h>
49 #include <sys/wait.h>
50 
51 #include <security/pam_appl.h>
52 
53 #include <alloca.h>
54 #include <ctype.h>
55 #include <deflt.h>
56 #include <dirent.h>
57 #include <errno.h>
58 #include <fcntl.h>
59 #include <grp.h>
60 #include <libcontract.h>
61 #include <libcontract_priv.h>
62 #include <limits.h>
63 #include <locale.h>
64 #include <poll.h>
65 #include <project.h>
66 #include <pwd.h>
67 #include <signal.h>
68 #include <stdarg.h>
69 #include <stdio.h>
70 #include <stdlib.h>
71 #include <string.h>
72 #include <stropts.h>
73 #include <time.h>
74 #include <unistd.h>
75 
76 #include "cron.h"
77 
78 /*
79  * #define	DEBUG
80  */
81 
82 #define	MAIL		"/usr/bin/mail"	/* mail program to use */
83 #define	CONSOLE		"/dev/console"	/* where messages go when cron dies */
84 
85 #define	TMPINFILE	"/tmp/crinXXXXXX"  /* file to put stdin in for cmd  */
86 #define	TMPDIR		"/tmp"
87 #define	PFX		"crout"
88 #define	TMPOUTFILE	"/tmp/croutXXXXXX" /* file to place stdout, stderr */
89 
90 #define	INMODE		00400		/* mode for stdin file	*/
91 #define	OUTMODE		00600		/* mode for stdout file */
92 #define	ISUID		S_ISUID		/* mode for verifing at jobs */
93 
94 #define	INFINITY	2147483647L	/* upper bound on time	*/
95 #define	CUSHION		180L
96 #define	ZOMB		100		/* proc slot used for mailing output */
97 
98 #define	JOBF		'j'
99 #define	NICEF		'n'
100 #define	USERF		'u'
101 #define	WAITF		'w'
102 
103 #define	BCHAR		'>'
104 #define	ECHAR		'<'
105 
106 #define	DEFAULT		0
107 #define	LOAD		1
108 #define	QBUFSIZ		80
109 
110 /* Defined actions for crabort() routine */
111 #define	NO_ACTION	000
112 #define	REMOVE_FIFO	001
113 #define	CONSOLE_MSG	002
114 
115 #define	BADCD		"can't change directory to the crontab directory."
116 #define	NOREADDIR	"can't read the crontab directory."
117 
118 #define	BADJOBOPEN	"unable to read your at job."
119 #define	BADSHELL	"because your login shell \
120 isn't /usr/bin/sh, you can't use cron."
121 
122 #define	BADSTAT		"can't access your crontab file.  Resubmit it."
123 #define	BADPROJID	"can't set project id for your job."
124 #define	CANTCDHOME	"can't change directory to your home directory.\
125 \nYour commands will not be executed."
126 #define	CANTEXECSH	"unable to exec the shell for one of your commands."
127 #define	NOREAD		"can't read your crontab file.  Resubmit it."
128 #define	BADTYPE		"crontab is not a regular file.\n"
129 #define	NOSTDIN		"unable to create a standard input file for \
130 one of your crontab commands. \
131 \nThat command was not executed."
132 
133 #define	NOTALLOWED	"you are not authorized to use cron.  Sorry."
134 #define	STDERRMSG	"\n\n********************************************\
135 *****\nCron: The previous message is the \
136 standard output and standard error \
137 \nof one of your cron commands.\n"
138 
139 #define	STDOUTERR	"one of your commands generated output or errors, \
140 but cron was unable to mail you this output.\
141 \nRemember to redirect standard output and standard \
142 error for each of your commands."
143 
144 #define	CLOCK_DRIFT	"clock time drifted backwards after event!\n"
145 #define	PIDERR		"unexpected pid returned %d (ignored)"
146 #define	CRONTABERR	"Subject: Your crontab file has an error in it\n\n"
147 #define	CRONOUT		"Subject: Output from \"cron\" command\n\n"
148 #define	MALLOCERR	"out of space, cannot create new string\n"
149 
150 #define	DIDFORK didfork
151 #define	NOFORK !didfork
152 
153 #define	MAILBUFLEN	(8*1024)
154 #define	LINELIMIT	80
155 #define	MAILBINITFREE	(MAILBUFLEN - (sizeof (cte_intro) - 1) \
156 	    - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1)
157 
158 #define	ERR_CRONTABENT	0	/* error in crontab file entry */
159 #define	ERR_UNIXERR	1	/* error in some system call */
160 #define	ERR_CANTEXECCRON 2	/* error setting up "cron" job environment */
161 #define	ERR_CANTEXECAT	3	/* error setting up "at" job environment */
162 
163 #define	PROJECT		"project="
164 
165 #define	MAX_LOST_CONTRACTS	2048	/* reset if this many failed abandons */
166 
167 #define	FORMAT	"%a %b %e %H:%M:%S %Y"
168 static char	timebuf[80];
169 
170 struct event {
171 	time_t time;	/* time of the event	*/
172 	short etype;	/* what type of event; 0=cron, 1=at	*/
173 	char *cmd;	/* command for cron, job name for at	*/
174 	struct usr *u;	/* ptr to the owner (usr) of this event	*/
175 	struct event *link;	/* ptr to another event for this user */
176 	union {
177 		struct { /* for crontab events */
178 			char *minute;	/*  (these	*/
179 			char *hour;	/*   fields	*/
180 			char *daymon;	/*   are	*/
181 			char *month;	/*   from	*/
182 			char *dayweek;	/*   crontab)	*/
183 			char *input;	/* ptr to stdin	*/
184 		} ct;
185 		struct { /* for at events */
186 			short exists;	/* for revising at events	*/
187 			int eventid;	/* for el_remove-ing at events	*/
188 		} at;
189 	} of;
190 };
191 
192 struct usr {
193 	char *name;	/* name of user (e.g. "root")	*/
194 	char *home;	/* home directory for user	*/
195 	uid_t uid;	/* user id	*/
196 	gid_t gid;	/* group id	*/
197 	int aruncnt;	/* counter for running jobs per uid */
198 	int cruncnt;	/* counter for running cron jobs per uid */
199 	int ctid;	/* for el_remove-ing crontab events */
200 	short ctexists;	/* for revising crontab events	*/
201 	struct event *ctevents;	/* list of this usr's crontab events */
202 	struct event *atevents;	/* list of this usr's at events */
203 	struct usr *nextusr;
204 };	/* ptr to next user	*/
205 
206 static struct	queue
207 {
208 	int njob;	/* limit */
209 	int nice;	/* nice for execution */
210 	int nwait;	/* wait time to next execution attempt */
211 	int nrun;	/* number running */
212 }
213 	qd = {100, 2, 60},		/* default values for queue defs */
214 	qt[NQUEUE];
215 static struct	queue	qq;
216 
217 static struct runinfo
218 {
219 	pid_t	pid;
220 	short	que;
221 	struct  usr *rusr;	/* pointer to usr struct */
222 	char	*outfile;	/* file where stdout & stderr are trapped */
223 	short	jobtype;	/* what type of event: 0=cron, 1=at */
224 	char	*jobname;	/* command for "cron", jobname for "at" */
225 	int	mailwhendone;	/* 1 = send mail even if no ouptut */
226 	struct runinfo *next;
227 }	*rthead;
228 
229 static struct miscpid {
230 	pid_t		pid;
231 	struct miscpid	*next;
232 }	*miscpid_head;
233 
234 static pid_t cron_pid;	/* own pid */
235 static char didfork = 0; /* flag to see if I'm process group leader */
236 static int msgfd;	/* file descriptor for fifo queue */
237 static int ecid = 1;	/* event class id for el_remove(); MUST be set to 1 */
238 static int delayed;	/* is job being rescheduled or did it run first time */
239 static int cwd;		/* current working directory */
240 static struct event *next_event;	/* the next event to execute	*/
241 static struct usr *uhead;		/* ptr to the list of users	*/
242 
243 /* Variables for error handling at reading crontabs. */
244 static char cte_intro[] = "Line(s) with errors:\n\n";
245 static char cte_trail1[] = "\nMax number of errors encountered.";
246 static char cte_trail2[] = " Evaluation of crontab aborted.\n";
247 static int cte_free = MAILBINITFREE;	/* Free buffer space */
248 static char *cte_text = NULL;		/* Text buffer pointer */
249 static char *cte_lp;			/* Next free line in cte_text */
250 static int cte_nvalid;			/* Valid lines found */
251 
252 /* user's default environment for the shell */
253 #define	ROOTPATH	"PATH=/usr/sbin:/usr/bin"
254 #define	NONROOTPATH	"PATH=/usr/bin:"
255 
256 static char *Def_supath	= NULL;
257 static char *Def_path		= NULL;
258 static char path[LINE_MAX]	= "PATH=";
259 static char supath[LINE_MAX]	= "PATH=";
260 static char homedir[LINE_MAX]	= "HOME=";
261 static char logname[LINE_MAX]	= "LOGNAME=";
262 static char tzone[LINE_MAX]	= "TZ=";
263 static char *envinit[] = {
264 	homedir,
265 	logname,
266 	ROOTPATH,
267 	"SHELL=/usr/bin/sh",
268 	tzone,
269 	NULL
270 };
271 
272 extern char **environ;
273 
274 #define	DEFTZ		"GMT"
275 static	int	log = 0;
276 static	char	hzname[10];
277 
278 static void cronend(int);
279 static void thaw_handler(int);
280 static void child_handler(int);
281 static void child_sigreset(void);
282 
283 static void dscan(DIR *dir, void (*fp)(char *, time_t));
284 static void mod_ctab(char *, time_t);
285 static void mod_atjob(char *, time_t);
286 static void add_atevent(struct usr *, char *, time_t, int);
287 static void rm_ctevents(struct usr *);
288 static void cleanup(struct runinfo *rn, int r);
289 static void crabort(char *, int);
290 static void msg(char *fmt, ...);
291 static void logit(int, struct runinfo *, int);
292 static void parsqdef(char *);
293 static void defaults();
294 static void initialize(int);
295 static void quedefs(int);
296 static int idle(long);
297 static struct usr *find_usr(char *);
298 static int ex(struct event *e);
299 static void read_dirs(void);
300 static void mail(char *, char *, int);
301 static char *next_field(int, int);
302 static void readcron(struct usr *, time_t);
303 static int next_ge(int, char *);
304 static void free_if_unused(struct usr *);
305 static void del_atjob(char *, char *);
306 static void del_ctab(char *);
307 static void resched(int);
308 static int msg_wait(long);
309 static struct runinfo *rinfo_get(pid_t);
310 static void rinfo_free(struct runinfo *rp);
311 static void mail_result(struct usr *p, struct runinfo *pr, size_t filesize);
312 static time_t next_time(struct event *, time_t);
313 static time_t get_switching_time(int, time_t);
314 static time_t xmktime(struct tm *);
315 static void process_msg(struct message *, time_t);
316 static void reap_child(void);
317 static void miscpid_insert(pid_t);
318 static int miscpid_delete(pid_t);
319 static void contract_set_template(void);
320 static void contract_clear_template(void);
321 static void contract_abandon_latest(pid_t);
322 
323 static void cte_init(void);
324 static void cte_add(int, char *);
325 static void cte_valid(void);
326 static int cte_istoomany(void);
327 static void cte_sendmail(char *);
328 
329 static int set_user_cred(const struct usr *, struct project *);
330 
331 /*
332  * last_time is set immediately prior to exection of an event (via ex())
333  * to indicate the last time an event was executed.  This was (surely)
334  * it's original intended use.
335  */
336 static time_t last_time, init_time, t_old;
337 
338 static int		accept_sigcld, notifypipe[2];
339 static sigset_t		defmask, childmask;
340 
341 /*
342  * BSM hooks
343  */
344 extern int	audit_cron_session(char *, char *, uid_t, gid_t, char *);
345 extern void	audit_cron_new_job(char *, int, void *);
346 extern void	audit_cron_bad_user(char *);
347 extern void	audit_cron_user_acct_expired(char *);
348 extern int	audit_cron_create_anc_file(char *, char *, char *, uid_t);
349 extern int	audit_cron_delete_anc_file(char *, char *);
350 extern int	audit_cron_is_anc_name(char *);
351 extern int	audit_cron_mode();
352 
353 static int cron_conv(int, struct pam_message **,
354 		struct pam_response **, void *);
355 
356 static struct pam_conv pam_conv = {cron_conv, NULL};
357 static pam_handle_t *pamh;	/* Authentication handle */
358 
359 /*
360  * Function to help check a user's credentials.
361  */
362 
363 static int verify_user_cred(const struct usr *u);
364 
365 /*
366  * Values returned by verify_user_cred and set_user_cred:
367  */
368 
369 #define	VUC_OK		0
370 #define	VUC_BADUSER	1
371 #define	VUC_NOTINGROUP	2
372 #define	VUC_EXPIRED	3
373 #define	VUC_NEW_AUTH	4
374 
375 /*
376  * Modes of process_anc_files function
377  */
378 #define	CRON_ANC_DELETE	1
379 #define	CRON_ANC_CREATE	0
380 
381 /*
382  * Functions to remove a user or job completely from the running database.
383  */
384 static void clean_out_atjobs(struct usr *u);
385 static void clean_out_ctab(struct usr *u);
386 static void clean_out_user(struct usr *u);
387 static void cron_unlink(char *name);
388 static void process_anc_files(int);
389 
390 /*
391  * functions in elm.c
392  */
393 extern void el_init(int, time_t, time_t, int);
394 extern void el_add(void *, time_t, int);
395 extern void el_remove(int, int);
396 extern int el_empty(void);
397 extern void *el_first(void);
398 extern void el_delete(void);
399 
400 int
401 main(int argc, char *argv[])
402 {
403 	time_t t;
404 	time_t ne_time;		/* amt of time until next event execution */
405 	time_t newtime, lastmtime = 0L;
406 	struct usr *u;
407 	struct event *e, *e2, *eprev;
408 	struct stat buf;
409 	pid_t rfork;
410 	struct sigaction act;
411 
412 	/*
413 	 * reset is set to 1 via the return from ex() should ex() find
414 	 * that the event to be executed is being run at the wrong time.
415 	 * We immediately return to the top of the while (TRUE) loop in
416 	 * main() where the event list is cleared and rebuilt, and reset
417 	 * is set back to 0.
418 	 */
419 	int reset = 0;
420 
421 	/*
422 	 * Only the privileged user can run this command.
423 	 */
424 	if (getuid() != 0)
425 		crabort(NOTALLOWED, 0);
426 
427 begin:
428 	(void) setlocale(LC_ALL, "");
429 	/* fork unless 'nofork' is specified */
430 	if ((argc <= 1) || (strcmp(argv[1], "nofork"))) {
431 		if (rfork = fork()) {
432 			if (rfork == (pid_t)-1) {
433 				(void) sleep(30);
434 				goto begin;
435 			}
436 			return (0);
437 		}
438 		didfork++;
439 		(void) setpgrp();	/* detach cron from console */
440 	}
441 
442 	(void) umask(022);
443 	(void) signal(SIGHUP, SIG_IGN);
444 	(void) signal(SIGINT, SIG_IGN);
445 	(void) signal(SIGQUIT, SIG_IGN);
446 	(void) signal(SIGTERM, cronend);
447 
448 	defaults();
449 	initialize(1);
450 	quedefs(DEFAULT);	/* load default queue definitions */
451 	cron_pid = getpid();
452 	msg("*** cron started ***   pid = %d", cron_pid);
453 	(void) sigset(SIGTHAW, thaw_handler);
454 	/*
455 	 * setup SIGCLD handler/mask
456 	 */
457 	act.sa_handler = child_handler;
458 	act.sa_flags = 0;
459 	(void) sigemptyset(&act.sa_mask);
460 	(void) sigaddset(&act.sa_mask, SIGCLD);
461 	(void) sigaction(SIGCLD, &act, NULL);
462 	(void) sigemptyset(&childmask);
463 	(void) sigaddset(&childmask, SIGCLD);
464 	(void) sigprocmask(SIG_BLOCK, &childmask, &defmask);
465 
466 	if (pipe(notifypipe) != 0) {
467 		crabort("cannot create pipe", REMOVE_FIFO|CONSOLE_MSG);
468 	}
469 	/*
470 	 * will set O_NONBLOCK, so that the write() from child_handler
471 	 * never be blocked.
472 	 */
473 	(void) fcntl(notifypipe[0], F_SETFL, O_WRONLY|O_NONBLOCK);
474 
475 	t_old = time(NULL);
476 	last_time = t_old;
477 	for (;;) {		/* MAIN LOOP */
478 		t = time(NULL);
479 		if ((t_old > t) || (t-last_time > CUSHION) || reset) {
480 			reset = 0;
481 			/* the time was set backwards or forward */
482 			el_delete();
483 			u = uhead;
484 			while (u != NULL) {
485 				rm_ctevents(u);
486 				e = u->atevents;
487 				while (e != NULL) {
488 					free(e->cmd);
489 					e2 = e->link;
490 					free(e);
491 					e = e2;
492 				}
493 				u->atevents = NULL;
494 				u = u->nextusr;
495 			}
496 			(void) close(msgfd);
497 			initialize(0);
498 			t = time(NULL);
499 			last_time = t;
500 		}
501 		t_old = t;
502 
503 		if (next_event == NULL && !el_empty()) {
504 			next_event = (struct event *)el_first();
505 		}
506 		if (next_event == NULL) {
507 			ne_time = INFINITY;
508 		} else {
509 			ne_time = next_event->time - t;
510 #ifdef DEBUG
511 			cftime(timebuf, "%C", &next_event->time);
512 			(void) fprintf(stderr, "next_time=%ld %s\n",
513 				next_event->time, timebuf);
514 #endif
515 		}
516 		if (ne_time > 0) {
517 			if ((reset = idle(ne_time)) != 0)
518 				continue;
519 		}
520 
521 		if (stat(QUEDEFS, &buf)) {
522 			msg("cannot stat QUEDEFS file");
523 		} else if (lastmtime != buf.st_mtime) {
524 			quedefs(LOAD);
525 			lastmtime = buf.st_mtime;
526 		}
527 
528 		last_time = next_event->time; /* save execution time */
529 
530 		if (reset = ex(next_event))
531 			continue;
532 
533 		switch (next_event->etype) {
534 		case CRONEVENT:
535 			/* add cronevent back into the main event list */
536 			if (delayed) {
537 				delayed = 0;
538 				break;
539 			}
540 
541 			/*
542 			 * check if time(0)< last_time. if so, then the
543 			 * system clock has gone backwards. to prevent this
544 			 * job from being started twice, we reschedule this
545 			 * job for the >>next time after last_time<<, and
546 			 * then set next_event->time to this. note that
547 			 * crontab's resolution is 1 minute.
548 			 */
549 
550 			if (last_time > time(NULL)) {
551 				msg(CLOCK_DRIFT);
552 				/*
553 				 * bump up to next 30 second
554 				 * increment
555 				 * 1 <= newtime <= 30
556 				 */
557 				newtime = 30 - (last_time % 30);
558 				newtime += last_time;
559 
560 				/*
561 				 * get the next scheduled event,
562 				 * not the one that we just
563 				 * kicked off!
564 				 */
565 				next_event->time =
566 					next_time(next_event, newtime);
567 				t_old = time(NULL);
568 			} else {
569 				next_event->time =
570 					next_time(next_event, (time_t)0);
571 			}
572 #ifdef DEBUG
573 			cftime(timebuf, "%C", &next_event->time);
574 			(void) fprintf(stderr,
575 				"pushing back cron event %s at %ld (%s)\n",
576 				next_event->cmd, next_event->time, timebuf);
577 #endif
578 
579 			el_add(next_event, next_event->time,
580 				(next_event->u)->ctid);
581 			break;
582 		default:
583 			/* remove at or batch job from system */
584 			if (delayed) {
585 				delayed = 0;
586 				break;
587 			}
588 			eprev = NULL;
589 			e = (next_event->u)->atevents;
590 			while (e != NULL) {
591 				if (e == next_event) {
592 					if (eprev == NULL)
593 						(e->u)->atevents = e->link;
594 					else
595 						eprev->link = e->link;
596 					free(e->cmd);
597 					free(e);
598 					break;
599 				} else {
600 					eprev = e;
601 					e = e->link;
602 				}
603 			}
604 			break;
605 		}
606 		next_event = NULL;
607 	}
608 
609 	/*NOTREACHED*/
610 }
611 
612 static void
613 initialize(int firstpass)
614 {
615 #ifdef DEBUG
616 	(void) fprintf(stderr, "in initialize\n");
617 #endif
618 	init_time = time(NULL);
619 	el_init(8, init_time, (time_t)(60*60*24), 10);
620 	if (firstpass) {
621 		/* for mail(1), make sure messages come from root */
622 		if (putenv("LOGNAME=root") != 0) {
623 			crabort("cannot expand env variable",
624 				REMOVE_FIFO|CONSOLE_MSG);
625 		}
626 		if (access(FIFO, R_OK) == -1) {
627 			if (errno == ENOENT) {
628 				if (mknod(FIFO, S_IFIFO|0600, 0) != 0)
629 					crabort("cannot create fifo queue",
630 						REMOVE_FIFO|CONSOLE_MSG);
631 			} else {
632 				if (NOFORK) {
633 					/* didn't fork... init(1M) is waiting */
634 					(void) sleep(60);
635 				}
636 				perror("FIFO");
637 				crabort("cannot access fifo queue",
638 					REMOVE_FIFO|CONSOLE_MSG);
639 			}
640 		} else {
641 			if (NOFORK) {
642 				/* didn't fork... init(1M) is waiting */
643 				(void) sleep(60);
644 				/*
645 				 * the wait is painful, but we don't want
646 				 * init respawning this quickly
647 				 */
648 			}
649 			crabort("cannot start cron; FIFO exists", CONSOLE_MSG);
650 		}
651 	}
652 
653 	if ((msgfd = open(FIFO, O_RDWR)) < 0) {
654 		perror("! open");
655 		crabort("cannot open fifo queue", REMOVE_FIFO|CONSOLE_MSG);
656 	}
657 
658 	/*
659 	 * read directories, create users list, and add events to the
660 	 * main event list. Only zero user list on firstpass.
661 	 */
662 	if (firstpass)
663 		uhead = NULL;
664 	read_dirs();
665 	next_event = NULL;
666 
667 	if (!firstpass)
668 		return;
669 
670 	/* stdout is log file */
671 	if (freopen(ACCTFILE, "a", stdout) == NULL)
672 		(void) fprintf(stderr, "cannot open %s\n", ACCTFILE);
673 
674 	/* log should be root-only */
675 	(void) fchmod(1, S_IRUSR|S_IWUSR);
676 
677 	/* stderr also goes to ACCTFILE */
678 	(void) close(fileno(stderr));
679 	(void) dup(1);
680 
681 	/* null for stdin */
682 	(void) freopen("/dev/null", "r", stdin);
683 
684 	contract_set_template();
685 }
686 
687 static void
688 read_dirs()
689 {
690 	DIR	*dir;
691 
692 	if (chdir(CRONDIR) == -1)
693 		crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG);
694 	cwd = CRON;
695 	if ((dir = opendir(".")) == NULL)
696 		crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG);
697 	dscan(dir, mod_ctab);
698 	(void) closedir(dir);
699 	if (chdir(ATDIR) == -1) {
700 		msg("cannot chdir to at directory");
701 		return;
702 	}
703 	cwd = AT;
704 	if ((dir = opendir(".")) == NULL) {
705 		msg("cannot read at at directory");
706 		return;
707 	}
708 	dscan(dir, mod_atjob);
709 	(void) closedir(dir);
710 }
711 
712 static void
713 dscan(DIR *df, void (*fp)(char *, time_t))
714 {
715 	struct dirent	*dp;
716 
717 	while ((dp = readdir(df)) != NULL) {
718 		if (strcmp(dp->d_name, ".") == 0 ||
719 		    strcmp(dp->d_name, "..") == 0) {
720 			continue;
721 		}
722 		(*fp)(dp->d_name, 0);
723 	}
724 }
725 
726 static void
727 mod_ctab(char *name, time_t reftime)
728 {
729 	struct	passwd	*pw;
730 	struct	stat	buf;
731 	struct	usr	*u;
732 	char	namebuf[PATH_MAX];
733 	char	*pname;
734 
735 	/* skip over ancillary file names */
736 	if (audit_cron_is_anc_name(name))
737 		return;
738 
739 	if ((pw = getpwnam(name)) == NULL) {
740 		msg("No such user as %s - cron entries not created", name);
741 		return;
742 	}
743 	if (cwd != CRON) {
744 		if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
745 				CRONDIR, name) >= sizeof (namebuf)) {
746 			msg("Too long path name %s - cron entries not created",
747 				namebuf);
748 			return;
749 		}
750 		pname = namebuf;
751 	} else {
752 		pname = name;
753 	}
754 	/*
755 	 * a warning message is given by the crontab command so there is
756 	 * no need to give one here......  use this code if you only want
757 	 * users with a login shell of /usr/bin/sh to use cron
758 	 */
759 #ifdef BOURNESHELLONLY
760 	if ((strcmp(pw->pw_shell, "") != 0) &&
761 	    (strcmp(pw->pw_shell, SHELL) != 0)) {
762 		mail(name, BADSHELL, ERR_CANTEXECCRON);
763 		cron_unlink(pname);
764 		return;
765 	}
766 #endif
767 	if (stat(pname, &buf)) {
768 		mail(name, BADSTAT, ERR_UNIXERR);
769 		cron_unlink(pname);
770 		return;
771 	}
772 	if (!S_ISREG(buf.st_mode)) {
773 		mail(name, BADTYPE, ERR_CRONTABENT);
774 		return;
775 	}
776 	if ((u = find_usr(name)) == NULL) {
777 #ifdef DEBUG
778 		(void) fprintf(stderr, "new user (%s) with a crontab\n", name);
779 #endif
780 		u = xmalloc(sizeof (struct usr));
781 		u->name = xmalloc(strlen(name)+1);
782 		(void) strcpy(u->name, name);
783 		u->home = xmalloc(strlen(pw->pw_dir)+1);
784 		(void) strcpy(u->home, pw->pw_dir);
785 		u->uid = pw->pw_uid;
786 		u->gid = pw->pw_gid;
787 		u->ctexists = TRUE;
788 		u->ctid = ecid++;
789 		u->ctevents = NULL;
790 		u->atevents = NULL;
791 		u->aruncnt = 0;
792 		u->cruncnt = 0;
793 		u->nextusr = uhead;
794 		uhead = u;
795 		readcron(u, reftime);
796 	} else {
797 		u->uid = pw->pw_uid;
798 		u->gid = pw->pw_gid;
799 		if (strcmp(u->home, pw->pw_dir) != 0) {
800 			free(u->home);
801 			u->home = xmalloc(strlen(pw->pw_dir)+1);
802 			(void) strcpy(u->home, pw->pw_dir);
803 		}
804 		u->ctexists = TRUE;
805 		if (u->ctid == 0) {
806 #ifdef DEBUG
807 			(void) fprintf(stderr, "%s now has a crontab\n",
808 			    u->name);
809 #endif
810 			/* user didnt have a crontab last time */
811 			u->ctid = ecid++;
812 			u->ctevents = NULL;
813 			readcron(u, reftime);
814 			return;
815 		}
816 #ifdef DEBUG
817 		(void) fprintf(stderr, "%s has revised his crontab\n", u->name);
818 #endif
819 		rm_ctevents(u);
820 		el_remove(u->ctid, 0);
821 		readcron(u, reftime);
822 	}
823 }
824 
825 /* ARGSUSED */
826 static void
827 mod_atjob(char *name, time_t reftime)
828 {
829 	char	*ptr;
830 	time_t	tim;
831 	struct	passwd	*pw;
832 	struct	stat	buf;
833 	struct	usr	*u;
834 	struct	event	*e;
835 	char	namebuf[PATH_MAX];
836 	char	*pname;
837 	int	jobtype;
838 
839 	ptr = name;
840 	if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
841 		return;
842 	ptr++;
843 	if (!isalpha(*ptr))
844 		return;
845 	jobtype = *ptr - 'a';
846 
847 	/* check for audit ancillary file */
848 	if (audit_cron_is_anc_name(name))
849 		return;
850 
851 	if (cwd != AT) {
852 		if (snprintf(namebuf, sizeof (namebuf), "%s/%s", ATDIR, name)
853 		    >= sizeof (namebuf)) {
854 			return;
855 		}
856 		pname = namebuf;
857 	} else {
858 		pname = name;
859 	}
860 	if (stat(pname, &buf) || jobtype >= NQUEUE) {
861 		cron_unlink(pname);
862 		return;
863 	}
864 	if (!(buf.st_mode & ISUID) || !S_ISREG(buf.st_mode)) {
865 		cron_unlink(pname);
866 		return;
867 	}
868 	if ((pw = getpwuid(buf.st_uid)) == NULL) {
869 		cron_unlink(pname);
870 		return;
871 	}
872 	/*
873 	 * a warning message is given by the at command so there is no
874 	 * need to give one here......use this code if you only want
875 	 * users with a login shell of /usr/bin/sh to use cron
876 	 */
877 #ifdef BOURNESHELLONLY
878 	if ((strcmp(pw->pw_shell, "") != 0) &&
879 	    (strcmp(pw->pw_shell, SHELL) != 0)) {
880 		mail(pw->pw_name, BADSHELL, ERR_CANTEXECAT);
881 		cron_unlink(pname);
882 		return;
883 	}
884 #endif
885 	if ((u = find_usr(pw->pw_name)) == NULL) {
886 #ifdef DEBUG
887 		(void) fprintf(stderr, "new user (%s) with an at job = %s\n",
888 			pw->pw_name, name);
889 #endif
890 		u = xmalloc(sizeof (struct usr));
891 		u->name = xmalloc(strlen(pw->pw_name)+1);
892 		(void) strcpy(u->name, pw->pw_name);
893 		u->home = xmalloc(strlen(pw->pw_dir)+1);
894 		(void) strcpy(u->home, pw->pw_dir);
895 		u->uid = pw->pw_uid;
896 		u->gid = pw->pw_gid;
897 		u->ctexists = FALSE;
898 		u->ctid = 0;
899 		u->ctevents = NULL;
900 		u->atevents = NULL;
901 		u->aruncnt = 0;
902 		u->cruncnt = 0;
903 		u->nextusr = uhead;
904 		uhead = u;
905 		add_atevent(u, name, tim, jobtype);
906 	} else {
907 		u->uid = pw->pw_uid;
908 		u->gid = pw->pw_gid;
909 		if (strcmp(u->home, pw->pw_dir) != 0) {
910 			free(u->home);
911 			u->home = xmalloc(strlen(pw->pw_dir)+1);
912 			(void) strcpy(u->home, pw->pw_dir);
913 		}
914 		e = u->atevents;
915 		while (e != NULL) {
916 			if (strcmp(e->cmd, name) == 0) {
917 				e->of.at.exists = TRUE;
918 				break;
919 			} else {
920 				e = e->link;
921 			}
922 		}
923 		if (e == NULL) {
924 #ifdef DEBUG
925 			(void) fprintf(stderr, "%s has a new at job = %s\n",
926 				u->name, name);
927 #endif
928 			add_atevent(u, name, tim, jobtype);
929 		}
930 	}
931 }
932 
933 static void
934 add_atevent(struct usr *u, char *job, time_t tim, int jobtype)
935 {
936 	struct event *e;
937 
938 	e = xmalloc(sizeof (struct event));
939 	e->etype = jobtype;
940 	e->cmd = xmalloc(strlen(job)+1);
941 	(void) strcpy(e->cmd, job);
942 	e->u = u;
943 	e->link = u->atevents;
944 	u->atevents = e;
945 	e->of.at.exists = TRUE;
946 	e->of.at.eventid = ecid++;
947 	if (tim < init_time)	/* old job */
948 		e->time = init_time;
949 	else
950 		e->time = tim;
951 #ifdef DEBUG
952 	(void) fprintf(stderr, "add_atevent: user=%s, job=%s, time=%ld\n",
953 		u->name, e->cmd, e->time);
954 #endif
955 	el_add(e, e->time, e->of.at.eventid);
956 }
957 
958 
959 static char line[CTLINESIZE];	/* holds a line from a crontab file */
960 static int cursor;		/* cursor for the above line */
961 
962 static void
963 readcron(struct usr *u, time_t reftime)
964 {
965 	/*
966 	 * readcron reads in a crontab file for a user (u). The list of
967 	 * events for user u is built, and u->events is made to point to
968 	 * this list. Each event is also entered into the main event
969 	 * list.
970 	 */
971 	FILE *cf;	/* cf will be a user's crontab file */
972 	struct event *e;
973 	int start;
974 	unsigned int i;
975 	char namebuf[PATH_MAX];
976 	char *pname;
977 	int lineno = 0;
978 
979 	/* read the crontab file */
980 	cte_init();		/* Init error handling */
981 	if (cwd != CRON) {
982 		if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
983 		    CRONDIR, u->name) >= sizeof (namebuf)) {
984 			return;
985 		}
986 		pname = namebuf;
987 	} else {
988 		pname = u->name;
989 	}
990 	if ((cf = fopen(pname, "r")) == NULL) {
991 		mail(u->name, NOREAD, ERR_UNIXERR);
992 		return;
993 	}
994 	while (fgets(line, CTLINESIZE, cf) != NULL) {
995 		/* process a line of a crontab file */
996 		lineno++;
997 		if (cte_istoomany())
998 			break;
999 		cursor = 0;
1000 		while (line[cursor] == ' ' || line[cursor] == '\t')
1001 			cursor++;
1002 		if (line[cursor] == '#' || line[cursor] == '\n')
1003 			continue;
1004 		e = xmalloc(sizeof (struct event));
1005 		e->etype = CRONEVENT;
1006 		if (!(((e->of.ct.minute = next_field(0, 59)) != NULL) &&
1007 		    ((e->of.ct.hour = next_field(0, 23)) != NULL) &&
1008 		    ((e->of.ct.daymon = next_field(1, 31)) != NULL) &&
1009 		    ((e->of.ct.month = next_field(1, 12)) != NULL) &&
1010 		    ((e->of.ct.dayweek = next_field(0, 6)) != NULL))) {
1011 			free(e);
1012 			cte_add(lineno, line);
1013 			continue;
1014 		}
1015 		while (line[cursor] == ' ' || line[cursor] == '\t')
1016 			cursor++;
1017 		if (line[cursor] == '\n' || line[cursor] == '\0')
1018 			continue;
1019 		/* get the command to execute	*/
1020 		start = cursor;
1021 again:
1022 		while ((line[cursor] != '%') &&
1023 		    (line[cursor] != '\n') &&
1024 		    (line[cursor] != '\0') &&
1025 		    (line[cursor] != '\\'))
1026 			cursor++;
1027 		if (line[cursor] == '\\') {
1028 			cursor += 2;
1029 			goto again;
1030 		}
1031 		e->cmd = xmalloc(cursor-start+1);
1032 		(void) strncpy(e->cmd, line+start, cursor-start);
1033 		e->cmd[cursor-start] = '\0';
1034 		/* see if there is any standard input	*/
1035 		if (line[cursor] == '%') {
1036 			e->of.ct.input = xmalloc(strlen(line)-cursor+1);
1037 			(void) strcpy(e->of.ct.input, line+cursor+1);
1038 			for (i = 0; i < strlen(e->of.ct.input); i++) {
1039 				if (e->of.ct.input[i] == '%')
1040 					e->of.ct.input[i] = '\n';
1041 			}
1042 		} else {
1043 			e->of.ct.input = NULL;
1044 		}
1045 		/* have the event point to it's owner	*/
1046 		e->u = u;
1047 		/* insert this event at the front of this user's event list */
1048 		e->link = u->ctevents;
1049 		u->ctevents = e;
1050 		/* set the time for the first occurance of this event	*/
1051 		e->time = next_time(e, reftime);
1052 		/* finally, add this event to the main event list	*/
1053 		el_add(e, e->time, u->ctid);
1054 		cte_valid();
1055 #ifdef DEBUG
1056 		cftime(timebuf, "%C", &e->time);
1057 		(void) fprintf(stderr, "inserting cron event %s at %ld (%s)\n",
1058 			e->cmd, e->time, timebuf);
1059 #endif
1060 	}
1061 	cte_sendmail(u->name);	/* mail errors if any to user */
1062 	(void) fclose(cf);
1063 }
1064 
1065 /*
1066  * Below are the functions for handling of errors in crontabs. Concept is to
1067  * collect faulty lines and send one email at the end of the crontab
1068  * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
1069  * of crontab is aborted. Otherwise reading of crontab is continued to the end
1070  * of the file but no further error logging appears.
1071  */
1072 static void
1073 cte_init()
1074 {
1075 	if (cte_text == NULL)
1076 		cte_text = xmalloc(MAILBUFLEN);
1077 	(void) strlcpy(cte_text, cte_intro, MAILBUFLEN);
1078 	cte_lp = cte_text + sizeof (cte_intro) - 1;
1079 	cte_free = MAILBINITFREE;
1080 	cte_nvalid = 0;
1081 }
1082 
1083 static void
1084 cte_add(int lineno, char *ctline)
1085 {
1086 	int len;
1087 	char *p;
1088 
1089 	if (cte_free >= LINELIMIT) {
1090 		(void) sprintf(cte_lp, "%4d: ", lineno);
1091 		(void) strlcat(cte_lp, ctline, LINELIMIT - 1);
1092 		len = strlen(cte_lp);
1093 		if (cte_lp[len - 1] != '\n') {
1094 			cte_lp[len++] = '\n';
1095 			cte_lp[len] = '\0';
1096 		}
1097 		for (p = cte_lp; *p; p++) {
1098 			if (isprint(*p) || *p == '\n' || *p == '\t')
1099 				continue;
1100 			*p = '.';
1101 		}
1102 		cte_lp += len;
1103 		cte_free -= len;
1104 		if (cte_free < LINELIMIT) {
1105 			size_t buflen = MAILBUFLEN - (cte_lp - cte_text);
1106 			(void) strlcpy(cte_lp, cte_trail1, buflen);
1107 			if (cte_nvalid == 0)
1108 				(void) strlcat(cte_lp, cte_trail2, buflen);
1109 		}
1110 	}
1111 }
1112 
1113 static void
1114 cte_valid()
1115 {
1116 	cte_nvalid++;
1117 }
1118 
1119 static int
1120 cte_istoomany()
1121 {
1122 	/*
1123 	 * Return TRUE only if all lines are faulty. So evaluation of
1124 	 * a crontab is not aborted if at least one valid line was found.
1125 	 */
1126 	return (cte_nvalid == 0 && cte_free < LINELIMIT);
1127 }
1128 
1129 static void
1130 cte_sendmail(char *username)
1131 {
1132 	if (cte_free < MAILBINITFREE)
1133 		mail(username, cte_text, ERR_CRONTABENT);
1134 }
1135 
1136 /*
1137  * Send mail with error message to a user
1138  */
1139 static void
1140 mail(char *usrname, char *mesg, int format)
1141 {
1142 	/* mail mails a user a message.	*/
1143 	FILE *pipe;
1144 	char *temp;
1145 	struct passwd	*ruser_ids;
1146 	pid_t fork_val;
1147 	int saveerrno = errno;
1148 	struct utsname	name;
1149 
1150 #ifdef TESTING
1151 	return;
1152 #endif
1153 	(void) uname(&name);
1154 	if ((fork_val = fork()) == (pid_t)-1) {
1155 		msg("cron cannot fork\n");
1156 		return;
1157 	}
1158 	if (fork_val == 0) {
1159 		child_sigreset();
1160 		contract_clear_template();
1161 		if ((ruser_ids = getpwnam(usrname)) == NULL)
1162 			exit(0);
1163 		(void) setuid(ruser_ids->pw_uid);
1164 		temp = xmalloc(strlen(MAIL)+strlen(usrname)+2);
1165 		(void) sprintf(temp, "%s %s", MAIL, usrname);
1166 		pipe = popen(temp, "w");
1167 		if (pipe != NULL) {
1168 			(void) fprintf(pipe, "To: %s\n", usrname);
1169 			switch (format) {
1170 			case ERR_CRONTABENT:
1171 				(void) fprintf(pipe, CRONTABERR);
1172 				(void) fprintf(pipe, "Your \"crontab\" on %s\n",
1173 				    name.nodename);
1174 				(void) fprintf(pipe, mesg);
1175 				(void) fprintf(pipe,
1176 				    "\nEntries or crontab have been ignored\n");
1177 				break;
1178 			case ERR_UNIXERR:
1179 				(void) fprintf(pipe, "Subject: %s\n\n", mesg);
1180 				(void) fprintf(pipe,
1181 				    "The error on %s was \"%s\"\n",
1182 				    name.nodename, errmsg(saveerrno));
1183 				break;
1184 
1185 			case ERR_CANTEXECCRON:
1186 				(void) fprintf(pipe,
1187 				"Subject: Couldn't run your \"cron\" job\n\n");
1188 				(void) fprintf(pipe,
1189 				    "Your \"cron\" job on %s ", name.nodename);
1190 				(void) fprintf(pipe, "couldn't be run\n");
1191 				(void) fprintf(pipe, "%s\n", mesg);
1192 				(void) fprintf(pipe,
1193 				"The error was \"%s\"\n", errmsg(saveerrno));
1194 				break;
1195 
1196 			case ERR_CANTEXECAT:
1197 				(void) fprintf(pipe,
1198 				"Subject: Couldn't run your \"at\" job\n\n");
1199 				(void) fprintf(pipe, "Your \"at\" job on %s ",
1200 				    name.nodename);
1201 				(void) fprintf(pipe, "couldn't be run\n");
1202 				(void) fprintf(pipe, "%s\n", mesg);
1203 				(void) fprintf(pipe,
1204 				"The error was \"%s\"\n", errmsg(saveerrno));
1205 				break;
1206 
1207 			default:
1208 				break;
1209 			}
1210 			(void) pclose(pipe);
1211 		}
1212 		free(temp);
1213 		exit(0);
1214 	}
1215 
1216 	contract_abandon_latest(fork_val);
1217 
1218 	if (cron_pid == getpid()) {
1219 		miscpid_insert(fork_val);
1220 	}
1221 }
1222 
1223 static char *
1224 next_field(int lower, int upper)
1225 {
1226 	/*
1227 	 * next_field returns a pointer to a string which holds the next
1228 	 * field of a line of a crontab file.
1229 	 *   if (numbers in this field are out of range (lower..upper),
1230 	 *	or there is a syntax error) then
1231 	 *	NULL is returned, and a mail message is sent to the
1232 	 *	user telling him which line the error was in.
1233 	 */
1234 
1235 	char *s;
1236 	int num, num2, start;
1237 
1238 	while ((line[cursor] == ' ') || (line[cursor] == '\t'))
1239 		cursor++;
1240 	start = cursor;
1241 	if (line[cursor] == '\0') {
1242 		return (NULL);
1243 	}
1244 	if (line[cursor] == '*') {
1245 		cursor++;
1246 		if ((line[cursor] != ' ') && (line[cursor] != '\t'))
1247 			return (NULL);
1248 		s = xmalloc(2);
1249 		(void) strcpy(s, "*");
1250 		return (s);
1251 	}
1252 	for (;;) {
1253 		if (!isdigit(line[cursor]))
1254 			return (NULL);
1255 		num = 0;
1256 		do {
1257 			num = num*10 + (line[cursor]-'0');
1258 		} while (isdigit(line[++cursor]));
1259 		if ((num < lower) || (num > upper))
1260 			return (NULL);
1261 		if (line[cursor] == '-') {
1262 			if (!isdigit(line[++cursor]))
1263 				return (NULL);
1264 			num2 = 0;
1265 			do {
1266 				num2 = num2*10 + (line[cursor]-'0');
1267 			} while (isdigit(line[++cursor]));
1268 			if ((num2 < lower) || (num2 > upper))
1269 				return (NULL);
1270 		}
1271 		if ((line[cursor] == ' ') || (line[cursor] == '\t'))
1272 			break;
1273 		if (line[cursor] == '\0')
1274 			return (NULL);
1275 		if (line[cursor++] != ',')
1276 			return (NULL);
1277 	}
1278 	s = xmalloc(cursor-start+1);
1279 	(void) strncpy(s, line+start, cursor-start);
1280 	s[cursor-start] = '\0';
1281 	return (s);
1282 }
1283 
1284 #define	tm_cmp(t1, t2) (\
1285 	(t1)->tm_year == (t2)->tm_year && \
1286 	(t1)->tm_mon == (t2)->tm_mon && \
1287 	(t1)->tm_mday == (t2)->tm_mday && \
1288 	(t1)->tm_hour == (t2)->tm_hour && \
1289 	(t1)->tm_min == (t2)->tm_min)
1290 
1291 #define	tm_setup(tp, yr, mon, dy, hr, min, dst) \
1292 	(tp)->tm_year = yr; \
1293 	(tp)->tm_mon = mon; \
1294 	(tp)->tm_mday = dy; \
1295 	(tp)->tm_hour = hr; \
1296 	(tp)->tm_min = min; \
1297 	(tp)->tm_isdst = dst; \
1298 	(tp)->tm_sec = 0; \
1299 	(tp)->tm_wday = 0; \
1300 	(tp)->tm_yday = 0;
1301 
1302 /*
1303  * modification for bugid 1104537. the second argument to next_time is
1304  * now the value of time(2) to be used. if this is 0, then use the
1305  * current time. otherwise, the second argument is the time from which to
1306  * calculate things. this is useful to correct situations where you've
1307  * gone backwards in time (I.e. the system's internal clock is correcting
1308  * itself backwards).
1309  */
1310 
1311 static time_t
1312 next_time(struct event *e, time_t tflag)
1313 {
1314 	/*
1315 	 * returns the integer time for the next occurance of event e.
1316 	 * the following fields have ranges as indicated:
1317 	 * PRGM  | min	hour	day of month	mon	day of week
1318 	 * ------|-------------------------------------------------------
1319 	 * cron  | 0-59	0-23	    1-31	1-12	0-6 (0=sunday)
1320 	 * time  | 0-59	0-23	    1-31	0-11	0-6 (0=sunday)
1321 	 * NOTE: this routine is hard to understand.
1322 	 */
1323 
1324 	struct tm *tm, ref_tm, tmp, tmp1, tmp2;
1325 	int tm_mon, tm_mday, tm_wday, wday, m, min, h, hr, carry, day, days,
1326 	d1, day1, carry1, d2, day2, carry2, daysahead, mon, yr, db, wd, today;
1327 	time_t t, ref_t, t1, t2, zone_start;
1328 	int fallback;
1329 	extern int days_btwn(int, int, int, int, int, int);
1330 
1331 	if (tflag == 0) {
1332 		t = time(NULL);	/* original way of doing things	*/
1333 	} else {
1334 		t =  tflag;
1335 	}
1336 
1337 	tm = &ref_tm;	/* use a local variable and call localtime_r() */
1338 	ref_t = t;	/* keep a copy of the reference time */
1339 
1340 recalc:
1341 	fallback = 0;
1342 
1343 	(void) localtime_r(&t, tm);
1344 
1345 	if (daylight) {
1346 		tmp = *tm;
1347 		tmp.tm_isdst = (tm->tm_isdst > 0 ? 0 : 1);
1348 		t1 = xmktime(&tmp);
1349 		/*
1350 		 * see if we will have timezone switch over, and clock will
1351 		 * fall back. zone_start will hold the time when it happens
1352 		 * (ie time of PST -> PDT switch over).
1353 		 */
1354 		if (tm->tm_isdst != tmp.tm_isdst &&
1355 		    (t1 - t) == (timezone - altzone) &&
1356 		    tm_cmp(tm, &tmp)) {
1357 			zone_start = get_switching_time(tmp.tm_isdst, t);
1358 			fallback = 1;
1359 		}
1360 	}
1361 
1362 	tm_mon = next_ge(tm->tm_mon+1, e->of.ct.month) - 1;	/* 0-11 */
1363 	tm_mday = next_ge(tm->tm_mday, e->of.ct.daymon);	/* 1-31 */
1364 	tm_wday = next_ge(tm->tm_wday, e->of.ct.dayweek);	/* 0-6	*/
1365 	today = TRUE;
1366 	if ((strcmp(e->of.ct.daymon, "*") == 0 && tm->tm_wday != tm_wday) ||
1367 	    (strcmp(e->of.ct.dayweek, "*") == 0 && tm->tm_mday != tm_mday) ||
1368 	    (tm->tm_mday != tm_mday && tm->tm_wday != tm_wday) ||
1369 	    (tm->tm_mon != tm_mon)) {
1370 		today = FALSE;
1371 	}
1372 	m = tm->tm_min + (t == ref_t ? 1 : 0);
1373 	if ((tm->tm_hour + 1) <= next_ge(tm->tm_hour, e->of.ct.hour)) {
1374 		m = 0;
1375 	}
1376 	min = next_ge(m%60, e->of.ct.minute);
1377 	carry = (min < m) ? 1 : 0;
1378 	h = tm->tm_hour + carry;
1379 	hr = next_ge(h%24, e->of.ct.hour);
1380 	carry = (hr < h) ? 1 : 0;
1381 
1382 	if (carry == 0 && today) {
1383 		/* this event must occur today */
1384 		tm_setup(&tmp, tm->tm_year, tm->tm_mon, tm->tm_mday,
1385 			hr, min, tm->tm_isdst);
1386 		tmp1 = tmp;
1387 		if ((t1 = xmktime(&tmp1)) == (time_t)-1) {
1388 			return (0);
1389 		}
1390 		if (daylight && tmp.tm_isdst != tmp1.tm_isdst) {
1391 			/* In case we are falling back */
1392 			if (fallback) {
1393 				/* we may need to run the job once more. */
1394 				t = zone_start;
1395 				goto recalc;
1396 			}
1397 
1398 			/*
1399 			 * In case we are not in falling back period,
1400 			 * calculate the time assuming the DST. If the
1401 			 * date/time is not altered by mktime, it is the
1402 			 * time to execute the job.
1403 			 */
1404 			tmp2 = tmp;
1405 			tmp2.tm_isdst = tmp1.tm_isdst;
1406 			if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
1407 				return (0);
1408 			}
1409 			if (tmp1.tm_isdst == tmp2.tm_isdst &&
1410 			    tm_cmp(&tmp, &tmp2)) {
1411 				/*
1412 				 * We got a valid time.
1413 				 */
1414 				return (t1);
1415 			} else {
1416 				/*
1417 				 * If the date does not match even if
1418 				 * we assume the alternate timezone, then
1419 				 * it must be the invalid time. eg
1420 				 * 2am while switching 1:59am to 3am.
1421 				 * t1 should point the time before the
1422 				 * switching over as we've calculate the
1423 				 * time with assuming alternate zone.
1424 				 */
1425 				if (tmp1.tm_isdst != tmp2.tm_isdst) {
1426 					t = get_switching_time(tmp1.tm_isdst,
1427 						t1);
1428 				} else {
1429 					/* does this really happen? */
1430 					t = get_switching_time(tmp1.tm_isdst,
1431 						t1 - abs(timezone - altzone));
1432 				}
1433 				if (t == (time_t)-1)
1434 					return (0);
1435 			}
1436 			goto recalc;
1437 		}
1438 		if (tm_cmp(&tmp, &tmp1)) {
1439 			/* got valid time */
1440 			return (t1);
1441 		} else {
1442 			/*
1443 			 * This should never happen, but just in
1444 			 * case, we fall back to the old code.
1445 			 */
1446 			if (tm->tm_min > min) {
1447 				t += (time_t)(hr-tm->tm_hour-1) * HOUR +
1448 					(time_t)(60-tm->tm_min+min) * MINUTE;
1449 			} else {
1450 				t += (time_t)(hr-tm->tm_hour) * HOUR +
1451 					(time_t)(min-tm->tm_min) * MINUTE;
1452 			}
1453 			t1 = t;
1454 			t -= (time_t)tm->tm_sec;
1455 			(void) localtime_r(&t, &tmp);
1456 			if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
1457 				t -= (timezone - altzone);
1458 			return ((t <= ref_t) ? t1 : t);
1459 		}
1460 	}
1461 
1462 	/*
1463 	 * Job won't run today, however if we have a switch over within
1464 	 * one hour and we will have one hour time drifting back in this
1465 	 * period, we may need to run the job one more time if the job was
1466 	 * set to run on this hour of clock.
1467 	 */
1468 	if (fallback) {
1469 		t = zone_start;
1470 		goto recalc;
1471 	}
1472 
1473 	min = next_ge(0, e->of.ct.minute);
1474 	hr = next_ge(0, e->of.ct.hour);
1475 
1476 	/*
1477 	 * calculate the date of the next occurance of this event, which
1478 	 * will be on a different day than the current
1479 	 */
1480 
1481 	/* check monthly day specification	*/
1482 	d1 = tm->tm_mday+1;
1483 	day1 = next_ge((d1-1)%days_in_mon(tm->tm_mon, tm->tm_year)+1,
1484 			e->of.ct.daymon);
1485 	carry1 = (day1 < d1) ? 1 : 0;
1486 
1487 	/* check weekly day specification	*/
1488 	d2 = tm->tm_wday+1;
1489 	wday = next_ge(d2%7, e->of.ct.dayweek);
1490 	if (wday < d2)
1491 		daysahead = 7 - d2 + wday;
1492 	else
1493 		daysahead = wday - d2;
1494 	day2 = (d1+daysahead-1)%days_in_mon(tm->tm_mon, tm->tm_year)+1;
1495 	carry2 = (day2 < d1) ? 1 : 0;
1496 
1497 	/*
1498 	 *	based on their respective specifications, day1, and day2 give
1499 	 *	the day of the month for the next occurance of this event.
1500 	 */
1501 	if ((strcmp(e->of.ct.daymon, "*") == 0) &&
1502 	    (strcmp(e->of.ct.dayweek, "*") != 0)) {
1503 		day1 = day2;
1504 		carry1 = carry2;
1505 	}
1506 	if ((strcmp(e->of.ct.daymon, "*") != 0) &&
1507 	    (strcmp(e->of.ct.dayweek, "*") == 0)) {
1508 		day2 = day1;
1509 		carry2 = carry1;
1510 	}
1511 
1512 	yr = tm->tm_year;
1513 	if ((carry1 && carry2) || (tm->tm_mon != tm_mon)) {
1514 		/* event does not occur in this month	*/
1515 		m = tm->tm_mon+1;
1516 		mon = next_ge(m%12+1, e->of.ct.month) - 1;	/* 0..11 */
1517 		carry = (mon < m) ? 1 : 0;
1518 		yr += carry;
1519 		/* recompute day1 and day2	*/
1520 		day1 = next_ge(1, e->of.ct.daymon);
1521 		db = days_btwn(tm->tm_mon, tm->tm_mday, tm->tm_year, mon,
1522 				1, yr) + 1;
1523 		wd = (tm->tm_wday+db)%7;
1524 		/* wd is the day of the week of the first of month mon	*/
1525 		wday = next_ge(wd, e->of.ct.dayweek);
1526 		if (wday < wd)
1527 			day2 = 1 + 7 - wd + wday;
1528 		else
1529 			day2 = 1 + wday - wd;
1530 		if ((strcmp(e->of.ct.daymon, "*") != 0) &&
1531 		    (strcmp(e->of.ct.dayweek, "*") == 0))
1532 			day2 = day1;
1533 		if ((strcmp(e->of.ct.daymon, "*") == 0) &&
1534 		    (strcmp(e->of.ct.dayweek, "*") != 0))
1535 			day1 = day2;
1536 		day = (day1 < day2) ? day1 : day2;
1537 	} else {			/* event occurs in this month	*/
1538 		mon = tm->tm_mon;
1539 		if (!carry1 && !carry2)
1540 			day = (day1 < day2) ? day1 : day2;
1541 		else if (!carry1)
1542 			day = day1;
1543 		else
1544 			day = day2;
1545 	}
1546 
1547 	/*
1548 	 * now that we have the min, hr, day, mon, yr of the next event,
1549 	 * figure out what time that turns out to be.
1550 	 */
1551 	tm_setup(&tmp, yr, mon, day, hr, min, -1);
1552 	tmp2 = tmp;
1553 	if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
1554 		return (0);
1555 	}
1556 	if (tm_cmp(&tmp, &tmp2)) {
1557 		/*
1558 		 * mktime returns clock for the current time zone. If the
1559 		 * target date was in fallback period, it needs to be adjusted
1560 		 * to the time comes first.
1561 		 * Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
1562 		 * mktime returns the time in PST, but 1:30am in PDT comes
1563 		 * first. So reverse the tm_isdst, and see if we have such
1564 		 * time/date.
1565 		 */
1566 		if (daylight) {
1567 			int dst = tmp2.tm_isdst;
1568 
1569 			tmp2 = tmp;
1570 			tmp2.tm_isdst = (dst > 0 ? 0 : 1);
1571 			if ((t2 = xmktime(&tmp2)) == (time_t)-1) {
1572 				return (0);
1573 			}
1574 			if (tm_cmp(&tmp, &tmp2)) {
1575 				/*
1576 				 * same time/date found in the opposite zone.
1577 				 * check the clock to see which comes early.
1578 				 */
1579 				if (t2 > ref_t && t2 < t1) {
1580 					t1 = t2;
1581 				}
1582 			}
1583 		}
1584 		return (t1);
1585 	} else {
1586 		/*
1587 		 * mktime has set different time/date for the given date.
1588 		 * This means that the next job is scheduled to be run on the
1589 		 * invalid time. There are three possible invalid date/time.
1590 		 * 1. Non existing day of the month. such as April 31th.
1591 		 * 2. Feb 29th in the non-leap year.
1592 		 * 3. Time gap during the DST switch over.
1593 		 */
1594 		d1 = days_in_mon(mon, yr);
1595 		if ((mon != 1 && day > d1) || (mon == 1 && day > 29)) {
1596 			/*
1597 			 * see if we have got a specific date which
1598 			 * is invalid.
1599 			 */
1600 			if (strcmp(e->of.ct.dayweek, "*") == 0 &&
1601 			    mon == (next_ge((mon+1)%12+1, e->of.ct.month)-1) &&
1602 			    day <= next_ge(1, e->of.ct.daymon)) {
1603 				/* job never run */
1604 				return (0);
1605 			}
1606 			/*
1607 			 * Since the day has gone invalid, we need to go to
1608 			 * next month, and recalcuate the first occurrence.
1609 			 * eg the cron tab such as:
1610 			 * 0 0 1,15,31 1,2,3,4,5 * /usr/bin....
1611 			 * 2/31 is invalid, so the next job is 3/1.
1612 			 */
1613 			tmp2 = tmp;
1614 			tmp2.tm_min = 0;
1615 			tmp2.tm_hour = 0;
1616 			tmp2.tm_mday = 1; /* 1st day of the month */
1617 			if (mon == 11) {
1618 				tmp2.tm_mon = 0;
1619 				tmp2.tm_year = yr + 1;
1620 			} else {
1621 				tmp2.tm_mon = mon + 1;
1622 			}
1623 			if ((t = xmktime(&tmp2)) == (time_t)-1) {
1624 				return (0);
1625 			}
1626 		} else if (mon == 1 && day > d1) {
1627 			/*
1628 			 * ie 29th in the non-leap year. Forwarding the
1629 			 * clock to Feb 29th 00:00 (March 1st), and recalculate
1630 			 * the next time.
1631 			 */
1632 			tmp2 = tmp;
1633 			tmp2.tm_min = 0;
1634 			tmp2.tm_hour = 0;
1635 			if ((t = xmktime(&tmp2)) == (time_t)-1) {
1636 				return (0);
1637 			}
1638 		} else if (daylight) {
1639 			/*
1640 			 * Non existing time, eg 2am PST during summer time
1641 			 * switch.
1642 			 * We need to get the correct isdst which we are
1643 			 * swithing to, by adding time difference to make sure
1644 			 * that t2 is in the zone being switched.
1645 			 */
1646 			t2 = t1;
1647 			t2 += abs(timezone - altzone);
1648 			(void) localtime_r(&t2, &tmp2);
1649 			zone_start = get_switching_time(tmp2.tm_isdst,
1650 						t1 - abs(timezone - altzone));
1651 			if (zone_start == (time_t)-1) {
1652 				return (0);
1653 			}
1654 			t = zone_start;
1655 		} else {
1656 			/*
1657 			 * This should never happen, but fall back to the
1658 			 * old code.
1659 			 */
1660 			days = days_btwn(tm->tm_mon,
1661 				tm->tm_mday, tm->tm_year, mon, day, yr);
1662 			t += (time_t)(23-tm->tm_hour)*HOUR
1663 				+ (time_t)(60-tm->tm_min)*MINUTE
1664 				+ (time_t)hr*HOUR + (time_t)min*MINUTE
1665 				+ (time_t)days*DAY;
1666 			t1 = t;
1667 			t -= (time_t)tm->tm_sec;
1668 			(void) localtime_r(&t, &tmp);
1669 			if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
1670 				t -= (timezone - altzone);
1671 			return (t <= ref_t ? t1 : t);
1672 		}
1673 		goto recalc;
1674 	}
1675 	/*NOTREACHED*/
1676 }
1677 
1678 /*
1679  * This returns TOD in time_t that zone switch will happen, and this
1680  * will be called when clock fallback is about to happen.
1681  * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
1682  * will fall back to 1:00 PDT. So this function will be called only
1683  * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
1684  * First goes through the common time differences to see if zone
1685  * switch happens at those minutes later. If not, check every minutes
1686  * until 6 hours ahead see if it happens(We might have 45minutes
1687  * fallback).
1688  */
1689 static time_t
1690 get_switching_time(int to_dst, time_t t_ref)
1691 {
1692 	time_t t, t1;
1693 	struct tm tmp, tmp1;
1694 	int hints[] = { 60, 120, 30, 90, 0}; /* minutes */
1695 	int i;
1696 
1697 	(void) localtime_r(&t_ref, &tmp);
1698 	tmp1 = tmp;
1699 	tmp1.tm_sec = 0;
1700 	tmp1.tm_min = 0;
1701 	if ((t = xmktime(&tmp1)) == (time_t)-1)
1702 		return ((time_t)-1);
1703 
1704 	/* fast path */
1705 	for (i = 0; hints[i] != 0; i++) {
1706 		t1 = t + hints[i] * 60;
1707 		(void) localtime_r(&t1, &tmp1);
1708 		if (tmp1.tm_isdst == to_dst) {
1709 			t1--;
1710 			(void) localtime_r(&t1, &tmp1);
1711 			if (tmp1.tm_isdst != to_dst) {
1712 				return (t1 + 1);
1713 			}
1714 		}
1715 	}
1716 
1717 	/* ugly, but don't know other than this. */
1718 	tmp1 = tmp;
1719 	tmp1.tm_sec = 0;
1720 	if ((t = xmktime(&tmp1)) == (time_t)-1)
1721 		return ((time_t)-1);
1722 	while (t < (t_ref + 6*60*60)) { /* 6 hours should be enough */
1723 		t += 60; /* at least one minute, I assume */
1724 		(void) localtime_r(&t, &tmp);
1725 		if (tmp.tm_isdst == to_dst)
1726 			return (t);
1727 	}
1728 	return ((time_t)-1);
1729 }
1730 
1731 static time_t
1732 xmktime(struct tm *tmp)
1733 {
1734 	time_t ret;
1735 
1736 	if ((ret = mktime(tmp)) == (time_t)-1) {
1737 		if (errno == EOVERFLOW) {
1738 			return ((time_t)-1);
1739 		}
1740 		crabort("internal error: mktime failed",
1741 			REMOVE_FIFO|CONSOLE_MSG);
1742 	}
1743 	return (ret);
1744 }
1745 
1746 #define	DUMMY	100
1747 
1748 static int
1749 next_ge(int current, char *list)
1750 {
1751 	/*
1752 	 * list is a character field as in a crontab file;
1753 	 * for example: "40, 20, 50-10"
1754 	 * next_ge returns the next number in the list that is
1755 	 * greater than  or equal to current. if no numbers of list
1756 	 * are >= current, the smallest element of list is returned.
1757 	 * NOTE: current must be in the appropriate range.
1758 	 */
1759 
1760 	char *ptr;
1761 	int n, n2, min, min_gt;
1762 
1763 	if (strcmp(list, "*") == 0)
1764 		return (current);
1765 	ptr = list;
1766 	min = DUMMY;
1767 	min_gt = DUMMY;
1768 	for (;;) {
1769 		if ((n = (int)num(&ptr)) == current)
1770 			return (current);
1771 		if (n < min)
1772 			min = n;
1773 		if ((n > current) && (n < min_gt))
1774 			min_gt = n;
1775 		if (*ptr == '-') {
1776 			ptr++;
1777 			if ((n2 = (int)num(&ptr)) > n) {
1778 				if ((current > n) && (current <= n2))
1779 					return (current);
1780 			} else {	/* range that wraps around */
1781 				if (current > n)
1782 					return (current);
1783 				if (current <= n2)
1784 					return (current);
1785 			}
1786 		}
1787 		if (*ptr == '\0')
1788 			break;
1789 		ptr += 1;
1790 	}
1791 	if (min_gt != DUMMY)
1792 		return (min_gt);
1793 	else
1794 		return (min);
1795 }
1796 
1797 static void
1798 free_if_unused(struct usr *u)
1799 {
1800 	struct usr *cur, *prev;
1801 	/*
1802 	 *	To make sure a usr structure is idle we must check that
1803 	 *	there are no at jobs queued for the user; the user does
1804 	 *	not have a crontab, and also that there are no running at
1805 	 *	or cron jobs (since the runinfo structure also has a
1806 	 *	pointer to the usr structure).
1807 	 */
1808 	if (!u->ctexists && u->atevents == NULL &&
1809 	    u->cruncnt == 0 && u->aruncnt == 0) {
1810 #ifdef DEBUG
1811 		(void) fprintf(stderr, "%s removed from usr list\n", u->name);
1812 #endif
1813 		for (cur = uhead, prev = NULL;
1814 			cur != u;
1815 			prev = cur, cur = cur->nextusr) {
1816 			if (cur == NULL) {
1817 				return;
1818 			}
1819 		}
1820 
1821 		if (prev == NULL)
1822 			uhead = u->nextusr;
1823 		else
1824 			prev->nextusr = u->nextusr;
1825 		free(u->name);
1826 		free(u->home);
1827 		free(u);
1828 	}
1829 }
1830 
1831 static void
1832 del_atjob(char *name, char *usrname)
1833 {
1834 
1835 	struct	event	*e, *eprev;
1836 	struct	usr	*u;
1837 
1838 	if ((u = find_usr(usrname)) == NULL)
1839 		return;
1840 	e = u->atevents;
1841 	eprev = NULL;
1842 	while (e != NULL) {
1843 		if (strcmp(name, e->cmd) == 0) {
1844 			if (next_event == e)
1845 				next_event = NULL;
1846 			if (eprev == NULL)
1847 				u->atevents = e->link;
1848 			else
1849 				eprev->link = e->link;
1850 			el_remove(e->of.at.eventid, 1);
1851 			free(e->cmd);
1852 			free(e);
1853 			break;
1854 		} else {
1855 			eprev = e;
1856 			e = e->link;
1857 		}
1858 	}
1859 
1860 	free_if_unused(u);
1861 }
1862 
1863 static void
1864 del_ctab(char *name)
1865 {
1866 
1867 	struct	usr *u;
1868 
1869 	if ((u = find_usr(name)) == NULL)
1870 		return;
1871 	rm_ctevents(u);
1872 	el_remove(u->ctid, 0);
1873 	u->ctid = 0;
1874 	u->ctexists = 0;
1875 
1876 	free_if_unused(u);
1877 }
1878 
1879 static void
1880 rm_ctevents(struct usr *u)
1881 {
1882 	struct event *e2, *e3;
1883 
1884 	/*
1885 	 * see if the next event (to be run by cron) is a cronevent
1886 	 * owned by this user.
1887 	 */
1888 
1889 	if ((next_event != NULL) &&
1890 	    (next_event->etype == CRONEVENT) &&
1891 	    (next_event->u == u)) {
1892 		next_event = NULL;
1893 	}
1894 	e2 = u->ctevents;
1895 	while (e2 != NULL) {
1896 		free(e2->cmd);
1897 		free(e2->of.ct.minute);
1898 		free(e2->of.ct.hour);
1899 		free(e2->of.ct.daymon);
1900 		free(e2->of.ct.month);
1901 		free(e2->of.ct.dayweek);
1902 		if (e2->of.ct.input != NULL)
1903 			free(e2->of.ct.input);
1904 		e3 = e2->link;
1905 		free(e2);
1906 		e2 = e3;
1907 	}
1908 	u->ctevents = NULL;
1909 }
1910 
1911 
1912 static struct usr *
1913 find_usr(char *uname)
1914 {
1915 	struct usr *u;
1916 
1917 	u = uhead;
1918 	while (u != NULL) {
1919 		if (strcmp(u->name, uname) == 0)
1920 			return (u);
1921 		u = u->nextusr;
1922 	}
1923 	return (NULL);
1924 }
1925 
1926 /*
1927  * Execute cron command or at/batch job.
1928  * If ever a premature return is added to this function pay attention to
1929  * free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
1930  */
1931 static int
1932 ex(struct event *e)
1933 {
1934 	int r;
1935 	int fd;
1936 	pid_t rfork;
1937 	FILE *atcmdfp;
1938 	char mailvar[4];
1939 	char *at_cmdfile = NULL;
1940 	struct stat buf;
1941 	struct queue *qp;
1942 	struct runinfo *rp;
1943 	struct project proj, *pproj = NULL;
1944 	char mybuf[PROJECT_BUFSZ];
1945 	char mybuf2[PROJECT_BUFSZ];
1946 	char *tmpfile;
1947 	FILE *fptr;
1948 	time_t dhltime;
1949 	projid_t projid;
1950 	int projflag = 0;
1951 
1952 	qp = &qt[e->etype];	/* set pointer to queue defs */
1953 	if (qp->nrun >= qp->njob) {
1954 		msg("%c queue max run limit reached", e->etype+'a');
1955 		resched(qp->nwait);
1956 		return (0);
1957 	}
1958 	rp = rinfo_get(0); /* allocating a new runinfo struct */
1959 
1960 #ifdef ATLIMIT
1961 	if ((e->u)->uid != 0 && (e->u)->aruncnt >= ATLIMIT) {
1962 		msg("ATLIMIT (%d) reached for uid %d",
1963 			ATLIMIT, (e->u)->uid);
1964 		rinfo_free(rp);
1965 		resched(qp->nwait);
1966 		return (0);
1967 	}
1968 #endif
1969 #ifdef CRONLIMIT
1970 	if ((e->u)->uid != 0 && (e->u)->cruncnt >= CRONLIMIT) {
1971 		msg("CRONLIMIT (%d) reached for uid %d",
1972 			CRONLIMIT, (e->u)->uid);
1973 		rinfo_free(rp);
1974 		resched(qp->nwait);
1975 		return (0);
1976 	}
1977 #endif
1978 	if ((e->u)->uid == 0) {	/* set default path */
1979 		/* path settable in defaults file */
1980 		envinit[2] = supath;
1981 	} else {
1982 		envinit[2] = path;
1983 	}
1984 
1985 	/*
1986 	 * the tempnam() function uses malloc(3C) to allocate space for the
1987 	 * constructed file name, and returns a pointer to this area, which
1988 	 * is assigned to rp->outfile. Here rp->outfile is not overwritten.
1989 	 */
1990 
1991 	rp->outfile = tempnam(TMPDIR, PFX);
1992 	rp->jobtype = e->etype;
1993 	if (e->etype == CRONEVENT) {
1994 		rp->jobname = xmalloc(strlen(e->cmd)+1);
1995 		(void) strcpy(rp->jobname, e->cmd);
1996 		/* "cron" jobs only produce mail if there's output */
1997 		rp->mailwhendone = 0;
1998 	} else {
1999 		at_cmdfile = xmalloc(strlen(ATDIR)+strlen(e->cmd)+2);
2000 		(void) sprintf(at_cmdfile, "%s/%s", ATDIR, e->cmd);
2001 		if ((atcmdfp = fopen(at_cmdfile, "r")) == NULL) {
2002 			if (errno == ENAMETOOLONG) {
2003 				if (chdir(ATDIR) == 0)
2004 					cron_unlink(e->cmd);
2005 			} else {
2006 				cron_unlink(at_cmdfile);
2007 			}
2008 			mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECAT);
2009 			free(at_cmdfile);
2010 			rinfo_free(rp);
2011 			return (0);
2012 		}
2013 		rp->jobname = xmalloc(strlen(at_cmdfile)+1);
2014 		(void) strcpy(rp->jobname, at_cmdfile);
2015 
2016 		/*
2017 		 * Skip over the first two lines.
2018 		 */
2019 		(void) fscanf(atcmdfp, "%*[^\n]\n");
2020 		(void) fscanf(atcmdfp, "%*[^\n]\n");
2021 		if (fscanf(atcmdfp, ": notify by mail: %3s%*[^\n]\n",
2022 			mailvar) == 1) {
2023 			/*
2024 			 * Check to see if we should always send mail
2025 			 * to the owner.
2026 			 */
2027 			rp->mailwhendone = (strcmp(mailvar, "yes") == 0);
2028 		} else {
2029 			rp->mailwhendone = 0;
2030 		}
2031 
2032 		if (fscanf(atcmdfp, "\n: project: %d\n", &projid) == 1) {
2033 			projflag = 1;
2034 		}
2035 		(void) fclose(atcmdfp);
2036 	}
2037 
2038 	/*
2039 	 * we make sure that the system time
2040 	 * hasn't drifted backwards. if it has, el_add() is now
2041 	 * called, to make sure that the event queue is back in order,
2042 	 * and we set the delayed flag. cron will pick up the request
2043 	 * later on at the proper time.
2044 	 */
2045 	dhltime = time(NULL);
2046 	if ((dhltime - e->time) < 0) {
2047 		msg("clock time drifted backwards!\n");
2048 		if (next_event->etype == CRONEVENT) {
2049 			msg("correcting cron event\n");
2050 			next_event->time = next_time(next_event, dhltime);
2051 			el_add(next_event, next_event->time,
2052 				(next_event->u)->ctid);
2053 		} else { /* etype == ATEVENT */
2054 			msg("correcting batch event\n");
2055 			el_add(next_event, next_event->time,
2056 				next_event->of.at.eventid);
2057 		}
2058 		delayed++;
2059 		t_old = time(NULL);
2060 		free(at_cmdfile);
2061 		rinfo_free(rp);
2062 		return (0);
2063 	}
2064 
2065 	if ((rfork = fork()) == (pid_t)-1) {
2066 		reap_child();
2067 		if ((rfork = fork()) == (pid_t)-1) {
2068 			msg("cannot fork");
2069 			free(at_cmdfile);
2070 			rinfo_free(rp);
2071 			resched(60);
2072 			(void) sleep(30);
2073 			return (0);
2074 		}
2075 	}
2076 	if (rfork) {		/* parent process */
2077 		contract_abandon_latest(rfork);
2078 
2079 		++qp->nrun;
2080 		rp->pid = rfork;
2081 		rp->que = e->etype;
2082 		if (e->etype != CRONEVENT)
2083 			(e->u)->aruncnt++;
2084 		else
2085 			(e->u)->cruncnt++;
2086 		rp->rusr = (e->u);
2087 		logit(BCHAR, rp, 0);
2088 		free(at_cmdfile);
2089 
2090 		return (0);
2091 	}
2092 
2093 	child_sigreset();
2094 	contract_clear_template();
2095 
2096 	if (e->etype != CRONEVENT) {
2097 		/* open jobfile as stdin to shell */
2098 		if (stat(at_cmdfile, &buf)) {
2099 			if (errno == ENAMETOOLONG) {
2100 				if (chdir(ATDIR) == 0)
2101 					cron_unlink(e->cmd);
2102 			} else
2103 				cron_unlink(at_cmdfile);
2104 			mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2105 			exit(1);
2106 		}
2107 		if (!(buf.st_mode&ISUID)) {
2108 			/*
2109 			 * if setuid bit off, original owner has
2110 			 * given this file to someone else
2111 			 */
2112 			cron_unlink(at_cmdfile);
2113 			exit(1);
2114 		}
2115 		if ((fd = open(at_cmdfile, O_RDONLY)) == -1) {
2116 			mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2117 			cron_unlink(at_cmdfile);
2118 			exit(1);
2119 		}
2120 		if (fd != 0) {
2121 			(void) dup2(fd, 0);
2122 			(void) close(fd);
2123 		}
2124 		/*
2125 		 * retrieve the project id of the at job and convert it
2126 		 * to a project name.  fail if it's not a valid project
2127 		 * or if the user isn't a member of the project.
2128 		 */
2129 		if (projflag == 1) {
2130 			if ((pproj = getprojbyid(projid, &proj,
2131 			    (void *)&mybuf, sizeof (mybuf))) == NULL ||
2132 			    !inproj(e->u->name, pproj->pj_name,
2133 			    mybuf2, sizeof (mybuf2))) {
2134 				cron_unlink(at_cmdfile);
2135 				mail((e->u)->name, BADPROJID, ERR_CANTEXECAT);
2136 				exit(1);
2137 			}
2138 		}
2139 	}
2140 
2141 	/*
2142 	 * Put process in a new session, and create a new task.
2143 	 */
2144 	if (setsid() < 0) {
2145 		msg("setsid failed with errno = %d. job failed (%s)"
2146 		    " for user %s", errno, e->cmd, e->u->name);
2147 		if (e->etype != CRONEVENT)
2148 			cron_unlink(at_cmdfile);
2149 		exit(1);
2150 	}
2151 
2152 	/*
2153 	 * set correct user identification and check his account
2154 	 */
2155 	r = set_user_cred(e->u, pproj);
2156 	if (r == VUC_EXPIRED) {
2157 		msg("user (%s) account is expired", e->u->name);
2158 		audit_cron_user_acct_expired(e->u->name);
2159 		clean_out_user(e->u);
2160 		exit(1);
2161 	}
2162 	if (r == VUC_NEW_AUTH) {
2163 		msg("user (%s) password has expired", e->u->name);
2164 		audit_cron_user_acct_expired(e->u->name);
2165 		clean_out_user(e->u);
2166 		exit(1);
2167 	}
2168 	if (r != VUC_OK) {
2169 		msg("bad user (%s)", e->u->name);
2170 		audit_cron_bad_user(e->u->name);
2171 		clean_out_user(e->u);
2172 		exit(1);
2173 	}
2174 	/*
2175 	 * check user and initialize the supplementary group access list.
2176 	 * bugid 1230784: deleted from parent to avoid cron hang. Now
2177 	 * only child handles the call.
2178 	 */
2179 
2180 	if (verify_user_cred(e->u) != VUC_OK ||
2181 	    setgid(e->u->gid) == -1 ||
2182 	    initgroups(e->u->name, e->u->gid) == -1) {
2183 		msg("bad user (%s) or setgid failed (%s)",
2184 			e->u->name, e->u->name);
2185 		audit_cron_bad_user(e->u->name);
2186 		clean_out_user(e->u);
2187 		exit(1);
2188 	}
2189 
2190 	if (e->etype != CRONEVENT) {
2191 		r = audit_cron_session(e->u->name, NULL,
2192 					e->u->uid, e->u->gid,
2193 					at_cmdfile);
2194 		cron_unlink(at_cmdfile);
2195 	} else {
2196 		r = audit_cron_session(e->u->name, CRONDIR,
2197 					e->u->uid, e->u->gid,
2198 					NULL);
2199 	}
2200 	if (r != 0) {
2201 		msg("cron audit problem. job failed (%s) for user %s",
2202 			e->cmd, e->u->name);
2203 		exit(1);
2204 	}
2205 
2206 	audit_cron_new_job(e->cmd, e->etype, (void *)e);
2207 
2208 	if (setuid(e->u->uid) == -1)  {
2209 		msg("setuid failed (%s)", e->u->name);
2210 		clean_out_user(e->u);
2211 		exit(1);
2212 	}
2213 
2214 	if (e->etype == CRONEVENT) {
2215 		/* check for standard input to command	*/
2216 		if (e->of.ct.input != NULL) {
2217 			if ((tmpfile = strdup(TMPINFILE)) == NULL) {
2218 				mail((e->u)->name, MALLOCERR,
2219 					ERR_CANTEXECCRON);
2220 				exit(1);
2221 			}
2222 			if ((fd = mkstemp(tmpfile)) == -1 ||
2223 				(fptr = fdopen(fd, "w")) == NULL) {
2224 				mail((e->u)->name, NOSTDIN,
2225 					ERR_CANTEXECCRON);
2226 				cron_unlink(tmpfile);
2227 				free(tmpfile);
2228 				exit(1);
2229 			}
2230 			if ((fwrite(e->of.ct.input, sizeof (char),
2231 				strlen(e->of.ct.input), fptr)) !=
2232 					strlen(e->of.ct.input)) {
2233 				mail((e->u)->name, NOSTDIN, ERR_CANTEXECCRON);
2234 				cron_unlink(tmpfile);
2235 				free(tmpfile);
2236 				(void) close(fd);
2237 				(void) fclose(fptr);
2238 				exit(1);
2239 			}
2240 			if (fseek(fptr, (off_t)0, SEEK_SET) != -1) {
2241 				if (fd != 0) {
2242 					(void) dup2(fd, 0);
2243 					(void) close(fd);
2244 				}
2245 			}
2246 			cron_unlink(tmpfile);
2247 			free(tmpfile);
2248 			(void) fclose(fptr);
2249 		} else if ((fd = open("/dev/null", O_RDONLY)) > 0) {
2250 			(void) dup2(fd, 0);
2251 			(void) close(fd);
2252 		}
2253 	}
2254 
2255 	/* redirect stdout and stderr for the shell	*/
2256 	if ((fd = open(rp->outfile, O_WRONLY|O_CREAT|O_EXCL, OUTMODE)) == 1)
2257 		fd = open("/dev/null", O_WRONLY);
2258 
2259 	if (fd >= 0 && fd != 1)
2260 		(void) dup2(fd, 1);
2261 
2262 	if (fd >= 0 && fd != 2) {
2263 		(void) dup2(fd, 2);
2264 		if (fd != 1)
2265 			(void) close(fd);
2266 	}
2267 
2268 	(void) strlcat(homedir, (e->u)->home, sizeof (homedir));
2269 	(void) strlcat(logname, (e->u)->name, sizeof (logname));
2270 	environ = envinit;
2271 	if (chdir((e->u)->home) == -1) {
2272 		mail((e->u)->name, CANTCDHOME,
2273 			e->etype == CRONEVENT ? ERR_CANTEXECCRON :
2274 				ERR_CANTEXECAT);
2275 		exit(1);
2276 	}
2277 #ifdef TESTING
2278 	exit(1);
2279 #endif
2280 	/*
2281 	 * make sure that all file descriptors EXCEPT 0, 1 and 2
2282 	 * will be closed.
2283 	 */
2284 	closefrom(3);
2285 
2286 	if ((e->u)->uid != 0)
2287 		(void) nice(qp->nice);
2288 	if (e->etype == CRONEVENT)
2289 		(void) execl(SHELL, "sh", "-c", e->cmd, 0);
2290 	else		/* type == ATEVENT */
2291 		(void) execl(SHELL, "sh", 0);
2292 	mail((e->u)->name, CANTEXECSH,
2293 		e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT);
2294 	exit(1);
2295 	/*NOTREACHED*/
2296 }
2297 
2298 static int
2299 idle(long t)
2300 {
2301 	time_t	now;
2302 
2303 	while (t > 0L) {
2304 
2305 		if (msg_wait(t) != 0) {
2306 			/* we need to run next job immediately */
2307 			return (0);
2308 		}
2309 
2310 		reap_child();
2311 
2312 		now = time(NULL);
2313 		if (last_time > now) {
2314 			/* clock has been reset */
2315 			return (1);
2316 		}
2317 
2318 		if (next_event == NULL && !el_empty()) {
2319 			next_event = (struct event *)el_first();
2320 		}
2321 		if (next_event == NULL)
2322 			t = INFINITY;
2323 		else
2324 			t = (long)next_event->time - now;
2325 	}
2326 	return (0);
2327 }
2328 
2329 /*
2330  * This used to be in the idle(), but moved to the separate function.
2331  * This called from various place when cron needs to reap the
2332  * child. It includes the situation that cron hit maxrun, and needs
2333  * to reschedule the job.
2334  */
2335 static void
2336 reap_child()
2337 {
2338 	pid_t	pid;
2339 	int	prc;
2340 	struct	runinfo	*rp;
2341 
2342 	for (;;) {
2343 		pid = waitpid((pid_t)-1, &prc, WNOHANG);
2344 		if (pid <= 0)
2345 			break;
2346 #ifdef DEBUG
2347 		fprintf(stderr,
2348 			"wait returned %x for process %d\n", prc, pid);
2349 #endif
2350 		if ((rp = rinfo_get(pid)) == NULL) {
2351 			if (miscpid_delete(pid) == 0) {
2352 				/* not found in anywhere */
2353 				msg(PIDERR, pid);
2354 			}
2355 		} else if (rp->que == ZOMB) {
2356 			(void) unlink(rp->outfile);
2357 			rinfo_free(rp);
2358 		} else {
2359 			cleanup(rp, prc);
2360 		}
2361 	}
2362 }
2363 
2364 static void
2365 cleanup(struct runinfo *pr, int rc)
2366 {
2367 	int	nextfork = 1;
2368 	struct	usr	*p;
2369 	struct	stat	buf;
2370 
2371 	logit(ECHAR, pr, rc);
2372 	--qt[pr->que].nrun;
2373 	p = pr->rusr;
2374 	if (pr->que != CRONEVENT)
2375 		--p->aruncnt;
2376 	else
2377 		--p->cruncnt;
2378 
2379 	if (lstat(pr->outfile, &buf) == 0) {
2380 		if (!S_ISLNK(buf.st_mode) &&
2381 		    (buf.st_size > 0 || pr->mailwhendone)) {
2382 			/* mail user stdout and stderr */
2383 			for (;;) {
2384 				if ((pr->pid = fork()) < 0) {
2385 					/*
2386 					 * if fork fails try forever in doubling
2387 					 * retry times, up to 16 seconds
2388 					 */
2389 					(void) sleep(nextfork);
2390 					if (nextfork < 16)
2391 						nextfork += nextfork;
2392 					continue;
2393 				} else if (pr->pid == 0) {
2394 					child_sigreset();
2395 					contract_clear_template();
2396 
2397 					mail_result(p, pr, buf.st_size);
2398 					/* NOTREACHED */
2399 				} else {
2400 					contract_abandon_latest(pr->pid);
2401 					pr->que = ZOMB;
2402 					break;
2403 				}
2404 			}
2405 		} else {
2406 			(void) unlink(pr->outfile);
2407 			rinfo_free(pr);
2408 		}
2409 	} else {
2410 		rinfo_free(pr);
2411 	}
2412 
2413 	free_if_unused(p);
2414 }
2415 
2416 /*
2417  * Mail stdout and stderr of a job to user. Get uid for real user and become
2418  * that person. We do this so that mail won't come from root since this
2419  * could be a security hole. If failure, quit - don't send mail as root.
2420  */
2421 static void
2422 mail_result(struct usr *p, struct runinfo *pr, size_t filesize)
2423 {
2424 	struct	passwd	*ruser_ids;
2425 	FILE	*mailpipe;
2426 	FILE	*st;
2427 	struct utsname	name;
2428 	int	nbytes;
2429 	char	iobuf[BUFSIZ];
2430 	char	*cmd;
2431 
2432 	(void) uname(&name);
2433 	if ((ruser_ids = getpwnam(p->name)) == NULL)
2434 		exit(0);
2435 	(void) setuid(ruser_ids->pw_uid);
2436 
2437 	cmd = xmalloc(strlen(MAIL)+strlen(p->name)+2);
2438 	(void) sprintf(cmd, "%s %s", MAIL, p->name);
2439 	mailpipe = popen(cmd, "w");
2440 	free(cmd);
2441 	if (mailpipe == NULL)
2442 		exit(127);
2443 	(void) fprintf(mailpipe, "To: %s\n", p->name);
2444 	if (pr->jobtype == CRONEVENT) {
2445 		(void) fprintf(mailpipe, CRONOUT);
2446 		(void) fprintf(mailpipe, "Your \"cron\" job on %s\n",
2447 			name.nodename);
2448 		if (pr->jobname != NULL) {
2449 			(void) fprintf(mailpipe, "%s\n\n", pr->jobname);
2450 		}
2451 	} else {
2452 		(void) fprintf(mailpipe, "Subject: Output from \"at\" job\n\n");
2453 		(void) fprintf(mailpipe, "Your \"at\" job on %s\n",
2454 			name.nodename);
2455 		if (pr->jobname != NULL) {
2456 			(void) fprintf(mailpipe, "\"%s\"\n\n", pr->jobname);
2457 		}
2458 	}
2459 	/* Tmp. file is fopen'ed w/ "r",  secure open */
2460 	if (filesize > 0 &&
2461 	    (st = fopen(pr->outfile, "r")) != NULL) {
2462 		(void) fprintf(mailpipe,
2463 			"produced the following output:\n\n");
2464 		while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0)
2465 			(void) fwrite(iobuf, sizeof (char), nbytes, mailpipe);
2466 		(void) fclose(st);
2467 	} else {
2468 		(void) fprintf(mailpipe, "completed.\n");
2469 	}
2470 	(void) pclose(mailpipe);
2471 	exit(0);
2472 }
2473 
2474 static int
2475 msg_wait(long tim)
2476 {
2477 	struct	message	msg;
2478 	int	cnt;
2479 	time_t	reftime;
2480 	struct	pollfd pfd[2];
2481 	int64_t	tl;
2482 	int	timeout;
2483 	static int	pending_msg;
2484 	static time_t	pending_reftime;
2485 
2486 	if (pending_msg) {
2487 		process_msg(&msgbuf, pending_reftime);
2488 		pending_msg = 0;
2489 		return (0);
2490 	}
2491 
2492 	/*
2493 	 * We are opening the signal mask to receive SIGCLD. The notifypipe
2494 	 * is used to avoid race condition between SIGCLD and poll system
2495 	 * call.
2496 	 * If SIGCLD is delivered in poll(), poll will be interrupted, and
2497 	 * we will return to idle() to reap the dead children.
2498 	 * If SIGCLD is delivered between sigprocmask() below and poll(),
2499 	 * there is no way we can detect the SIGCLD because poll() won't
2500 	 * be interrupted. In such case, the dead children can't be wait'ed
2501 	 * until poll returns by timeout or a new job. To avoid this race
2502 	 * condition, child_handler write to the notifypipe, so that
2503 	 * poll() will be able to return with POLLIN which indicates that
2504 	 * we have received SIGCLD.
2505 	 *
2506 	 * Since the notifypipe is used to just let poll return from
2507 	 * system call, the data in the pipe won't be read. Therefore,
2508 	 * any data in the pipe needs to be flushed before opening signal
2509 	 * mask.
2510 	 *
2511 	 * Note that we can probably re-write this code with pselect()
2512 	 * which can handle this situation easily.
2513 	 */
2514 	(void) ioctl(notifypipe[0], I_FLUSH, FLUSHW);
2515 
2516 	pfd[0].fd = msgfd;
2517 	pfd[0].events = POLLIN;
2518 	pfd[1].fd = notifypipe[1];
2519 	pfd[1].events = POLLIN;
2520 
2521 #ifdef CRON_MAXSLEEP
2522 	/*
2523 	 * CRON_MAXSLEEP can be defined to have cron periodically wake
2524 	 * up, so that cron can detect a change of TOD and adjust the
2525 	 * sleep time accordingly.
2526 	 */
2527 	tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim;
2528 #endif
2529 	tl = (tim == INFINITY) ? -1ll : (int64_t)tim * 1000;
2530 
2531 	accept_sigcld = 1;
2532 	(void) sigprocmask(SIG_UNBLOCK, &childmask, NULL);
2533 	do {
2534 		timeout = (tl > INT_MAX ? INT_MAX : (int)tl);
2535 		tl -= timeout;
2536 		cnt = poll(pfd, 2, timeout);
2537 		if (cnt == -1 && errno != EINTR) {
2538 			perror("! poll");
2539 		}
2540 	} while (tl > 0 && cnt == 0);
2541 	(void) sigprocmask(SIG_BLOCK, &childmask, NULL);
2542 	accept_sigcld = 0;
2543 
2544 	/*
2545 	 * poll timeout or interrupted.
2546 	 */
2547 	if (cnt <= 0)
2548 		return (0);
2549 
2550 	/*
2551 	 * Not the timeout or new job, but a SIGCLD has been delivered.
2552 	 */
2553 	if ((pfd[0].revents & POLLIN) == 0)
2554 		return (0);
2555 
2556 	errno = 0;
2557 	if ((cnt = read(msgfd, &msg, sizeof (msg))) != sizeof (msg)) {
2558 		if (cnt != -1 || errno != EAGAIN)
2559 			perror("! read");
2560 		return (0);
2561 	}
2562 	reftime = time(NULL);
2563 	if (next_event != NULL && reftime >= next_event->time) {
2564 		/*
2565 		 * we need to run the job before reloading crontab.
2566 		 */
2567 		(void) memcpy(&msgbuf, &msg, sizeof (msg));
2568 		pending_msg = 1;
2569 		pending_reftime = reftime;
2570 		return (1);
2571 	}
2572 	process_msg(&msg, reftime);
2573 	return (0);
2574 }
2575 
2576 /*
2577  * process the message supplied via pipe. This will be called either
2578  * immediately after cron read the message from pipe, or idle time
2579  * if the message was pending due to the job execution.
2580  */
2581 static void
2582 process_msg(struct message *pmsg, time_t reftime)
2583 {
2584 	if (pmsg->etype == NULL)
2585 		return;
2586 
2587 	switch (pmsg->etype) {
2588 	case AT:
2589 		if (pmsg->action == DELETE)
2590 			del_atjob(pmsg->fname, pmsg->logname);
2591 		else
2592 			mod_atjob(pmsg->fname, (time_t)0);
2593 		break;
2594 	case CRON:
2595 		if (pmsg->action == DELETE)
2596 			del_ctab(pmsg->fname);
2597 		else
2598 			mod_ctab(pmsg->fname, reftime);
2599 		break;
2600 	default:
2601 		msg("message received - bad format");
2602 		break;
2603 	}
2604 	if (next_event != NULL) {
2605 		if (next_event->etype == CRONEVENT)
2606 			el_add(next_event, next_event->time,
2607 				(next_event->u)->ctid);
2608 		else	/* etype == ATEVENT */
2609 			el_add(next_event, next_event->time,
2610 				next_event->of.at.eventid);
2611 		next_event = NULL;
2612 	}
2613 	(void) fflush(stdout);
2614 	pmsg->etype = NULL;
2615 }
2616 
2617 /*
2618  * Allocate a new or find an existing runinfo structure
2619  */
2620 static struct runinfo *
2621 rinfo_get(pid_t pid)
2622 {
2623 	struct runinfo *rp;
2624 
2625 	if (pid == 0) {		/* allocate a new entry */
2626 		rp = xcalloc(1, sizeof (struct runinfo));
2627 		rp->next = rthead;	/* link the entry into the list */
2628 		rthead = rp;
2629 		return (rp);
2630 	}
2631 	/* search the list for an existing entry */
2632 	for (rp = rthead; rp != NULL; rp = rp->next) {
2633 		if (rp->pid == pid)
2634 			break;
2635 	}
2636 	return (rp);
2637 }
2638 
2639 /*
2640  * Free a runinfo structure and its associated memory
2641  */
2642 static void
2643 rinfo_free(struct runinfo *entry)
2644 {
2645 	struct runinfo **rpp;
2646 	struct runinfo *rp;
2647 
2648 #ifdef DEBUG
2649 	(void) fprintf(stderr, "freeing job %s\n", entry->jobname);
2650 #endif
2651 	for (rpp = &rthead; (rp = *rpp) != NULL; rpp = &rp->next) {
2652 		if (rp == entry) {
2653 			*rpp = rp->next;	/* unlink the entry */
2654 			free(rp->outfile);
2655 			free(rp->jobname);
2656 			free(rp);
2657 			break;
2658 		}
2659 	}
2660 }
2661 
2662 /* ARGSUSED */
2663 static void
2664 thaw_handler(int sig)
2665 {
2666 	;
2667 }
2668 
2669 
2670 /* ARGSUSED */
2671 static void
2672 cronend(int sig)
2673 {
2674 	crabort("SIGTERM", REMOVE_FIFO);
2675 }
2676 
2677 /*ARGSUSED*/
2678 static void
2679 child_handler(int sig)
2680 {
2681 	/*
2682 	 * Just in case someone changes the signal mask.
2683 	 * we don't want to notify the SIGCLD.
2684 	 */
2685 	if (accept_sigcld) {
2686 		(void) write(notifypipe[0], &sig, 1);
2687 	}
2688 }
2689 
2690 static void
2691 child_sigreset(void)
2692 {
2693 	(void) signal(SIGCLD, SIG_DFL);
2694 	(void) sigprocmask(SIG_SETMASK, &defmask, NULL);
2695 }
2696 
2697 /*
2698  * crabort() - handle exits out of cron
2699  */
2700 static void
2701 crabort(char *mssg, int action)
2702 {
2703 	int	c;
2704 
2705 	if (action & REMOVE_FIFO) {
2706 		/* FIFO vanishes when cron finishes */
2707 		if (unlink(FIFO) < 0)
2708 			perror("cron could not unlink FIFO");
2709 	}
2710 
2711 	if (action & CONSOLE_MSG) {
2712 		/* write error msg to console */
2713 		if ((c = open(CONSOLE, O_WRONLY)) >= 0) {
2714 			(void) write(c, "cron aborted: ", 14);
2715 			(void) write(c, mssg, strlen(mssg));
2716 			(void) write(c, "\n", 1);
2717 			(void) close(c);
2718 		}
2719 	}
2720 
2721 	/* always log the message */
2722 	msg(mssg);
2723 	msg("******* CRON ABORTED ********");
2724 	exit(1);
2725 }
2726 
2727 /*
2728  * msg() - time-stamped error reporting function
2729  */
2730 /*PRINTFLIKE1*/
2731 static void
2732 msg(char *fmt, ...)
2733 {
2734 	va_list args;
2735 	time_t	t;
2736 
2737 	t = time(NULL);
2738 
2739 	(void) fflush(stdout);
2740 
2741 	(void) fprintf(stderr, "! ");
2742 
2743 	va_start(args, fmt);
2744 	(void) vfprintf(stderr, fmt, args);
2745 	va_end(args);
2746 
2747 	(void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
2748 	(void) fprintf(stderr, " %s\n", timebuf);
2749 
2750 	(void) fflush(stderr);
2751 }
2752 
2753 static void
2754 logit(int cc, struct runinfo *rp, int rc)
2755 {
2756 	time_t t;
2757 	int    ret;
2758 
2759 	if (!log)
2760 		return;
2761 
2762 	t = time(NULL);
2763 	if (cc == BCHAR)
2764 		(void) printf("%c  CMD: %s\n", cc, next_event->cmd);
2765 	(void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
2766 	(void) printf("%c  %.8s %u %c %s",
2767 		cc, (rp->rusr)->name, rp->pid, QUE(rp->que), timebuf);
2768 	if ((ret = TSTAT(rc)) != 0)
2769 		(void) printf(" ts=%d", ret);
2770 	if ((ret = RCODE(rc)) != 0)
2771 		(void) printf(" rc=%d", ret);
2772 	(void) putchar('\n');
2773 	(void) fflush(stdout);
2774 }
2775 
2776 static void
2777 resched(int delay)
2778 {
2779 	time_t	nt;
2780 
2781 	/* run job at a later time */
2782 	nt = next_event->time + delay;
2783 	if (next_event->etype == CRONEVENT) {
2784 		next_event->time = next_time(next_event, (time_t)0);
2785 		if (nt < next_event->time)
2786 			next_event->time = nt;
2787 		el_add(next_event, next_event->time, (next_event->u)->ctid);
2788 		delayed = 1;
2789 		msg("rescheduling a cron job");
2790 		return;
2791 	}
2792 	add_atevent(next_event->u, next_event->cmd, nt, next_event->etype);
2793 	msg("rescheduling at job");
2794 }
2795 
2796 static void
2797 quedefs(int action)
2798 {
2799 	int	i;
2800 	int	j;
2801 	char	qbuf[QBUFSIZ];
2802 	FILE	*fd;
2803 
2804 	/* set up default queue definitions */
2805 	for (i = 0; i < NQUEUE; i++) {
2806 		qt[i].njob = qd.njob;
2807 		qt[i].nice = qd.nice;
2808 		qt[i].nwait = qd.nwait;
2809 	}
2810 	if (action == DEFAULT)
2811 		return;
2812 	if ((fd = fopen(QUEDEFS, "r")) == NULL) {
2813 		msg("cannot open quedefs file");
2814 		msg("using default queue definitions");
2815 		return;
2816 	}
2817 	while (fgets(qbuf, QBUFSIZ, fd) != NULL) {
2818 		if ((j = qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.')
2819 			continue;
2820 		parsqdef(&qbuf[2]);
2821 		qt[j].njob = qq.njob;
2822 		qt[j].nice = qq.nice;
2823 		qt[j].nwait = qq.nwait;
2824 	}
2825 	(void) fclose(fd);
2826 }
2827 
2828 static void
2829 parsqdef(char *name)
2830 {
2831 	int i;
2832 
2833 	qq = qd;
2834 	while (*name) {
2835 		i = 0;
2836 		while (isdigit(*name)) {
2837 			i *= 10;
2838 			i += *name++ - '0';
2839 		}
2840 		switch (*name++) {
2841 		case JOBF:
2842 			qq.njob = i;
2843 			break;
2844 		case NICEF:
2845 			qq.nice = i;
2846 			break;
2847 		case WAITF:
2848 			qq.nwait = i;
2849 			break;
2850 		}
2851 	}
2852 }
2853 
2854 /*
2855  * defaults - read defaults from /etc/default/cron
2856  */
2857 static void
2858 defaults()
2859 {
2860 	int  flags;
2861 	char *deflog;
2862 	char *hz, *tz;
2863 
2864 	/*
2865 	 * get HZ value for environment
2866 	 */
2867 	if ((hz = getenv("HZ")) == (char *)NULL)
2868 		(void) sprintf(hzname, "HZ=%d", HZ);
2869 	else
2870 		(void) snprintf(hzname, sizeof (hzname), "HZ=%s", hz);
2871 	/*
2872 	 * get TZ value for environment
2873 	 */
2874 	(void) snprintf(tzone, sizeof (tzone), "TZ=%s",
2875 		((tz = getenv("TZ")) != NULL) ? tz : DEFTZ);
2876 
2877 	if (defopen(DEFFILE) == 0) {
2878 		/* ignore case */
2879 		flags = defcntl(DC_GETFLAGS, 0);
2880 		TURNOFF(flags, DC_CASE);
2881 		(void) defcntl(DC_SETFLAGS, flags);
2882 
2883 		if (((deflog = defread("CRONLOG=")) == NULL) ||
2884 		    (*deflog == 'N') || (*deflog == 'n'))
2885 			log = 0;
2886 		else
2887 			log = 1;
2888 		/* fix for 1087611 - allow paths to be set in defaults file */
2889 		if ((Def_path = defread("PATH=")) != NULL) {
2890 			(void) strlcat(path, Def_path, LINE_MAX);
2891 		} else {
2892 			(void) strlcpy(path, NONROOTPATH, LINE_MAX);
2893 		}
2894 		if ((Def_supath = defread("SUPATH=")) != NULL) {
2895 			(void) strlcat(supath, Def_supath, LINE_MAX);
2896 		} else {
2897 			(void) strlcpy(supath, ROOTPATH, LINE_MAX);
2898 		}
2899 		(void) defopen(NULL);
2900 	}
2901 }
2902 
2903 /*
2904  * Determine if a user entry for a job is still ok.  The method used here
2905  * is a lot (about 75x) faster than using setgrent() / getgrent()
2906  * endgrent().  It should be safe because we use the sysconf to determine
2907  * the max, and it tolerates the max being 0.
2908  */
2909 
2910 static int
2911 verify_user_cred(const struct usr *u)
2912 {
2913 	struct passwd *pw;
2914 	size_t numUsrGrps = 0;
2915 	size_t numOrigGrps = 0;
2916 	size_t i;
2917 	int retval;
2918 
2919 	/*
2920 	 * Maximum number of groups a user may be in concurrently.  This
2921 	 * is a value which we obtain at runtime through a sysconf()
2922 	 * call.
2923 	 */
2924 
2925 	static size_t nGroupsMax = (size_t)-1;
2926 
2927 	/*
2928 	 * Arrays for cron user's group list, constructed at startup to
2929 	 * be nGroupsMax elements long, used for verifying user
2930 	 * credentials prior to execution.
2931 	 */
2932 
2933 	static gid_t *UsrGrps;
2934 	static gid_t *OrigGrps;
2935 
2936 	if (((pw = getpwnam(u->name)) == NULL) ||
2937 	    (pw->pw_uid != u->uid)) {
2938 		return (VUC_BADUSER);
2939 	}
2940 
2941 	/*
2942 	 * Create the group id lists needed for job credential
2943 	 * verification.
2944 	 */
2945 
2946 	if (nGroupsMax == (size_t)-1) {
2947 		if ((nGroupsMax = sysconf(_SC_NGROUPS_MAX)) > 0) {
2948 			UsrGrps = xcalloc(nGroupsMax, sizeof (gid_t));
2949 			OrigGrps = xcalloc(nGroupsMax, sizeof (gid_t));
2950 		}
2951 
2952 #ifdef DEBUG
2953 		(void) fprintf(stderr, "nGroupsMax = %ld\n", nGroupsMax);
2954 #endif
2955 	}
2956 
2957 #ifdef DEBUG
2958 	(void) fprintf(stderr, "verify_user_cred (%s-%d)\n", pw->pw_name,
2959 	    pw->pw_uid);
2960 	(void) fprintf(stderr, "verify_user_cred: pw->pw_gid = %d, "
2961 	    "u->gid = %d\n", pw->pw_gid, u->gid);
2962 #endif
2963 
2964 	retval = (u->gid == pw->pw_gid) ? VUC_OK : VUC_NOTINGROUP;
2965 
2966 	if (nGroupsMax > 0) {
2967 		numOrigGrps = getgroups(nGroupsMax, OrigGrps);
2968 
2969 		(void) initgroups(pw->pw_name, pw->pw_gid);
2970 		numUsrGrps = getgroups(nGroupsMax, UsrGrps);
2971 
2972 		for (i = 0; i < numUsrGrps; i++) {
2973 			if (UsrGrps[i] == u->gid) {
2974 				retval = VUC_OK;
2975 				break;
2976 			}
2977 		}
2978 
2979 		if (OrigGrps) {
2980 			(void) setgroups(numOrigGrps, OrigGrps);
2981 		}
2982 	}
2983 
2984 #ifdef DEBUG
2985 	(void) fprintf(stderr, "verify_user_cred: VUC = %d\n", retval);
2986 #endif
2987 
2988 	return (retval);
2989 }
2990 
2991 static int
2992 set_user_cred(const struct usr *u, struct project *pproj)
2993 {
2994 	static char *progname = "cron";
2995 	int r = 0, rval = 0;
2996 
2997 	if ((r = pam_start(progname, u->name, &pam_conv, &pamh))
2998 		!= PAM_SUCCESS) {
2999 #ifdef DEBUG
3000 		msg("pam_start returns %d\n", r);
3001 #endif
3002 		rval = VUC_BADUSER;
3003 		goto set_eser_cred_exit;
3004 	}
3005 
3006 	r = pam_acct_mgmt(pamh, 0);
3007 #ifdef DEBUG
3008 	msg("pam_acc_mgmt returns %d\n", r);
3009 #endif
3010 	if (r == PAM_ACCT_EXPIRED) {
3011 		rval = VUC_EXPIRED;
3012 		goto set_eser_cred_exit;
3013 	}
3014 	if (r == PAM_NEW_AUTHTOK_REQD) {
3015 		rval = VUC_NEW_AUTH;
3016 		goto set_eser_cred_exit;
3017 	}
3018 	if (r != PAM_SUCCESS) {
3019 		rval = VUC_BADUSER;
3020 		goto set_eser_cred_exit;
3021 	}
3022 
3023 	if (pproj != NULL) {
3024 		size_t sz = sizeof (PROJECT) + strlen(pproj->pj_name);
3025 		char *buf = alloca(sz);
3026 
3027 		(void) snprintf(buf, sz, PROJECT "%s", pproj->pj_name);
3028 		(void) pam_set_item(pamh, PAM_RESOURCE, buf);
3029 	}
3030 
3031 	r = pam_setcred(pamh, PAM_ESTABLISH_CRED);
3032 	if (r != PAM_SUCCESS)
3033 		rval = VUC_BADUSER;
3034 
3035 set_eser_cred_exit:
3036 	(void) pam_end(pamh, r);
3037 	return (rval);
3038 }
3039 
3040 static void
3041 clean_out_user(struct usr *u)
3042 {
3043 	if (next_event->u == u) {
3044 		next_event = NULL;
3045 	}
3046 
3047 	clean_out_ctab(u);
3048 	clean_out_atjobs(u);
3049 	free_if_unused(u);
3050 }
3051 
3052 static void
3053 clean_out_atjobs(struct usr *u)
3054 {
3055 	struct event *ev, *pv;
3056 
3057 	for (pv = NULL, ev = u->atevents;
3058 		ev != NULL;
3059 		pv = ev, ev = ev->link, free(pv)) {
3060 		el_remove(ev->of.at.eventid, 1);
3061 		if (cwd == AT)
3062 			cron_unlink(ev->cmd);
3063 		else {
3064 			char buf[PATH_MAX];
3065 			if (strlen(ATDIR) + strlen(ev->cmd) + 2
3066 				< PATH_MAX) {
3067 				(void) sprintf(buf, "%s/%s", ATDIR, ev->cmd);
3068 				cron_unlink(buf);
3069 			}
3070 		}
3071 		free(ev->cmd);
3072 	}
3073 
3074 	u->atevents = NULL;
3075 }
3076 
3077 static void
3078 clean_out_ctab(struct usr *u)
3079 {
3080 	rm_ctevents(u);
3081 	el_remove(u->ctid, 0);
3082 	u->ctid = 0;
3083 	u->ctexists = 0;
3084 }
3085 
3086 static void
3087 cron_unlink(char *name)
3088 {
3089 	int r;
3090 
3091 	r = unlink(name);
3092 	if (r == 0 || (r == -1 && errno == ENOENT)) {
3093 		(void) audit_cron_delete_anc_file(name, NULL);
3094 	}
3095 }
3096 
3097 static void
3098 create_anc_ctab(struct event *e)
3099 {
3100 	if (audit_cron_create_anc_file(e->u->name,
3101 		(cwd == CRON) ? NULL:CRONDIR,
3102 		e->u->name,
3103 		e->u->uid) == -1) {
3104 		process_anc_files(CRON_ANC_DELETE);
3105 		crabort("cannot create ancillary files for crontabs",
3106 			REMOVE_FIFO|CONSOLE_MSG);
3107 	}
3108 }
3109 
3110 static void
3111 delete_anc_ctab(struct event *e)
3112 {
3113 	(void) audit_cron_delete_anc_file(e->u->name,
3114 		(cwd == CRON) ? NULL:CRONDIR);
3115 }
3116 
3117 static void
3118 create_anc_atjob(struct event *e)
3119 {
3120 	if (!e->of.at.exists)
3121 		return;
3122 
3123 	if (audit_cron_create_anc_file(e->cmd,
3124 		(cwd == AT) ? NULL:ATDIR,
3125 		e->u->name,
3126 		e->u->uid) == -1) {
3127 		process_anc_files(CRON_ANC_DELETE);
3128 		crabort("cannot create ancillary files for atjobs",
3129 			REMOVE_FIFO|CONSOLE_MSG);
3130 	}
3131 }
3132 
3133 static void
3134 delete_anc_atjob(struct event *e)
3135 {
3136 	if (!e->of.at.exists)
3137 		return;
3138 
3139 	(void) audit_cron_delete_anc_file(e->cmd,
3140 		(cwd == AT) ? NULL:ATDIR);
3141 }
3142 
3143 
3144 static void
3145 process_anc_files(int del)
3146 {
3147 	struct usr	*u = uhead;
3148 	struct event	*e;
3149 
3150 	if (!audit_cron_mode())
3151 		return;
3152 
3153 	for (;;) {
3154 		if (u->ctexists && u->ctevents != NULL) {
3155 			e = u->ctevents;
3156 			for (;;) {
3157 				if (del)
3158 					delete_anc_ctab(e);
3159 				else
3160 					create_anc_ctab(e);
3161 				if ((e = e->link) == NULL)
3162 					break;
3163 			}
3164 		}
3165 
3166 		if (u->atevents != NULL) {
3167 			e = u->atevents;
3168 			for (;;) {
3169 				if (del)
3170 					delete_anc_atjob(e);
3171 				else
3172 					create_anc_atjob(e);
3173 				if ((e = e->link) == NULL)
3174 					break;
3175 			}
3176 		}
3177 
3178 		if ((u = u->nextusr)  == NULL)
3179 			break;
3180 	}
3181 }
3182 
3183 /*ARGSUSED*/
3184 static int
3185 cron_conv(int num_msg, struct pam_message **msgs,
3186     struct pam_response **response, void *appdata_ptr)
3187 {
3188 	struct pam_message	**m = msgs;
3189 	int i;
3190 
3191 	for (i = 0; i < num_msg; i++) {
3192 		switch (m[i]->msg_style) {
3193 		case PAM_ERROR_MSG:
3194 		case PAM_TEXT_INFO:
3195 			if (m[i]->msg != NULL) {
3196 				(void) msg("%s\n", m[i]->msg);
3197 			}
3198 			break;
3199 
3200 		default:
3201 			break;
3202 		}
3203 	}
3204 	return (0);
3205 }
3206 
3207 /*
3208  * Cron creates process for other than job. Mail process is the
3209  * one which rinfo does not cover. Therefore, miscpid will keep
3210  * track of the pids executed from cron. Otherwise, we will see
3211  * "unexpected pid returned.." messages appear in the log file.
3212  */
3213 static void
3214 miscpid_insert(pid_t pid)
3215 {
3216 	struct miscpid *mp;
3217 
3218 	mp = xmalloc(sizeof (*mp));
3219 	mp->pid = pid;
3220 	mp->next = miscpid_head;
3221 	miscpid_head = mp;
3222 }
3223 
3224 static int
3225 miscpid_delete(pid_t pid)
3226 {
3227 	struct miscpid *mp, *omp;
3228 	int found = 0;
3229 
3230 	omp = NULL;
3231 	for (mp = miscpid_head; mp != NULL; mp = mp->next) {
3232 		if (mp->pid == pid) {
3233 			found = 1;
3234 			break;
3235 		}
3236 		omp = mp;
3237 	}
3238 	if (found) {
3239 		if (omp != NULL)
3240 			omp->next = mp->next;
3241 		else
3242 			miscpid_head = NULL;
3243 		free(mp);
3244 	}
3245 	return (found);
3246 }
3247 
3248 /*
3249  * Establish contract terms such that all children are in abandoned
3250  * process contracts.
3251  */
3252 static void
3253 contract_set_template(void)
3254 {
3255 	int fd;
3256 
3257 	if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3258 		crabort("cannot open process contract template",
3259 		    REMOVE_FIFO | CONSOLE_MSG);
3260 
3261 	if (ct_pr_tmpl_set_param(fd, 0) ||
3262 	    ct_tmpl_set_informative(fd, 0) ||
3263 	    ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR))
3264 		crabort("cannot establish contract template terms",
3265 		    REMOVE_FIFO | CONSOLE_MSG);
3266 
3267 	if (ct_tmpl_activate(fd))
3268 		crabort("cannot activate contract template",
3269 		    REMOVE_FIFO | CONSOLE_MSG);
3270 
3271 	(void) close(fd);
3272 }
3273 
3274 /*
3275  * Clear active process contract template.
3276  */
3277 static void
3278 contract_clear_template(void)
3279 {
3280 	int fd;
3281 
3282 	if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3283 		crabort("cannot open process contract template",
3284 		    REMOVE_FIFO | CONSOLE_MSG);
3285 
3286 	if (ct_tmpl_clear(fd))
3287 		crabort("cannot clear contract template",
3288 		    REMOVE_FIFO | CONSOLE_MSG);
3289 
3290 	(void) close(fd);
3291 }
3292 
3293 /*
3294  * Abandon latest process contract unconditionally.  If we have leaked [some
3295  * critical amount], exit such that the kernel reaps our contracts.
3296  */
3297 static void
3298 contract_abandon_latest(pid_t pid)
3299 {
3300 	int r;
3301 	ctid_t id;
3302 	static uint_t cts_lost;
3303 
3304 	if (cts_lost > MAX_LOST_CONTRACTS)
3305 		crabort("repeated failure to abandon contracts",
3306 		    REMOVE_FIFO | CONSOLE_MSG);
3307 
3308 	if (r = contract_latest(&id)) {
3309 		msg("could not obtain latest contract for "
3310 		    "PID %ld: %s", pid, strerror(r));
3311 		cts_lost++;
3312 		return;
3313 	}
3314 
3315 	if (r = contract_abandon_id(id)) {
3316 		msg("could not abandon latest contract %ld: %s", id,
3317 		    strerror(r));
3318 		cts_lost++;
3319 		return;
3320 	}
3321 }
3322