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