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