xref: /titanic_51/usr/src/cmd/cron/cron.c (revision bdfc6d18da790deeec2e0eb09c625902defe2498)
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 2005 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	MAXRUN		100		/* max total jobs allowed in system */
97 #define	ZOMB		100		/* proc slot used for mailing output */
98 
99 #define	JOBF		'j'
100 #define	NICEF		'n'
101 #define	USERF		'u'
102 #define	WAITF		'w'
103 
104 #define	BCHAR		'>'
105 #define	ECHAR		'<'
106 
107 #define	DEFAULT		0
108 #define	LOAD		1
109 #define	QBUFSIZ		80
110 
111 /* Defined actions for crabort() routine */
112 #define	NO_ACTION	000
113 #define	REMOVE_FIFO	001
114 #define	CONSOLE_MSG	002
115 
116 #define	BADCD		"can't change directory to the crontab directory."
117 #define	NOREADDIR	"can't read the crontab directory."
118 
119 #define	BADJOBOPEN	"unable to read your at job."
120 #define	BADSHELL	"because your login shell \
121 isn't /usr/bin/sh, you can't use cron."
122 
123 #define	BADSTAT		"can't access your crontab file.  Resubmit it."
124 #define	BADPROJID	"can't set project id for your job."
125 #define	CANTCDHOME	"can't change directory to your home directory.\
126 \nYour commands will not be executed."
127 #define	CANTEXECSH	"unable to exec the shell for one of your commands."
128 #define	NOREAD		"can't read your crontab file.  Resubmit it."
129 #define	BADTYPE		"crontab is not a regular file.\n"
130 #define	NOSTDIN		"unable to create a standard input file for \
131 one of your crontab commands. \
132 \nThat command was not executed."
133 
134 #define	NOTALLOWED	"you are not authorized to use cron.  Sorry."
135 #define	STDERRMSG	"\n\n********************************************\
136 *****\nCron: The previous message is the \
137 standard output and standard error \
138 \nof one of your cron commands.\n"
139 
140 #define	STDOUTERR	"one of your commands generated output or errors, \
141 but cron was unable to mail you this output.\
142 \nRemember to redirect standard output and standard \
143 error for each of your commands."
144 
145 #define	CLOCK_DRIFT	"clock time drifted backwards after event!\n"
146 #define	PIDERR		"unexpected pid returned %d (ignored)"
147 #define	CRONTABERR	"Subject: Your crontab file has an error in it\n\n"
148 #define	CRONOUT		"Subject: Output from \"cron\" command\n\n"
149 #define	MALLOCERR	"out of space, cannot create new string\n"
150 
151 #define	DIDFORK didfork
152 #define	NOFORK !didfork
153 
154 #define	MAILBUFLEN	(8*1024)
155 #define	LINELIMIT	80
156 #define	MAILBINITFREE	(MAILBUFLEN - (sizeof (cte_intro) - 1) \
157 	    - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1)
158 
159 #define	ERR_CRONTABENT	0	/* error in crontab file entry */
160 #define	ERR_UNIXERR	1	/* error in some system call */
161 #define	ERR_CANTEXECCRON 2	/* error setting up "cron" job environment */
162 #define	ERR_CANTEXECAT	3	/* error setting up "at" job environment */
163 
164 #define	PROJECT		"project="
165 
166 #define	MAX_LOST_CONTRACTS	2048	/* reset if this many failed abandons */
167 
168 #define	FORMAT	"%a %b %e %H:%M:%S %Y"
169 static char	timebuf[80];
170 
171 struct event {
172 	time_t time;	/* time of the event	*/
173 	short etype;	/* what type of event; 0=cron, 1=at	*/
174 	char *cmd;	/* command for cron, job name for at	*/
175 	struct usr *u;	/* ptr to the owner (usr) of this event	*/
176 	struct event *link;	/* ptr to another event for this user */
177 	union {
178 		struct { /* for crontab events */
179 			char *minute;	/*  (these	*/
180 			char *hour;	/*   fields	*/
181 			char *daymon;	/*   are	*/
182 			char *month;	/*   from	*/
183 			char *dayweek;	/*   crontab)	*/
184 			char *input;	/* ptr to stdin	*/
185 		} ct;
186 		struct { /* for at events */
187 			short exists;	/* for revising at events	*/
188 			int eventid;	/* for el_remove-ing at events	*/
189 		} at;
190 	} of;
191 };
192 
193 struct usr {
194 	char *name;	/* name of user (e.g. "root")	*/
195 	char *home;	/* home directory for user	*/
196 	uid_t uid;	/* user id	*/
197 	gid_t gid;	/* group id	*/
198 	int aruncnt;	/* counter for running jobs per uid */
199 	int cruncnt;	/* counter for running cron jobs per uid */
200 	int ctid;	/* for el_remove-ing crontab events */
201 	short ctexists;	/* for revising crontab events	*/
202 	struct event *ctevents;	/* list of this usr's crontab events */
203 	struct event *atevents;	/* list of this usr's at events */
204 	struct usr *nextusr;
205 };	/* ptr to next user	*/
206 
207 static struct	queue
208 {
209 	int njob;	/* limit */
210 	int nice;	/* nice for execution */
211 	int nwait;	/* wait time to next execution attempt */
212 	int nrun;	/* number running */
213 }
214 	qd = {100, 2, 60},		/* default values for queue defs */
215 	qt[NQUEUE];
216 static struct	queue	qq;
217 
218 struct runinfo
219 {
220 	pid_t	pid;
221 	short	que;
222 	struct  usr *rusr;	/* pointer to usr struct */
223 	char	*outfile;	/* file where stdout & stderr are trapped */
224 	short	jobtype;	/* what type of event: 0=cron, 1=at */
225 	char	*jobname;	/* command for "cron", jobname for "at" */
226 	int	mailwhendone;	/* 1 = send mail even if no ouptut */
227 }	rt[MAXRUN];
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 int contract_set_template(void);
320 static int 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 	if ((rp = rinfo_get(0)) == NULL) {
1959 		msg("MAXRUN (%d) procs reached", MAXRUN);
1960 		resched(qp->nwait);
1961 		return (0);
1962 	}
1963 
1964 #ifdef ATLIMIT
1965 	if ((e->u)->uid != 0 && (e->u)->aruncnt >= ATLIMIT) {
1966 		msg("ATLIMIT (%d) reached for uid %d",
1967 			ATLIMIT, (e->u)->uid);
1968 		rinfo_free(rp);
1969 		resched(qp->nwait);
1970 		return (0);
1971 	}
1972 #endif
1973 #ifdef CRONLIMIT
1974 	if ((e->u)->uid != 0 && (e->u)->cruncnt >= CRONLIMIT) {
1975 		msg("CRONLIMIT (%d) reached for uid %d",
1976 			CRONLIMIT, (e->u)->uid);
1977 		rinfo_free(rp);
1978 		resched(qp->nwait);
1979 		return (0);
1980 	}
1981 #endif
1982 	if ((e->u)->uid == 0) {	/* set default path */
1983 		/* path settable in defaults file */
1984 		envinit[2] = supath;
1985 	} else {
1986 		envinit[2] = path;
1987 	}
1988 
1989 	/*
1990 	 * the tempnam() function uses malloc(3C) to allocate space for the
1991 	 * constructed file name, and returns a pointer to this area, which
1992 	 * is assigned to rp->outfile. Here rp->outfile is not overwritten.
1993 	 */
1994 
1995 	rp->outfile = tempnam(TMPDIR, PFX);
1996 	rp->jobtype = e->etype;
1997 	if (e->etype == CRONEVENT) {
1998 		rp->jobname = xmalloc(strlen(e->cmd)+1);
1999 		(void) strcpy(rp->jobname, e->cmd);
2000 		/* "cron" jobs only produce mail if there's output */
2001 		rp->mailwhendone = 0;
2002 	} else {
2003 		at_cmdfile = xmalloc(strlen(ATDIR)+strlen(e->cmd)+2);
2004 		(void) sprintf(at_cmdfile, "%s/%s", ATDIR, e->cmd);
2005 		if ((atcmdfp = fopen(at_cmdfile, "r")) == NULL) {
2006 			if (errno == ENAMETOOLONG) {
2007 				if (chdir(ATDIR) == 0)
2008 					cron_unlink(e->cmd);
2009 			} else {
2010 				cron_unlink(at_cmdfile);
2011 			}
2012 			mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECAT);
2013 			free(at_cmdfile);
2014 			rinfo_free(rp);
2015 			return (0);
2016 		}
2017 		rp->jobname = xmalloc(strlen(at_cmdfile)+1);
2018 		(void) strcpy(rp->jobname, at_cmdfile);
2019 
2020 		/*
2021 		 * Skip over the first two lines.
2022 		 */
2023 		(void) fscanf(atcmdfp, "%*[^\n]\n");
2024 		(void) fscanf(atcmdfp, "%*[^\n]\n");
2025 		if (fscanf(atcmdfp, ": notify by mail: %3s%*[^\n]\n",
2026 			mailvar) == 1) {
2027 			/*
2028 			 * Check to see if we should always send mail
2029 			 * to the owner.
2030 			 */
2031 			rp->mailwhendone = (strcmp(mailvar, "yes") == 0);
2032 		} else {
2033 			rp->mailwhendone = 0;
2034 		}
2035 
2036 		if (fscanf(atcmdfp, "\n: project: %d\n", &projid) == 1) {
2037 			projflag = 1;
2038 		}
2039 		(void) fclose(atcmdfp);
2040 	}
2041 
2042 	/*
2043 	 * we make sure that the system time
2044 	 * hasn't drifted backwards. if it has, el_add() is now
2045 	 * called, to make sure that the event queue is back in order,
2046 	 * and we set the delayed flag. cron will pick up the request
2047 	 * later on at the proper time.
2048 	 */
2049 	dhltime = time(NULL);
2050 	if ((dhltime - e->time) < 0) {
2051 		msg("clock time drifted backwards!\n");
2052 		if (next_event->etype == CRONEVENT) {
2053 			msg("correcting cron event\n");
2054 			next_event->time = next_time(next_event, dhltime);
2055 			el_add(next_event, next_event->time,
2056 				(next_event->u)->ctid);
2057 		} else { /* etype == ATEVENT */
2058 			msg("correcting batch event\n");
2059 			el_add(next_event, next_event->time,
2060 				next_event->of.at.eventid);
2061 		}
2062 		delayed++;
2063 		t_old = time(NULL);
2064 		free(at_cmdfile);
2065 		rinfo_free(rp);
2066 		return (0);
2067 	}
2068 
2069 	if ((rfork = fork()) == (pid_t)-1) {
2070 		reap_child();
2071 		if ((rfork = fork()) == (pid_t)-1) {
2072 			msg("cannot fork");
2073 			free(at_cmdfile);
2074 			rinfo_free(rp);
2075 			resched(60);
2076 			(void) sleep(30);
2077 			return (0);
2078 		}
2079 	}
2080 	if (rfork) {		/* parent process */
2081 		contract_abandon_latest(rfork);
2082 
2083 		++qp->nrun;
2084 		rp->pid = rfork;
2085 		rp->que = e->etype;
2086 		if (e->etype != CRONEVENT)
2087 			(e->u)->aruncnt++;
2088 		else
2089 			(e->u)->cruncnt++;
2090 		rp->rusr = (e->u);
2091 		logit(BCHAR, rp, 0);
2092 		free(at_cmdfile);
2093 
2094 		return (0);
2095 	}
2096 
2097 	child_sigreset();
2098 	contract_clear_template();
2099 
2100 	if (e->etype != CRONEVENT) {
2101 		/* open jobfile as stdin to shell */
2102 		if (stat(at_cmdfile, &buf)) {
2103 			if (errno == ENAMETOOLONG) {
2104 				if (chdir(ATDIR) == 0)
2105 					cron_unlink(e->cmd);
2106 			} else
2107 				cron_unlink(at_cmdfile);
2108 			mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2109 			exit(1);
2110 		}
2111 		if (!(buf.st_mode&ISUID)) {
2112 			/*
2113 			 * if setuid bit off, original owner has
2114 			 * given this file to someone else
2115 			 */
2116 			cron_unlink(at_cmdfile);
2117 			exit(1);
2118 		}
2119 		if ((fd = open(at_cmdfile, O_RDONLY)) == -1) {
2120 			mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2121 			cron_unlink(at_cmdfile);
2122 			exit(1);
2123 		}
2124 		if (fd != 0) {
2125 			(void) dup2(fd, 0);
2126 			(void) close(fd);
2127 		}
2128 		/*
2129 		 * retrieve the project id of the at job and convert it
2130 		 * to a project name.  fail if it's not a valid project
2131 		 * or if the user isn't a member of the project.
2132 		 */
2133 		if (projflag == 1) {
2134 			if ((pproj = getprojbyid(projid, &proj,
2135 			    (void *)&mybuf, sizeof (mybuf))) == NULL ||
2136 			    !inproj(e->u->name, pproj->pj_name,
2137 			    mybuf2, sizeof (mybuf2))) {
2138 				cron_unlink(at_cmdfile);
2139 				mail((e->u)->name, BADPROJID, ERR_CANTEXECAT);
2140 				exit(1);
2141 			}
2142 		}
2143 	}
2144 
2145 	/*
2146 	 * Put process in a new session, and create a new task.
2147 	 */
2148 	if (setsid() < 0) {
2149 		msg("setsid failed with errno = %d. job failed (%s)"
2150 		    " for user %s", errno, e->cmd, e->u->name);
2151 		if (e->etype != CRONEVENT)
2152 			cron_unlink(at_cmdfile);
2153 		exit(1);
2154 	}
2155 
2156 	/*
2157 	 * set correct user identification and check his account
2158 	 */
2159 	r = set_user_cred(e->u, pproj);
2160 	if (r == VUC_EXPIRED) {
2161 		msg("user (%s) account is expired", e->u->name);
2162 		audit_cron_user_acct_expired(e->u->name);
2163 		clean_out_user(e->u);
2164 		exit(1);
2165 	}
2166 	if (r == VUC_NEW_AUTH) {
2167 		msg("user (%s) password has expired", e->u->name);
2168 		audit_cron_user_acct_expired(e->u->name);
2169 		clean_out_user(e->u);
2170 		exit(1);
2171 	}
2172 	if (r != VUC_OK) {
2173 		msg("bad user (%s)", e->u->name);
2174 		audit_cron_bad_user(e->u->name);
2175 		clean_out_user(e->u);
2176 		exit(1);
2177 	}
2178 	/*
2179 	 * check user and initialize the supplementary group access list.
2180 	 * bugid 1230784: deleted from parent to avoid cron hang. Now
2181 	 * only child handles the call.
2182 	 */
2183 
2184 	if (verify_user_cred(e->u) != VUC_OK ||
2185 	    setgid(e->u->gid) == -1 ||
2186 	    initgroups(e->u->name, e->u->gid) == -1) {
2187 		msg("bad user (%s) or setgid failed (%s)",
2188 			e->u->name, e->u->name);
2189 		audit_cron_bad_user(e->u->name);
2190 		clean_out_user(e->u);
2191 		exit(1);
2192 	}
2193 
2194 	if (e->etype != CRONEVENT) {
2195 		r = audit_cron_session(e->u->name, NULL,
2196 					e->u->uid, e->u->gid,
2197 					at_cmdfile);
2198 		cron_unlink(at_cmdfile);
2199 	} else {
2200 		r = audit_cron_session(e->u->name, CRONDIR,
2201 					e->u->uid, e->u->gid,
2202 					NULL);
2203 	}
2204 	if (r != 0) {
2205 		msg("cron audit problem. job failed (%s) for user %s",
2206 			e->cmd, e->u->name);
2207 		exit(1);
2208 	}
2209 
2210 	audit_cron_new_job(e->cmd, e->etype, (void *)e);
2211 
2212 	if (setuid(e->u->uid) == -1)  {
2213 		msg("setuid failed (%s)", e->u->name);
2214 		clean_out_user(e->u);
2215 		exit(1);
2216 	}
2217 
2218 	if (e->etype == CRONEVENT) {
2219 		/* check for standard input to command	*/
2220 		if (e->of.ct.input != NULL) {
2221 			if ((tmpfile = strdup(TMPINFILE)) == NULL) {
2222 				mail((e->u)->name, MALLOCERR,
2223 					ERR_CANTEXECCRON);
2224 				exit(1);
2225 			}
2226 			if ((fd = mkstemp(tmpfile)) == -1 ||
2227 				(fptr = fdopen(fd, "w")) == NULL) {
2228 				mail((e->u)->name, NOSTDIN,
2229 					ERR_CANTEXECCRON);
2230 				cron_unlink(tmpfile);
2231 				free(tmpfile);
2232 				exit(1);
2233 			}
2234 			if ((fwrite(e->of.ct.input, sizeof (char),
2235 				strlen(e->of.ct.input), fptr)) !=
2236 					strlen(e->of.ct.input)) {
2237 				mail((e->u)->name, NOSTDIN, ERR_CANTEXECCRON);
2238 				cron_unlink(tmpfile);
2239 				free(tmpfile);
2240 				(void) close(fd);
2241 				(void) fclose(fptr);
2242 				exit(1);
2243 			}
2244 			if (fseek(fptr, (off_t)0, SEEK_SET) != -1) {
2245 				if (fd != 0) {
2246 					(void) dup2(fd, 0);
2247 					(void) close(fd);
2248 				}
2249 			}
2250 			cron_unlink(tmpfile);
2251 			free(tmpfile);
2252 			(void) fclose(fptr);
2253 		} else if ((fd = open("/dev/null", O_RDONLY)) > 0) {
2254 			(void) dup2(fd, 0);
2255 			(void) close(fd);
2256 		}
2257 	}
2258 
2259 	/* redirect stdout and stderr for the shell	*/
2260 	if ((fd = open(rp->outfile, O_WRONLY|O_CREAT|O_EXCL, OUTMODE)) == 1)
2261 		fd = open("/dev/null", O_WRONLY);
2262 
2263 	if (fd >= 0 && fd != 1)
2264 		(void) dup2(fd, 1);
2265 
2266 	if (fd >= 0 && fd != 2) {
2267 		(void) dup2(fd, 2);
2268 		if (fd != 1)
2269 			(void) close(fd);
2270 	}
2271 
2272 	(void) strlcat(homedir, (e->u)->home, sizeof (homedir));
2273 	(void) strlcat(logname, (e->u)->name, sizeof (logname));
2274 	environ = envinit;
2275 	if (chdir((e->u)->home) == -1) {
2276 		mail((e->u)->name, CANTCDHOME,
2277 			e->etype == CRONEVENT ? ERR_CANTEXECCRON :
2278 				ERR_CANTEXECAT);
2279 		exit(1);
2280 	}
2281 #ifdef TESTING
2282 	exit(1);
2283 #endif
2284 	/*
2285 	 * make sure that all file descriptors EXCEPT 0, 1 and 2
2286 	 * will be closed.
2287 	 */
2288 	closefrom(3);
2289 
2290 	if ((e->u)->uid != 0)
2291 		(void) nice(qp->nice);
2292 	if (e->etype == CRONEVENT)
2293 		(void) execl(SHELL, "sh", "-c", e->cmd, 0);
2294 	else		/* type == ATEVENT */
2295 		(void) execl(SHELL, "sh", 0);
2296 	mail((e->u)->name, CANTEXECSH,
2297 		e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT);
2298 	exit(1);
2299 	/*NOTREACHED*/
2300 }
2301 
2302 static int
2303 idle(long t)
2304 {
2305 	time_t	now;
2306 
2307 	while (t > 0L) {
2308 
2309 		if (msg_wait(t) != 0) {
2310 			/* we need to run next job immediately */
2311 			return (0);
2312 		}
2313 
2314 		reap_child();
2315 
2316 		now = time(NULL);
2317 		if (last_time > now) {
2318 			/* clock has been reset */
2319 			return (1);
2320 		}
2321 
2322 		if (next_event == NULL && !el_empty()) {
2323 			next_event = (struct event *)el_first();
2324 		}
2325 		if (next_event == NULL)
2326 			t = INFINITY;
2327 		else
2328 			t = (long)next_event->time - now;
2329 	}
2330 	return (0);
2331 }
2332 
2333 /*
2334  * This used to be in the idle(), but moved to the separate function.
2335  * This called from various place when cron needs to reap the
2336  * child. It includes the situation that cron hit maxrun, and needs
2337  * to reschedule the job.
2338  */
2339 static void
2340 reap_child()
2341 {
2342 	pid_t	pid;
2343 	int	prc;
2344 	struct	runinfo	*rp;
2345 
2346 	for (;;) {
2347 		pid = waitpid((pid_t)-1, &prc, WNOHANG);
2348 		if (pid <= 0)
2349 			break;
2350 #ifdef DEBUG
2351 		fprintf(stderr,
2352 			"wait returned %x for process %d\n", prc, pid);
2353 #endif
2354 		if ((rp = rinfo_get(pid)) == NULL) {
2355 			if (miscpid_delete(pid) == 0) {
2356 				/* not found in anywhere */
2357 				msg(PIDERR, pid);
2358 			}
2359 		} else if (rp->que == ZOMB) {
2360 			(void) unlink(rp->outfile);
2361 			rinfo_free(rp);
2362 		} else {
2363 			cleanup(rp, prc);
2364 		}
2365 	}
2366 }
2367 
2368 static void
2369 cleanup(struct runinfo *pr, int rc)
2370 {
2371 	int	nextfork = 1;
2372 	struct	usr	*p;
2373 	struct	stat	buf;
2374 
2375 	logit(ECHAR, pr, rc);
2376 	--qt[pr->que].nrun;
2377 	p = pr->rusr;
2378 	if (pr->que != CRONEVENT)
2379 		--p->aruncnt;
2380 	else
2381 		--p->cruncnt;
2382 
2383 	if (!lstat(pr->outfile, &buf)) {
2384 		if ((buf.st_mode != S_IFLNK) &&
2385 		    (buf.st_size > 0 || pr->mailwhendone)) {
2386 			/* mail user stdout and stderr */
2387 			for (;;) {
2388 				if ((pr->pid = fork()) < 0) {
2389 					/*
2390 					 * if fork fails try forever in doubling
2391 					 * retry times, up to 16 seconds
2392 					 */
2393 					(void) sleep(nextfork);
2394 					if (nextfork < 16)
2395 						nextfork += nextfork;
2396 					continue;
2397 				} else if (pr->pid == 0) {
2398 					child_sigreset();
2399 					contract_clear_template();
2400 
2401 					mail_result(p, pr, buf.st_size);
2402 					/* NOTREACHED */
2403 				} else {
2404 					contract_abandon_latest(pr->pid);
2405 					pr->que = ZOMB;
2406 					break;
2407 				}
2408 			}
2409 		} else {
2410 			(void) unlink(pr->outfile);
2411 			rinfo_free(pr);
2412 		}
2413 	} else {
2414 		rinfo_free(pr);
2415 	}
2416 
2417 	free_if_unused(p);
2418 }
2419 
2420 /*
2421  * Mail stdout and stderr of a job to user. Get uid for real user and become
2422  * that person. We do this so that mail won't come from root since this
2423  * could be a security hole. If failure, quit - don't send mail as root.
2424  */
2425 static void
2426 mail_result(struct usr *p, struct runinfo *pr, size_t filesize)
2427 {
2428 	struct	passwd	*ruser_ids;
2429 	FILE	*mailpipe;
2430 	FILE	*st;
2431 	struct utsname	name;
2432 	int	nbytes;
2433 	char	iobuf[BUFSIZ];
2434 	char	*cmd;
2435 
2436 	(void) uname(&name);
2437 	if ((ruser_ids = getpwnam(p->name)) == NULL)
2438 		exit(0);
2439 	(void) setuid(ruser_ids->pw_uid);
2440 
2441 	cmd = xmalloc(strlen(MAIL)+strlen(p->name)+2);
2442 	(void) sprintf(cmd, "%s %s", MAIL, p->name);
2443 	mailpipe = popen(cmd, "w");
2444 	contract_abandon_latest(0);
2445 	free(cmd);
2446 	if (mailpipe == NULL)
2447 		exit(127);
2448 	(void) fprintf(mailpipe, "To: %s\n", p->name);
2449 	if (pr->jobtype == CRONEVENT) {
2450 		(void) fprintf(mailpipe, CRONOUT);
2451 		(void) fprintf(mailpipe, "Your \"cron\" job on %s\n",
2452 			name.nodename);
2453 		if (pr->jobname != NULL) {
2454 			(void) fprintf(mailpipe, "%s\n\n", pr->jobname);
2455 		}
2456 	} else {
2457 		(void) fprintf(mailpipe, "Subject: Output from \"at\" job\n\n");
2458 		(void) fprintf(mailpipe, "Your \"at\" job on %s\n",
2459 			name.nodename);
2460 		if (pr->jobname != NULL) {
2461 			(void) fprintf(mailpipe, "\"%s\"\n\n", pr->jobname);
2462 		}
2463 	}
2464 	/* Tmp. file is fopen'ed w/ "r",  secure open */
2465 	if (filesize > 0 &&
2466 	    (st = fopen(pr->outfile, "r")) != NULL) {
2467 		(void) fprintf(mailpipe,
2468 			"produced the following output:\n\n");
2469 		while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0)
2470 			(void) fwrite(iobuf, sizeof (char), nbytes, mailpipe);
2471 		(void) fclose(st);
2472 	} else {
2473 		(void) fprintf(mailpipe, "completed.\n");
2474 	}
2475 	(void) pclose(mailpipe);
2476 	exit(0);
2477 }
2478 
2479 static int
2480 msg_wait(long tim)
2481 {
2482 	struct	message	msg;
2483 	int	cnt;
2484 	time_t	reftime;
2485 	struct	pollfd pfd[2];
2486 	int64_t	tl;
2487 	int	timeout;
2488 	static int	pending_msg;
2489 	static time_t	pending_reftime;
2490 
2491 	if (pending_msg) {
2492 		process_msg(&msgbuf, pending_reftime);
2493 		pending_msg = 0;
2494 		return (0);
2495 	}
2496 
2497 	/*
2498 	 * We are opening the signal mask to receive SIGCLD. The notifypipe
2499 	 * is used to avoid race condition between SIGCLD and poll system
2500 	 * call.
2501 	 * If SIGCLD is delivered in poll(), poll will be interrupted, and
2502 	 * we will return to idle() to reap the dead children.
2503 	 * If SIGCLD is delivered between sigprocmask() below and poll(),
2504 	 * there is no way we can detect the SIGCLD because poll() won't
2505 	 * be interrupted. In such case, the dead children can't be wait'ed
2506 	 * until poll returns by timeout or a new job. To avoid this race
2507 	 * condition, child_handler write to the notifypipe, so that
2508 	 * poll() will be able to return with POLLIN which indicates that
2509 	 * we have received SIGCLD.
2510 	 *
2511 	 * Since the notifypipe is used to just let poll return from
2512 	 * system call, the data in the pipe won't be read. Therefore,
2513 	 * any data in the pipe needs to be flushed before opening signal
2514 	 * mask.
2515 	 *
2516 	 * Note that we can probably re-write this code with pselect()
2517 	 * which can handle this situation easily.
2518 	 */
2519 	(void) ioctl(notifypipe[0], I_FLUSH, FLUSHW);
2520 
2521 	pfd[0].fd = msgfd;
2522 	pfd[0].events = POLLIN;
2523 	pfd[1].fd = notifypipe[1];
2524 	pfd[1].events = POLLIN;
2525 
2526 #ifdef CRON_MAXSLEEP
2527 	/*
2528 	 * CRON_MAXSLEEP can be defined to have cron periodically wake
2529 	 * up, so that cron can detect a change of TOD and adjust the
2530 	 * sleep time accordingly.
2531 	 */
2532 	tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim;
2533 #endif
2534 	tl = (tim == INFINITY) ? -1ll : (int64_t)tim * 1000;
2535 
2536 	accept_sigcld = 1;
2537 	(void) sigprocmask(SIG_UNBLOCK, &childmask, NULL);
2538 	do {
2539 		timeout = (tl > INT_MAX ? INT_MAX : (int)tl);
2540 		tl -= timeout;
2541 		cnt = poll(pfd, 2, timeout);
2542 		if (cnt == -1 && errno != EINTR) {
2543 			perror("! poll");
2544 		}
2545 	} while (tl > 0 && cnt == 0);
2546 	(void) sigprocmask(SIG_BLOCK, &childmask, NULL);
2547 	accept_sigcld = 0;
2548 
2549 	/*
2550 	 * poll timeout or interrupted.
2551 	 */
2552 	if (cnt <= 0)
2553 		return (0);
2554 
2555 	/*
2556 	 * Not the timeout or new job, but a SIGCLD has been delivered.
2557 	 */
2558 	if ((pfd[0].revents & POLLIN) == 0)
2559 		return (0);
2560 
2561 	errno = 0;
2562 	if ((cnt = read(msgfd, &msg, sizeof (msg))) != sizeof (msg)) {
2563 		if (cnt != -1 || errno != EAGAIN)
2564 			perror("! read");
2565 		return (0);
2566 	}
2567 	reftime = time(NULL);
2568 	if (next_event != NULL && reftime >= next_event->time) {
2569 		/*
2570 		 * we need to run the job before reloading crontab.
2571 		 */
2572 		(void) memcpy(&msgbuf, &msg, sizeof (msg));
2573 		pending_msg = 1;
2574 		pending_reftime = reftime;
2575 		return (1);
2576 	}
2577 	process_msg(&msg, reftime);
2578 	return (0);
2579 }
2580 
2581 /*
2582  * process the message supplied via pipe. This will be called either
2583  * immediately after cron read the message from pipe, or idle time
2584  * if the message was pending due to the job execution.
2585  */
2586 static void
2587 process_msg(struct message *pmsg, time_t reftime)
2588 {
2589 	if (pmsg->etype == NULL)
2590 		return;
2591 
2592 	switch (pmsg->etype) {
2593 	case AT:
2594 		if (pmsg->action == DELETE)
2595 			del_atjob(pmsg->fname, pmsg->logname);
2596 		else
2597 			mod_atjob(pmsg->fname, (time_t)0);
2598 		break;
2599 	case CRON:
2600 		if (pmsg->action == DELETE)
2601 			del_ctab(pmsg->fname);
2602 		else
2603 			mod_ctab(pmsg->fname, reftime);
2604 		break;
2605 	default:
2606 		msg("message received - bad format");
2607 		break;
2608 	}
2609 	if (next_event != NULL) {
2610 		if (next_event->etype == CRONEVENT)
2611 			el_add(next_event, next_event->time,
2612 				(next_event->u)->ctid);
2613 		else	/* etype == ATEVENT */
2614 			el_add(next_event, next_event->time,
2615 				next_event->of.at.eventid);
2616 		next_event = NULL;
2617 	}
2618 	(void) fflush(stdout);
2619 	pmsg->etype = NULL;
2620 }
2621 
2622 static struct runinfo *
2623 rinfo_get(pid_t pid)
2624 {
2625 	struct runinfo *rp;
2626 
2627 	for (rp = rt; rp < rt+MAXRUN; rp++) {
2628 		if (rp->pid == pid)
2629 			break;
2630 	}
2631 	if (rp >= rt+MAXRUN)
2632 		return (NULL);
2633 	else
2634 		return (rp);
2635 }
2636 
2637 /*
2638  * Free memory used for output file name and job name in runinfo structure
2639  */
2640 static void
2641 rinfo_free(struct runinfo *rp)
2642 {
2643 	free(rp->outfile);
2644 	free(rp->jobname);
2645 	rp->outfile = rp->jobname = NULL;
2646 	rp->pid = 0;
2647 }
2648 
2649 /* ARGSUSED */
2650 static void
2651 thaw_handler(int sig)
2652 {
2653 	;
2654 }
2655 
2656 
2657 /* ARGSUSED */
2658 static void
2659 cronend(int sig)
2660 {
2661 	crabort("SIGTERM", REMOVE_FIFO);
2662 }
2663 
2664 /*ARGSUSED*/
2665 static void
2666 child_handler(int sig)
2667 {
2668 	/*
2669 	 * Just in case someone changes the signal mask.
2670 	 * we don't want to notify the SIGCLD.
2671 	 */
2672 	if (accept_sigcld) {
2673 		(void) write(notifypipe[0], &sig, 1);
2674 	}
2675 }
2676 
2677 static void
2678 child_sigreset(void)
2679 {
2680 	(void) signal(SIGCLD, SIG_DFL);
2681 	(void) sigprocmask(SIG_SETMASK, &defmask, NULL);
2682 }
2683 
2684 /*
2685  * crabort() - handle exits out of cron
2686  */
2687 static void
2688 crabort(char *mssg, int action)
2689 {
2690 	int	c;
2691 
2692 	if (action & REMOVE_FIFO) {
2693 		/* FIFO vanishes when cron finishes */
2694 		if (unlink(FIFO) < 0)
2695 			perror("cron could not unlink FIFO");
2696 	}
2697 
2698 	if (action & CONSOLE_MSG) {
2699 		/* write error msg to console */
2700 		if ((c = open(CONSOLE, O_WRONLY)) >= 0) {
2701 			(void) write(c, "cron aborted: ", 14);
2702 			(void) write(c, mssg, strlen(mssg));
2703 			(void) write(c, "\n", 1);
2704 			(void) close(c);
2705 		}
2706 	}
2707 
2708 	/* always log the message */
2709 	msg(mssg);
2710 	msg("******* CRON ABORTED ********");
2711 	exit(1);
2712 }
2713 
2714 /*
2715  * msg() - time-stamped error reporting function
2716  */
2717 /*PRINTFLIKE1*/
2718 static void
2719 msg(char *fmt, ...)
2720 {
2721 	va_list args;
2722 	time_t	t;
2723 
2724 	t = time(NULL);
2725 
2726 	(void) fflush(stdout);
2727 
2728 	(void) fprintf(stderr, "! ");
2729 
2730 	va_start(args, fmt);
2731 	(void) vfprintf(stderr, fmt, args);
2732 	va_end(args);
2733 
2734 	(void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
2735 	(void) fprintf(stderr, " %s\n", timebuf);
2736 
2737 	(void) fflush(stderr);
2738 }
2739 
2740 static void
2741 logit(int cc, struct runinfo *rp, int rc)
2742 {
2743 	time_t t;
2744 	int    ret;
2745 
2746 	if (!log)
2747 		return;
2748 
2749 	t = time(NULL);
2750 	if (cc == BCHAR)
2751 		(void) printf("%c  CMD: %s\n", cc, next_event->cmd);
2752 	(void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
2753 	(void) printf("%c  %.8s %u %c %s",
2754 		cc, (rp->rusr)->name, rp->pid, QUE(rp->que), timebuf);
2755 	if ((ret = TSTAT(rc)) != 0)
2756 		(void) printf(" ts=%d", ret);
2757 	if ((ret = RCODE(rc)) != 0)
2758 		(void) printf(" rc=%d", ret);
2759 	(void) putchar('\n');
2760 	(void) fflush(stdout);
2761 }
2762 
2763 static void
2764 resched(int delay)
2765 {
2766 	time_t	nt;
2767 
2768 	/* run job at a later time */
2769 	nt = next_event->time + delay;
2770 	if (next_event->etype == CRONEVENT) {
2771 		next_event->time = next_time(next_event, (time_t)0);
2772 		if (nt < next_event->time)
2773 			next_event->time = nt;
2774 		el_add(next_event, next_event->time, (next_event->u)->ctid);
2775 		delayed = 1;
2776 		msg("rescheduling a cron job");
2777 		return;
2778 	}
2779 	add_atevent(next_event->u, next_event->cmd, nt, next_event->etype);
2780 	msg("rescheduling at job");
2781 }
2782 
2783 static void
2784 quedefs(int action)
2785 {
2786 	int	i;
2787 	int	j;
2788 	char	qbuf[QBUFSIZ];
2789 	FILE	*fd;
2790 
2791 	/* set up default queue definitions */
2792 	for (i = 0; i < NQUEUE; i++) {
2793 		qt[i].njob = qd.njob;
2794 		qt[i].nice = qd.nice;
2795 		qt[i].nwait = qd.nwait;
2796 	}
2797 	if (action == DEFAULT)
2798 		return;
2799 	if ((fd = fopen(QUEDEFS, "r")) == NULL) {
2800 		msg("cannot open quedefs file");
2801 		msg("using default queue definitions");
2802 		return;
2803 	}
2804 	while (fgets(qbuf, QBUFSIZ, fd) != NULL) {
2805 		if ((j = qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.')
2806 			continue;
2807 		parsqdef(&qbuf[2]);
2808 		qt[j].njob = qq.njob;
2809 		qt[j].nice = qq.nice;
2810 		qt[j].nwait = qq.nwait;
2811 	}
2812 	(void) fclose(fd);
2813 }
2814 
2815 static void
2816 parsqdef(char *name)
2817 {
2818 	int i;
2819 
2820 	qq = qd;
2821 	while (*name) {
2822 		i = 0;
2823 		while (isdigit(*name)) {
2824 			i *= 10;
2825 			i += *name++ - '0';
2826 		}
2827 		switch (*name++) {
2828 		case JOBF:
2829 			qq.njob = i;
2830 			break;
2831 		case NICEF:
2832 			qq.nice = i;
2833 			break;
2834 		case WAITF:
2835 			qq.nwait = i;
2836 			break;
2837 		}
2838 	}
2839 }
2840 
2841 /*
2842  * defaults - read defaults from /etc/default/cron
2843  */
2844 static void
2845 defaults()
2846 {
2847 	int  flags;
2848 	char *deflog;
2849 	char *hz, *tz;
2850 
2851 	/*
2852 	 * get HZ value for environment
2853 	 */
2854 	if ((hz = getenv("HZ")) == (char *)NULL)
2855 		(void) sprintf(hzname, "HZ=%d", HZ);
2856 	else
2857 		(void) snprintf(hzname, sizeof (hzname), "HZ=%s", hz);
2858 	/*
2859 	 * get TZ value for environment
2860 	 */
2861 	(void) snprintf(tzone, sizeof (tzone), "TZ=%s",
2862 		((tz = getenv("TZ")) != NULL) ? tz : DEFTZ);
2863 
2864 	if (defopen(DEFFILE) == 0) {
2865 		/* ignore case */
2866 		flags = defcntl(DC_GETFLAGS, 0);
2867 		TURNOFF(flags, DC_CASE);
2868 		(void) defcntl(DC_SETFLAGS, flags);
2869 
2870 		if (((deflog = defread("CRONLOG=")) == NULL) ||
2871 		    (*deflog == 'N') || (*deflog == 'n'))
2872 			log = 0;
2873 		else
2874 			log = 1;
2875 		/* fix for 1087611 - allow paths to be set in defaults file */
2876 		if ((Def_path = defread("PATH=")) != NULL) {
2877 			(void) strlcat(path, Def_path, LINE_MAX);
2878 		} else {
2879 			(void) strlcpy(path, NONROOTPATH, LINE_MAX);
2880 		}
2881 		if ((Def_supath = defread("SUPATH=")) != NULL) {
2882 			(void) strlcat(supath, Def_supath, LINE_MAX);
2883 		} else {
2884 			(void) strlcpy(supath, ROOTPATH, LINE_MAX);
2885 		}
2886 		(void) defopen(NULL);
2887 	}
2888 }
2889 
2890 /*
2891  * Determine if a user entry for a job is still ok.  The method used here
2892  * is a lot (about 75x) faster than using setgrent() / getgrent()
2893  * endgrent().  It should be safe because we use the sysconf to determine
2894  * the max, and it tolerates the max being 0.
2895  */
2896 
2897 static int
2898 verify_user_cred(const struct usr *u)
2899 {
2900 	struct passwd *pw;
2901 	size_t numUsrGrps = 0;
2902 	size_t numOrigGrps = 0;
2903 	size_t i;
2904 	int retval;
2905 
2906 	/*
2907 	 * Maximum number of groups a user may be in concurrently.  This
2908 	 * is a value which we obtain at runtime through a sysconf()
2909 	 * call.
2910 	 */
2911 
2912 	static size_t nGroupsMax = (size_t)-1;
2913 
2914 	/*
2915 	 * Arrays for cron user's group list, constructed at startup to
2916 	 * be nGroupsMax elements long, used for verifying user
2917 	 * credentials prior to execution.
2918 	 */
2919 
2920 	static gid_t *UsrGrps;
2921 	static gid_t *OrigGrps;
2922 
2923 	if (((pw = getpwnam(u->name)) == NULL) ||
2924 	    (pw->pw_uid != u->uid)) {
2925 		return (VUC_BADUSER);
2926 	}
2927 
2928 	/*
2929 	 * Create the group id lists needed for job credential
2930 	 * verification.
2931 	 */
2932 
2933 	if (nGroupsMax == (size_t)-1) {
2934 		if ((nGroupsMax = sysconf(_SC_NGROUPS_MAX)) > 0) {
2935 			UsrGrps = xcalloc(nGroupsMax, sizeof (gid_t));
2936 			OrigGrps = xcalloc(nGroupsMax, sizeof (gid_t));
2937 		}
2938 
2939 #ifdef DEBUG
2940 		(void) fprintf(stderr, "nGroupsMax = %ld\n", nGroupsMax);
2941 #endif
2942 	}
2943 
2944 #ifdef DEBUG
2945 	(void) fprintf(stderr, "verify_user_cred (%s-%d)\n", pw->pw_name,
2946 	    pw->pw_uid);
2947 	(void) fprintf(stderr, "verify_user_cred: pw->pw_gid = %d, "
2948 	    "u->gid = %d\n", pw->pw_gid, u->gid);
2949 #endif
2950 
2951 	retval = (u->gid == pw->pw_gid) ? VUC_OK : VUC_NOTINGROUP;
2952 
2953 	if (nGroupsMax > 0) {
2954 		numOrigGrps = getgroups(nGroupsMax, OrigGrps);
2955 
2956 		(void) initgroups(pw->pw_name, pw->pw_gid);
2957 		numUsrGrps = getgroups(nGroupsMax, UsrGrps);
2958 
2959 		for (i = 0; i < numUsrGrps; i++) {
2960 			if (UsrGrps[i] == u->gid) {
2961 				retval = VUC_OK;
2962 				break;
2963 			}
2964 		}
2965 
2966 		if (OrigGrps) {
2967 			(void) setgroups(numOrigGrps, OrigGrps);
2968 		}
2969 	}
2970 
2971 #ifdef DEBUG
2972 	(void) fprintf(stderr, "verify_user_cred: VUC = %d\n", retval);
2973 #endif
2974 
2975 	return (retval);
2976 }
2977 
2978 static int
2979 set_user_cred(const struct usr *u, struct project *pproj)
2980 {
2981 	static char *progname = "cron";
2982 	int r = 0, rval = 0;
2983 
2984 	if ((r = pam_start(progname, u->name, &pam_conv, &pamh))
2985 		!= PAM_SUCCESS) {
2986 #ifdef DEBUG
2987 		msg("pam_start returns %d\n", r);
2988 #endif
2989 		rval = VUC_BADUSER;
2990 		goto set_eser_cred_exit;
2991 	}
2992 
2993 	r = pam_acct_mgmt(pamh, 0);
2994 #ifdef DEBUG
2995 	msg("pam_acc_mgmt returns %d\n", r);
2996 #endif
2997 	if (r == PAM_ACCT_EXPIRED) {
2998 		rval = VUC_EXPIRED;
2999 		goto set_eser_cred_exit;
3000 	}
3001 	if (r == PAM_NEW_AUTHTOK_REQD) {
3002 		rval = VUC_NEW_AUTH;
3003 		goto set_eser_cred_exit;
3004 	}
3005 	if (r != PAM_SUCCESS) {
3006 		rval = VUC_BADUSER;
3007 		goto set_eser_cred_exit;
3008 	}
3009 
3010 	if (pproj != NULL) {
3011 		size_t sz = sizeof (PROJECT) + strlen(pproj->pj_name);
3012 		char *buf = alloca(sz);
3013 
3014 		(void) snprintf(buf, sz, PROJECT "%s", pproj->pj_name);
3015 		(void) pam_set_item(pamh, PAM_RESOURCE, buf);
3016 	}
3017 
3018 	r = pam_setcred(pamh, PAM_ESTABLISH_CRED);
3019 	if (r != PAM_SUCCESS)
3020 		rval = VUC_BADUSER;
3021 
3022 set_eser_cred_exit:
3023 	(void) pam_end(pamh, r);
3024 	return (rval);
3025 }
3026 
3027 static void
3028 clean_out_user(struct usr *u)
3029 {
3030 	if (next_event->u == u) {
3031 		next_event = NULL;
3032 	}
3033 
3034 	clean_out_ctab(u);
3035 	clean_out_atjobs(u);
3036 	free_if_unused(u);
3037 }
3038 
3039 static void
3040 clean_out_atjobs(struct usr *u)
3041 {
3042 	struct event *ev, *pv;
3043 
3044 	for (pv = NULL, ev = u->atevents;
3045 		ev != NULL;
3046 		pv = ev, ev = ev->link, free(pv)) {
3047 		el_remove(ev->of.at.eventid, 1);
3048 		if (cwd == AT)
3049 			cron_unlink(ev->cmd);
3050 		else {
3051 			char buf[PATH_MAX];
3052 			if (strlen(ATDIR) + strlen(ev->cmd) + 2
3053 				< PATH_MAX) {
3054 				(void) sprintf(buf, "%s/%s", ATDIR, ev->cmd);
3055 				cron_unlink(buf);
3056 			}
3057 		}
3058 		free(ev->cmd);
3059 	}
3060 
3061 	u->atevents = NULL;
3062 }
3063 
3064 static void
3065 clean_out_ctab(struct usr *u)
3066 {
3067 	rm_ctevents(u);
3068 	el_remove(u->ctid, 0);
3069 	u->ctid = 0;
3070 	u->ctexists = 0;
3071 }
3072 
3073 static void
3074 cron_unlink(char *name)
3075 {
3076 	int r;
3077 
3078 	r = unlink(name);
3079 	if (r == 0 || (r == -1 && errno == ENOENT)) {
3080 		(void) audit_cron_delete_anc_file(name, NULL);
3081 	}
3082 }
3083 
3084 static void
3085 create_anc_ctab(struct event *e)
3086 {
3087 	if (audit_cron_create_anc_file(e->u->name,
3088 		(cwd == CRON) ? NULL:CRONDIR,
3089 		e->u->name,
3090 		e->u->uid) == -1) {
3091 		process_anc_files(CRON_ANC_DELETE);
3092 		crabort("cannot create ancillary files for crontabs",
3093 			REMOVE_FIFO|CONSOLE_MSG);
3094 	}
3095 }
3096 
3097 static void
3098 delete_anc_ctab(struct event *e)
3099 {
3100 	(void) audit_cron_delete_anc_file(e->u->name,
3101 		(cwd == CRON) ? NULL:CRONDIR);
3102 }
3103 
3104 static void
3105 create_anc_atjob(struct event *e)
3106 {
3107 	if (!e->of.at.exists)
3108 		return;
3109 
3110 	if (audit_cron_create_anc_file(e->cmd,
3111 		(cwd == AT) ? NULL:ATDIR,
3112 		e->u->name,
3113 		e->u->uid) == -1) {
3114 		process_anc_files(CRON_ANC_DELETE);
3115 		crabort("cannot create ancillary files for atjobs",
3116 			REMOVE_FIFO|CONSOLE_MSG);
3117 	}
3118 }
3119 
3120 static void
3121 delete_anc_atjob(struct event *e)
3122 {
3123 	if (!e->of.at.exists)
3124 		return;
3125 
3126 	(void) audit_cron_delete_anc_file(e->cmd,
3127 		(cwd == AT) ? NULL:ATDIR);
3128 }
3129 
3130 
3131 static void
3132 process_anc_files(int del)
3133 {
3134 	struct usr	*u = uhead;
3135 	struct event	*e;
3136 
3137 	if (!audit_cron_mode())
3138 		return;
3139 
3140 	for (;;) {
3141 		if (u->ctexists && u->ctevents != NULL) {
3142 			e = u->ctevents;
3143 			for (;;) {
3144 				if (del)
3145 					delete_anc_ctab(e);
3146 				else
3147 					create_anc_ctab(e);
3148 				if ((e = e->link) == NULL)
3149 					break;
3150 			}
3151 		}
3152 
3153 		if (u->atevents != NULL) {
3154 			e = u->atevents;
3155 			for (;;) {
3156 				if (del)
3157 					delete_anc_atjob(e);
3158 				else
3159 					create_anc_atjob(e);
3160 				if ((e = e->link) == NULL)
3161 					break;
3162 			}
3163 		}
3164 
3165 		if ((u = u->nextusr)  == NULL)
3166 			break;
3167 	}
3168 }
3169 
3170 /*ARGSUSED*/
3171 static int
3172 cron_conv(int num_msg, struct pam_message **msgs,
3173     struct pam_response **response, void *appdata_ptr)
3174 {
3175 	struct pam_message	**m = msgs;
3176 	int i;
3177 
3178 	for (i = 0; i < num_msg; i++) {
3179 		switch (m[i]->msg_style) {
3180 		case PAM_ERROR_MSG:
3181 		case PAM_TEXT_INFO:
3182 			if (m[i]->msg != NULL) {
3183 				(void) msg("%s\n", m[i]->msg);
3184 			}
3185 			break;
3186 
3187 		default:
3188 			break;
3189 		}
3190 	}
3191 	return (0);
3192 }
3193 
3194 /*
3195  * Cron creates process for other than job. Mail process is the
3196  * one which rinfo does not cover. Therefore, miscpid will keep
3197  * track of the pids executed from cron. Otherwise, we will see
3198  * "unexpected pid returned.." messages appear in the log file.
3199  */
3200 static void
3201 miscpid_insert(pid_t pid)
3202 {
3203 	struct miscpid *mp;
3204 
3205 	mp = xmalloc(sizeof (*mp));
3206 	mp->pid = pid;
3207 	mp->next = miscpid_head;
3208 	miscpid_head = mp;
3209 }
3210 
3211 static int
3212 miscpid_delete(pid_t pid)
3213 {
3214 	struct miscpid *mp, *omp;
3215 	int found = 0;
3216 
3217 	omp = NULL;
3218 	for (mp = miscpid_head; mp != NULL; mp = mp->next) {
3219 		if (mp->pid == pid) {
3220 			found = 1;
3221 			break;
3222 		}
3223 		omp = mp;
3224 	}
3225 	if (found) {
3226 		if (omp != NULL)
3227 			omp->next = mp->next;
3228 		else
3229 			miscpid_head = NULL;
3230 		free(mp);
3231 	}
3232 	return (found);
3233 }
3234 
3235 /*
3236  * Establish contract terms such that all children are in abandoned
3237  * process contracts.
3238  */
3239 static int
3240 contract_set_template()
3241 {
3242 	int fd;
3243 
3244 	if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3245 		crabort("cannot open process contract template",
3246 		    REMOVE_FIFO | CONSOLE_MSG);
3247 
3248 	if (ct_pr_tmpl_set_param(fd, 0) ||
3249 	    ct_tmpl_set_informative(fd, 0) ||
3250 	    ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR))
3251 		crabort("cannot establish contract template terms",
3252 		    REMOVE_FIFO | CONSOLE_MSG);
3253 
3254 	if (ct_tmpl_activate(fd))
3255 		crabort("cannot activate contract template",
3256 		    REMOVE_FIFO | CONSOLE_MSG);
3257 
3258 	(void) close(fd);
3259 }
3260 
3261 /*
3262  * Clear active process contract template.
3263  */
3264 static int
3265 contract_clear_template()
3266 {
3267 	int fd;
3268 
3269 	if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3270 		crabort("cannot open process contract template",
3271 		    REMOVE_FIFO | CONSOLE_MSG);
3272 
3273 	if (ct_tmpl_clear(fd))
3274 		crabort("cannot clear contract template",
3275 		    REMOVE_FIFO | CONSOLE_MSG);
3276 
3277 	(void) close(fd);
3278 }
3279 
3280 /*
3281  * Abandon latest process contract unconditionally.  If we have leaked [some
3282  * critical amount], exit such that the kernel reaps our contracts.
3283  */
3284 static void
3285 contract_abandon_latest(pid_t pid)
3286 {
3287 	int r;
3288 	ctid_t id;
3289 	static uint_t cts_lost;
3290 
3291 	if (cts_lost > MAX_LOST_CONTRACTS)
3292 		crabort("repeated failure to abandon contracts",
3293 		    REMOVE_FIFO | CONSOLE_MSG);
3294 
3295 	if (r = contract_latest(&id)) {
3296 		if (pid == 0)
3297 			msg("could not obtain latest contract from "
3298 			    "popen(3C): %s", strerror(r));
3299 		else
3300 			msg("could not obtain latest contract for "
3301 			    "PID %ld: %s", pid, strerror(r));
3302 		cts_lost++;
3303 		return;
3304 	}
3305 
3306 	if (r = contract_abandon_id(id)) {
3307 		msg("could not abandon latest contract %ld: %s", id,
3308 		    strerror(r));
3309 		cts_lost++;
3310 		return;
3311 	}
3312 }
3313