add UNLEASHED_OBJ to unleashed.mk
[unleashed/tickless.git] / usr / src / cmd / cron / cron.c
blob9768fefd919653c8d861840914e79e0f23ed55ad
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 * Copyright 2013 Joshua M. Clulow <josh@sysmgr.org>
27 * Copyright (c) 2014 Gary Mills
28 * Copyright (c) 2016 by Delphix. All rights reserved.
31 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
32 /* All Rights Reserved */
34 /* Copyright (c) 1987, 1988 Microsoft Corporation */
35 /* All Rights Reserved */
38 #include <sys/contract/process.h>
39 #include <sys/ctfs.h>
40 #include <sys/param.h>
41 #include <sys/resource.h>
42 #include <sys/stat.h>
43 #include <sys/task.h>
44 #include <sys/time.h>
45 #include <sys/types.h>
46 #include <sys/utsname.h>
47 #include <sys/wait.h>
49 #include <security/pam_appl.h>
51 #include <alloca.h>
52 #include <ctype.h>
53 #include <deflt.h>
54 #include <dirent.h>
55 #include <errno.h>
56 #include <fcntl.h>
57 #include <grp.h>
58 #include <libcontract.h>
59 #include <libcontract_priv.h>
60 #include <limits.h>
61 #include <locale.h>
62 #include <poll.h>
63 #include <project.h>
64 #include <pwd.h>
65 #include <signal.h>
66 #include <stdarg.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #include <stropts.h>
71 #include <time.h>
72 #include <unistd.h>
74 #include "cron.h"
77 * #define DEBUG
80 #define MAIL "/usr/bin/mail" /* mail program to use */
81 #define CONSOLE "/dev/console" /* where messages go when cron dies */
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 */
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 */
92 #define INFINITY 2147483647L /* upper bound on time */
93 #define CUSHION 180L
94 #define ZOMB 100 /* proc slot used for mailing output */
96 #define JOBF 'j'
97 #define NICEF 'n'
98 #define USERF 'u'
99 #define WAITF 'w'
101 #define BCHAR '>'
102 #define ECHAR '<'
104 #define DEFAULT 0
105 #define LOAD 1
106 #define QBUFSIZ 80
108 /* Defined actions for crabort() routine */
109 #define NO_ACTION 000
110 #define REMOVE_FIFO 001
111 #define CONSOLE_MSG 002
113 #define BADCD "can't change directory to the crontab directory."
114 #define NOREADDIR "can't read the crontab directory."
116 #define BADJOBOPEN "unable to read your at job."
117 #define BADSHELL "because your login shell \
118 isn't /usr/bin/sh, you can't use cron."
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 \
125 commands."
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 \
131 one of your crontab commands. \
132 \nThat command was not executed."
134 #define NOTALLOWED "you are not authorized to use cron. Sorry."
135 #define STDERRMSG "\n\n********************************************\
136 *****\nCron: The previous message is the \
137 standard output and standard error \
138 \nof one of your cron commands.\n"
140 #define STDOUTERR "one of your commands generated output or errors, \
141 but cron was unable to mail you this output.\
142 \nRemember to redirect standard output and standard \
143 error for each of your commands."
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 MALLOCERR "out of space, cannot create new string\n"
150 #define DIDFORK didfork
151 #define NOFORK !didfork
153 #define MAILBUFLEN (8*1024)
154 #define LINELIMIT 80
155 #define MAILBINITFREE (MAILBUFLEN - (sizeof (cte_intro) - 1) \
156 - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1)
158 #define ERR_CRONTABENT 0 /* error in crontab file entry */
159 #define ERR_UNIXERR 1 /* error in some system call */
160 #define ERR_CANTEXECCRON 2 /* error setting up "cron" job environment */
161 #define ERR_CANTEXECAT 3 /* error setting up "at" job environment */
162 #define ERR_NOTREG 4 /* error not a regular file */
164 #define PROJECT "project="
166 #define MAX_LOST_CONTRACTS 2048 /* reset if this many failed abandons */
168 #define FORMAT "%a %b %e %H:%M:%S %Y"
169 static char timebuf[80];
171 static struct message msgbuf;
173 struct shared {
174 int count; /* usage count */
175 void (*free)(void *obj); /* routine that will free obj */
176 void *obj; /* object */
179 struct event {
180 time_t time; /* time of the event */
181 short etype; /* what type of event; 0=cron, 1=at */
182 char *cmd; /* command for cron, job name for at */
183 struct usr *u; /* ptr to the owner (usr) of this event */
184 struct event *link; /* ptr to another event for this user */
185 union {
186 struct { /* for crontab events */
187 char *minute; /* (these */
188 char *hour; /* fields */
189 char *daymon; /* are */
190 char *month; /* from */
191 char *dayweek; /* crontab) */
192 char *input; /* ptr to stdin */
193 struct shared *tz; /* timezone of this event */
194 struct shared *home; /* directory for this event */
195 struct shared *shell; /* shell for this event */
196 } ct;
197 struct { /* for at events */
198 short exists; /* for revising at events */
199 int eventid; /* for el_remove-ing at events */
200 } at;
201 } of;
204 struct usr {
205 char *name; /* name of user (e.g. "root") */
206 char *home; /* home directory for user */
207 uid_t uid; /* user id */
208 gid_t gid; /* group id */
209 int aruncnt; /* counter for running jobs per uid */
210 int cruncnt; /* counter for running cron jobs per uid */
211 int ctid; /* for el_remove-ing crontab events */
212 short ctexists; /* for revising crontab events */
213 struct event *ctevents; /* list of this usr's crontab events */
214 struct event *atevents; /* list of this usr's at events */
215 struct usr *nextusr;
216 }; /* ptr to next user */
218 static struct queue
220 int njob; /* limit */
221 int nice; /* nice for execution */
222 int nwait; /* wait time to next execution attempt */
223 int nrun; /* number running */
225 qd = {100, 2, 60}, /* default values for queue defs */
226 qt[NQUEUE];
227 static struct queue qq;
229 static struct runinfo
231 pid_t pid;
232 short que;
233 struct usr *rusr; /* pointer to usr struct */
234 char *outfile; /* file where stdout & stderr are trapped */
235 short jobtype; /* what type of event: 0=cron, 1=at */
236 char *jobname; /* command for "cron", jobname for "at" */
237 int mailwhendone; /* 1 = send mail even if no ouptut */
238 struct runinfo *next;
239 } *rthead;
241 static struct miscpid {
242 pid_t pid;
243 struct miscpid *next;
244 } *miscpid_head;
246 static pid_t cron_pid; /* own pid */
247 static char didfork = 0; /* flag to see if I'm process group leader */
248 static int msgfd; /* file descriptor for fifo queue */
249 static int ecid = 1; /* event class id for el_remove(); MUST be set to 1 */
250 static int delayed; /* is job being rescheduled or did it run first time */
251 static int cwd; /* current working directory */
252 static struct event *next_event; /* the next event to execute */
253 static struct usr *uhead; /* ptr to the list of users */
255 /* Variables for error handling at reading crontabs. */
256 static char cte_intro[] = "Line(s) with errors:\n\n";
257 static char cte_trail1[] = "\nMax number of errors encountered.";
258 static char cte_trail2[] = " Evaluation of crontab aborted.\n";
259 static int cte_free = MAILBINITFREE; /* Free buffer space */
260 static char *cte_text = NULL; /* Text buffer pointer */
261 static char *cte_lp; /* Next free line in cte_text */
262 static int cte_nvalid; /* Valid lines found */
264 /* user's default environment for the shell */
265 #define ROOTPATH "PATH=/usr/sbin:/usr/bin"
266 #define NONROOTPATH "PATH=/usr/bin:"
268 static char *Def_supath = NULL;
269 static char *Def_path = NULL;
270 static char path[LINE_MAX] = "PATH=";
271 static char supath[LINE_MAX] = "PATH=";
272 static char homedir[LINE_MAX] = ENV_HOME;
273 static char logname[LINE_MAX] = "LOGNAME=";
274 static char tzone[LINE_MAX] = ENV_TZ;
275 static char *envinit[] = {
276 homedir,
277 logname,
278 ROOTPATH,
279 "SHELL=/usr/bin/sh",
280 tzone,
281 NULL
284 extern char **environ;
286 #define DEFTZ "GMT"
287 static int log = 0;
288 static char hzname[10];
290 static void cronend(int);
291 static void thaw_handler(int);
292 static void child_handler(int);
293 static void child_sigreset(void);
295 static void mod_ctab(char *, time_t);
296 static void mod_atjob(char *, time_t);
297 static void add_atevent(struct usr *, char *, time_t, int);
298 static void rm_ctevents(struct usr *);
299 static void cleanup(struct runinfo *rn, int r);
300 static void crabort(char *, int);
301 static void msg(char *fmt, ...);
302 static void ignore_msg(char *, char *, struct event *);
303 static void logit(int, struct runinfo *, int);
304 static void parsqdef(char *);
305 static void defaults();
306 static void initialize(int);
307 static void quedefs(int);
308 static int idle(long);
309 static struct usr *find_usr(char *);
310 static int ex(struct event *e);
311 static void read_dirs(int);
312 static void mail(char *, char *, int);
313 static char *next_field(int, int);
314 static void readcron(struct usr *, time_t);
315 static int next_ge(int, char *);
316 static void free_if_unused(struct usr *);
317 static void del_atjob(char *, char *);
318 static void del_ctab(char *);
319 static void resched(int);
320 static int msg_wait(long);
321 static struct runinfo *rinfo_get(pid_t);
322 static void rinfo_free(struct runinfo *rp);
323 static void mail_result(struct usr *p, struct runinfo *pr, size_t filesize);
324 static time_t next_time(struct event *, time_t);
325 static time_t get_switching_time(int, time_t);
326 static time_t xmktime(struct tm *);
327 static void process_msg(struct message *, time_t);
328 static void reap_child(void);
329 static void miscpid_insert(pid_t);
330 static int miscpid_delete(pid_t);
331 static void contract_set_template(void);
332 static void contract_clear_template(void);
333 static void contract_abandon_latest(pid_t);
335 static void cte_init(void);
336 static void cte_add(int, char *);
337 static void cte_valid(void);
338 static int cte_istoomany(void);
339 static void cte_sendmail(char *);
341 static int set_user_cred(const struct usr *, struct project *);
343 static struct shared *create_shared_str(char *str);
344 static struct shared *dup_shared(struct shared *obj);
345 static void rel_shared(struct shared *obj);
346 static void *get_obj(struct shared *obj);
348 * last_time is set immediately prior to exection of an event (via ex())
349 * to indicate the last time an event was executed. This was (surely)
350 * it's original intended use.
352 static time_t last_time, init_time, t_old;
353 static int reset_needed; /* set to 1 when cron(1M) needs to re-initialize */
355 static int refresh;
356 static sigset_t defmask, sigmask;
359 * BSM hooks
361 extern int audit_cron_session(char *, char *, uid_t, gid_t, char *);
362 extern void audit_cron_new_job(char *, int, void *);
363 extern void audit_cron_bad_user(char *);
364 extern void audit_cron_user_acct_expired(char *);
365 extern int audit_cron_create_anc_file(char *, char *, char *, uid_t);
366 extern int audit_cron_delete_anc_file(char *, char *);
367 extern int audit_cron_is_anc_name(char *);
368 extern int audit_cron_mode();
370 static int cron_conv(int, struct pam_message **,
371 struct pam_response **, void *);
373 static struct pam_conv pam_conv = {cron_conv, NULL};
374 static pam_handle_t *pamh; /* Authentication handle */
377 * Function to help check a user's credentials.
380 static int verify_user_cred(struct usr *u);
383 * Values returned by verify_user_cred and set_user_cred:
386 #define VUC_OK 0
387 #define VUC_BADUSER 1
388 #define VUC_NOTINGROUP 2
389 #define VUC_EXPIRED 3
390 #define VUC_NEW_AUTH 4
393 * Modes of process_anc_files function
395 #define CRON_ANC_DELETE 1
396 #define CRON_ANC_CREATE 0
399 * Functions to remove a user or job completely from the running database.
401 static void clean_out_atjobs(struct usr *u);
402 static void clean_out_ctab(struct usr *u);
403 static void clean_out_user(struct usr *u);
404 static void cron_unlink(char *name);
405 static void process_anc_files(int);
408 * functions in elm.c
410 extern void el_init(int, time_t, time_t, int);
411 extern int el_add(void *, time_t, int);
412 extern void el_remove(int, int);
413 extern int el_empty(void);
414 extern void *el_first(void);
415 extern void el_delete(void);
417 static int valid_entry(char *, int);
418 static struct usr *create_ulist(char *, int);
419 static void init_cronevent(char *, int);
420 static void init_atevent(char *, time_t, int, int);
421 static void update_atevent(struct usr *, char *, time_t, int);
424 main(int argc, char *argv[])
426 time_t t;
427 time_t ne_time; /* amt of time until next event execution */
428 time_t newtime, lastmtime = 0L;
429 struct usr *u;
430 struct event *e, *e2, *eprev;
431 struct stat buf;
432 pid_t rfork;
433 struct sigaction act;
436 * reset_needed is set to 1 whenever el_add() finds out that a cron
437 * job is scheduled to be run before the time when cron(1M) daemon
438 * initialized.
439 * Other cases where a reset is needed is when ex() finds that the
440 * event to be executed is being run at the wrong time, or when idle()
441 * determines that time was reset.
442 * We immediately return to the top of the while (TRUE) loop in
443 * main() where the event list is cleared and rebuilt, and reset_needed
444 * is set back to 0.
446 reset_needed = 0;
449 * Only the privileged user can run this command.
451 if (getuid() != 0)
452 crabort(NOTALLOWED, 0);
454 begin:
455 (void) setlocale(LC_ALL, "");
456 /* fork unless 'nofork' is specified */
457 if ((argc <= 1) || (strcmp(argv[1], "nofork"))) {
458 if (rfork = fork()) {
459 if (rfork == (pid_t)-1) {
460 (void) sleep(30);
461 goto begin;
463 return (0);
465 didfork++;
466 (void) setpgrp(); /* detach cron from console */
469 (void) umask(022);
470 (void) signal(SIGHUP, SIG_IGN);
471 (void) signal(SIGINT, SIG_IGN);
472 (void) signal(SIGQUIT, SIG_IGN);
473 (void) signal(SIGTERM, cronend);
475 defaults();
476 initialize(1);
477 quedefs(DEFAULT); /* load default queue definitions */
478 cron_pid = getpid();
479 msg("*** cron started *** pid = %d", cron_pid);
481 /* setup THAW handler */
482 act.sa_handler = thaw_handler;
483 act.sa_flags = 0;
484 (void) sigemptyset(&act.sa_mask);
485 (void) sigaction(SIGTHAW, &act, NULL);
487 /* setup CHLD handler */
488 act.sa_handler = child_handler;
489 act.sa_flags = 0;
490 (void) sigemptyset(&act.sa_mask);
491 (void) sigaddset(&act.sa_mask, SIGCLD);
492 (void) sigaction(SIGCLD, &act, NULL);
494 (void) sigemptyset(&defmask);
495 (void) sigemptyset(&sigmask);
496 (void) sigaddset(&sigmask, SIGCLD);
497 (void) sigaddset(&sigmask, SIGTHAW);
498 (void) sigprocmask(SIG_BLOCK, &sigmask, NULL);
500 t_old = init_time;
501 last_time = t_old;
502 for (;;) { /* MAIN LOOP */
503 t = time(NULL);
504 if ((t_old > t) || (t-last_time > CUSHION) || reset_needed) {
505 reset_needed = 0;
507 * the time was set backwards or forward or
508 * refresh is requested.
510 if (refresh)
511 msg("re-scheduling jobs");
512 else
513 msg("time was reset, re-initializing");
514 el_delete();
515 u = uhead;
516 while (u != NULL) {
517 rm_ctevents(u);
518 e = u->atevents;
519 while (e != NULL) {
520 free(e->cmd);
521 e2 = e->link;
522 free(e);
523 e = e2;
525 u->atevents = NULL;
526 u = u->nextusr;
528 (void) close(msgfd);
529 initialize(0);
530 t = time(NULL);
531 last_time = t;
533 * reset_needed might have been set in the functions
534 * call path from initialize()
536 if (reset_needed) {
537 continue;
540 t_old = t;
542 if (next_event == NULL && !el_empty()) {
543 next_event = (struct event *)el_first();
545 if (next_event == NULL) {
546 ne_time = INFINITY;
547 } else {
548 ne_time = next_event->time - t;
549 #ifdef DEBUG
550 cftime(timebuf, "%+", &next_event->time);
551 (void) fprintf(stderr, "next_time=%ld %s\n",
552 next_event->time, timebuf);
553 #endif
555 if (ne_time > 0) {
557 * reset_needed may be set in the functions call path
558 * from idle()
560 if (idle(ne_time) || reset_needed) {
561 reset_needed = 1;
562 continue;
566 if (stat(QUEDEFS, &buf)) {
567 msg("cannot stat QUEDEFS file");
568 } else if (lastmtime != buf.st_mtime) {
569 quedefs(LOAD);
570 lastmtime = buf.st_mtime;
573 last_time = next_event->time; /* save execution time */
576 * reset_needed may be set in the functions call path
577 * from ex()
579 if (ex(next_event) || reset_needed) {
580 reset_needed = 1;
581 continue;
584 switch (next_event->etype) {
585 case CRONEVENT:
586 /* add cronevent back into the main event list */
587 if (delayed) {
588 delayed = 0;
589 break;
593 * check if time(0)< last_time. if so, then the
594 * system clock has gone backwards. to prevent this
595 * job from being started twice, we reschedule this
596 * job for the >>next time after last_time<<, and
597 * then set next_event->time to this. note that
598 * crontab's resolution is 1 minute.
601 if (last_time > time(NULL)) {
602 msg(CLOCK_DRIFT);
604 * bump up to next 30 second
605 * increment
606 * 1 <= newtime <= 30
608 newtime = 30 - (last_time % 30);
609 newtime += last_time;
612 * get the next scheduled event,
613 * not the one that we just
614 * kicked off!
616 next_event->time =
617 next_time(next_event, newtime);
618 t_old = time(NULL);
619 } else {
620 next_event->time =
621 next_time(next_event, (time_t)0);
623 #ifdef DEBUG
624 cftime(timebuf, "%+", &next_event->time);
625 (void) fprintf(stderr,
626 "pushing back cron event %s at %ld (%s)\n",
627 next_event->cmd, next_event->time, timebuf);
628 #endif
630 switch (el_add(next_event, next_event->time,
631 (next_event->u)->ctid)) {
632 case -1:
633 ignore_msg("main", "cron", next_event);
634 break;
635 case -2: /* event time lower than init time */
636 reset_needed = 1;
637 break;
639 break;
640 default:
641 /* remove at or batch job from system */
642 if (delayed) {
643 delayed = 0;
644 break;
646 eprev = NULL;
647 e = (next_event->u)->atevents;
648 while (e != NULL) {
649 if (e == next_event) {
650 if (eprev == NULL)
651 (e->u)->atevents = e->link;
652 else
653 eprev->link = e->link;
654 free(e->cmd);
655 free(e);
656 break;
657 } else {
658 eprev = e;
659 e = e->link;
662 break;
664 next_event = NULL;
667 /*NOTREACHED*/
670 static void
671 initialize(int firstpass)
673 #ifdef DEBUG
674 (void) fprintf(stderr, "in initialize\n");
675 #endif
676 if (firstpass) {
677 /* for mail(1), make sure messages come from root */
678 if (putenv("LOGNAME=root") != 0) {
679 crabort("cannot expand env variable",
680 REMOVE_FIFO|CONSOLE_MSG);
682 if (access(FIFO, R_OK) == -1) {
683 if (errno == ENOENT) {
684 if (mknod(FIFO, S_IFIFO|0600, 0) != 0)
685 crabort("cannot create fifo queue",
686 REMOVE_FIFO|CONSOLE_MSG);
687 } else {
688 if (NOFORK) {
689 /* didn't fork... init(1M) is waiting */
690 (void) sleep(60);
692 perror("FIFO");
693 crabort("cannot access fifo queue",
694 REMOVE_FIFO|CONSOLE_MSG);
696 } else {
697 if (NOFORK) {
698 /* didn't fork... init(1M) is waiting */
699 (void) sleep(60);
701 * the wait is painful, but we don't want
702 * init respawning this quickly
705 crabort("cannot start cron; FIFO exists", CONSOLE_MSG);
709 if ((msgfd = open(FIFO, O_RDWR)) < 0) {
710 perror("! open");
711 crabort("cannot open fifo queue", REMOVE_FIFO|CONSOLE_MSG);
714 init_time = time(NULL);
715 el_init(8, init_time, (time_t)(60*60*24), 10);
717 init_time = time(NULL);
718 el_init(8, init_time, (time_t)(60*60*24), 10);
721 * read directories, create users list, and add events to the
722 * main event list. Only zero user list on firstpass.
724 if (firstpass)
725 uhead = NULL;
726 read_dirs(firstpass);
727 next_event = NULL;
729 if (!firstpass)
730 return;
732 /* stdout is log file */
733 if (freopen(ACCTFILE, "a", stdout) == NULL)
734 (void) fprintf(stderr, "cannot open %s\n", ACCTFILE);
736 /* log should be root-only */
737 (void) fchmod(1, S_IRUSR|S_IWUSR);
739 /* stderr also goes to ACCTFILE */
740 (void) close(fileno(stderr));
741 (void) dup(1);
742 /* null for stdin */
743 (void) freopen("/dev/null", "r", stdin);
745 contract_set_template();
748 static void
749 read_dirs(int first)
751 DIR *dir;
752 struct dirent *dp;
753 char *ptr;
754 int jobtype;
755 time_t tim;
758 if (chdir(CRONDIR) == -1)
759 crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG);
760 cwd = CRON;
761 if ((dir = opendir(".")) == NULL)
762 crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG);
763 while ((dp = readdir(dir)) != NULL) {
764 if (!valid_entry(dp->d_name, CRONEVENT))
765 continue;
766 init_cronevent(dp->d_name, first);
768 (void) closedir(dir);
770 if (chdir(ATDIR) == -1) {
771 msg("cannot chdir to at directory");
772 return;
774 if ((dir = opendir(".")) == NULL) {
775 msg("cannot read at at directory");
776 return;
778 cwd = AT;
779 while ((dp = readdir(dir)) != NULL) {
780 if (!valid_entry(dp->d_name, ATEVENT))
781 continue;
782 ptr = dp->d_name;
783 if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
784 continue;
785 ptr++;
786 if (!isalpha(*ptr))
787 continue;
788 jobtype = *ptr - 'a';
789 if (jobtype >= NQUEUE) {
790 cron_unlink(dp->d_name);
791 continue;
793 init_atevent(dp->d_name, tim, jobtype, first);
795 (void) closedir(dir);
798 static int
799 valid_entry(char *name, int type)
801 struct stat buf;
803 if (strcmp(name, ".") == 0 ||
804 strcmp(name, "..") == 0)
805 return (0);
807 /* skip over ancillary file names */
808 if (audit_cron_is_anc_name(name))
809 return (0);
811 if (stat(name, &buf)) {
812 mail(name, BADSTAT, ERR_UNIXERR);
813 cron_unlink(name);
814 return (0);
816 if (!S_ISREG(buf.st_mode)) {
817 mail(name, BADTYPE, ERR_NOTREG);
818 cron_unlink(name);
819 return (0);
821 if (type == ATEVENT) {
822 if (!(buf.st_mode & ISUID)) {
823 cron_unlink(name);
824 return (0);
827 return (1);
830 struct usr *
831 create_ulist(char *name, int type)
833 struct usr *u;
835 u = xcalloc(1, sizeof (struct usr));
836 u->name = xstrdup(name);
837 if (type == CRONEVENT) {
838 u->ctexists = TRUE;
839 u->ctid = ecid++;
840 } else {
841 u->ctexists = FALSE;
842 u->ctid = 0;
844 u->uid = (uid_t)-1;
845 u->gid = (uid_t)-1;
846 u->nextusr = uhead;
847 uhead = u;
848 return (u);
851 void
852 init_cronevent(char *name, int first)
854 struct usr *u;
856 if (first) {
857 u = create_ulist(name, CRONEVENT);
858 readcron(u, 0);
859 } else {
860 if ((u = find_usr(name)) == NULL) {
861 u = create_ulist(name, CRONEVENT);
862 readcron(u, 0);
863 } else {
864 u->ctexists = TRUE;
865 rm_ctevents(u);
866 el_remove(u->ctid, 0);
867 readcron(u, 0);
872 void
873 init_atevent(char *name, time_t tim, int jobtype, int first)
875 struct usr *u;
877 if (first) {
878 u = create_ulist(name, ATEVENT);
879 add_atevent(u, name, tim, jobtype);
880 } else {
881 if ((u = find_usr(name)) == NULL) {
882 u = create_ulist(name, ATEVENT);
883 add_atevent(u, name, tim, jobtype);
884 } else {
885 update_atevent(u, name, tim, jobtype);
890 static void
891 mod_ctab(char *name, time_t reftime)
893 struct passwd *pw;
894 struct stat buf;
895 struct usr *u;
896 char namebuf[LINE_MAX];
897 char *pname;
899 /* skip over ancillary file names */
900 if (audit_cron_is_anc_name(name))
901 return;
903 if ((pw = getpwnam(name)) == NULL) {
904 msg("No such user as %s - cron entries not created", name);
905 return;
907 if (cwd != CRON) {
908 if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
909 CRONDIR, name) >= sizeof (namebuf)) {
910 msg("Too long path name %s - cron entries not created",
911 namebuf);
912 return;
914 pname = namebuf;
915 } else {
916 pname = name;
919 * a warning message is given by the crontab command so there is
920 * no need to give one here...... use this code if you only want
921 * users with a login shell of /usr/bin/sh to use cron
923 #ifdef BOURNESHELLONLY
924 if ((strcmp(pw->pw_shell, "") != 0) &&
925 (strcmp(pw->pw_shell, SHELL) != 0)) {
926 mail(name, BADSHELL, ERR_CANTEXECCRON);
927 cron_unlink(pname);
928 return;
930 #endif
931 if (stat(pname, &buf)) {
932 mail(name, BADSTAT, ERR_UNIXERR);
933 cron_unlink(pname);
934 return;
936 if (!S_ISREG(buf.st_mode)) {
937 mail(name, BADTYPE, ERR_CRONTABENT);
938 return;
940 if ((u = find_usr(name)) == NULL) {
941 #ifdef DEBUG
942 (void) fprintf(stderr, "new user (%s) with a crontab\n", name);
943 #endif
944 u = create_ulist(name, CRONEVENT);
945 u->home = xmalloc(strlen(pw->pw_dir) + 1);
946 (void) strcpy(u->home, pw->pw_dir);
947 u->uid = pw->pw_uid;
948 u->gid = pw->pw_gid;
949 readcron(u, reftime);
950 } else {
951 u->uid = pw->pw_uid;
952 u->gid = pw->pw_gid;
953 if (u->home != NULL) {
954 if (strcmp(u->home, pw->pw_dir) != 0) {
955 free(u->home);
956 u->home = xmalloc(strlen(pw->pw_dir) + 1);
957 (void) strcpy(u->home, pw->pw_dir);
959 } else {
960 u->home = xmalloc(strlen(pw->pw_dir) + 1);
961 (void) strcpy(u->home, pw->pw_dir);
963 u->ctexists = TRUE;
964 if (u->ctid == 0) {
965 #ifdef DEBUG
966 (void) fprintf(stderr, "%s now has a crontab\n",
967 u->name);
968 #endif
969 /* user didnt have a crontab last time */
970 u->ctid = ecid++;
971 u->ctevents = NULL;
972 readcron(u, reftime);
973 return;
975 #ifdef DEBUG
976 (void) fprintf(stderr, "%s has revised his crontab\n", u->name);
977 #endif
978 rm_ctevents(u);
979 el_remove(u->ctid, 0);
980 readcron(u, reftime);
984 /* ARGSUSED */
985 static void
986 mod_atjob(char *name, time_t reftime)
988 char *ptr;
989 time_t tim;
990 struct passwd *pw;
991 struct stat buf;
992 struct usr *u;
993 char namebuf[PATH_MAX];
994 char *pname;
995 int jobtype;
997 ptr = name;
998 if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
999 return;
1000 ptr++;
1001 if (!isalpha(*ptr))
1002 return;
1003 jobtype = *ptr - 'a';
1005 /* check for audit ancillary file */
1006 if (audit_cron_is_anc_name(name))
1007 return;
1009 if (cwd != AT) {
1010 if (snprintf(namebuf, sizeof (namebuf), "%s/%s", ATDIR, name)
1011 >= sizeof (namebuf)) {
1012 return;
1014 pname = namebuf;
1015 } else {
1016 pname = name;
1018 if (stat(pname, &buf) || jobtype >= NQUEUE) {
1019 cron_unlink(pname);
1020 return;
1022 if (!(buf.st_mode & ISUID) || !S_ISREG(buf.st_mode)) {
1023 cron_unlink(pname);
1024 return;
1026 if ((pw = getpwuid(buf.st_uid)) == NULL) {
1027 cron_unlink(pname);
1028 return;
1031 * a warning message is given by the at command so there is no
1032 * need to give one here......use this code if you only want
1033 * users with a login shell of /usr/bin/sh to use cron
1035 #ifdef BOURNESHELLONLY
1036 if ((strcmp(pw->pw_shell, "") != 0) &&
1037 (strcmp(pw->pw_shell, SHELL) != 0)) {
1038 mail(pw->pw_name, BADSHELL, ERR_CANTEXECAT);
1039 cron_unlink(pname);
1040 return;
1042 #endif
1043 if ((u = find_usr(pw->pw_name)) == NULL) {
1044 #ifdef DEBUG
1045 (void) fprintf(stderr, "new user (%s) with an at job = %s\n",
1046 pw->pw_name, name);
1047 #endif
1048 u = create_ulist(pw->pw_name, ATEVENT);
1049 u->home = xstrdup(pw->pw_dir);
1050 u->uid = pw->pw_uid;
1051 u->gid = pw->pw_gid;
1052 add_atevent(u, name, tim, jobtype);
1053 } else {
1054 u->uid = pw->pw_uid;
1055 u->gid = pw->pw_gid;
1056 free(u->home);
1057 u->home = xstrdup(pw->pw_dir);
1058 update_atevent(u, name, tim, jobtype);
1062 static void
1063 add_atevent(struct usr *u, char *job, time_t tim, int jobtype)
1065 struct event *e;
1067 e = xmalloc(sizeof (struct event));
1068 e->etype = jobtype;
1069 e->cmd = xmalloc(strlen(job) + 1);
1070 (void) strcpy(e->cmd, job);
1071 e->u = u;
1072 e->link = u->atevents;
1073 u->atevents = e;
1074 e->of.at.exists = TRUE;
1075 e->of.at.eventid = ecid++;
1076 if (tim < init_time) /* old job */
1077 e->time = init_time;
1078 else
1079 e->time = tim;
1080 #ifdef DEBUG
1081 (void) fprintf(stderr, "add_atevent: user=%s, job=%s, time=%ld\n",
1082 u->name, e->cmd, e->time);
1083 #endif
1084 if (el_add(e, e->time, e->of.at.eventid) < 0) {
1085 ignore_msg("add_atevent", "at", e);
1089 void
1090 update_atevent(struct usr *u, char *name, time_t tim, int jobtype)
1092 struct event *e;
1094 e = u->atevents;
1095 while (e != NULL) {
1096 if (strcmp(e->cmd, name) == 0) {
1097 e->of.at.exists = TRUE;
1098 break;
1099 } else {
1100 e = e->link;
1103 if (e == NULL) {
1104 #ifdef DEBUG
1105 (void) fprintf(stderr, "%s has a new at job = %s\n",
1106 u->name, name);
1107 #endif
1108 add_atevent(u, name, tim, jobtype);
1112 static char line[CTLINESIZE]; /* holds a line from a crontab file */
1113 static int cursor; /* cursor for the above line */
1115 static void
1116 readcron(struct usr *u, time_t reftime)
1119 * readcron reads in a crontab file for a user (u). The list of
1120 * events for user u is built, and u->events is made to point to
1121 * this list. Each event is also entered into the main event
1122 * list.
1124 FILE *cf; /* cf will be a user's crontab file */
1125 struct event *e;
1126 int start;
1127 unsigned int i;
1128 char namebuf[PATH_MAX];
1129 char *pname;
1130 struct shared *tz = NULL;
1131 struct shared *home = NULL;
1132 struct shared *shell = NULL;
1133 int lineno = 0;
1135 /* read the crontab file */
1136 cte_init(); /* Init error handling */
1137 if (cwd != CRON) {
1138 if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
1139 CRONDIR, u->name) >= sizeof (namebuf)) {
1140 return;
1142 pname = namebuf;
1143 } else {
1144 pname = u->name;
1146 if ((cf = fopen(pname, "r")) == NULL) {
1147 mail(u->name, NOREAD, ERR_UNIXERR);
1148 return;
1150 while (fgets(line, CTLINESIZE, cf) != NULL) {
1151 char *tmp;
1152 /* process a line of a crontab file */
1153 lineno++;
1154 if (cte_istoomany())
1155 break;
1156 cursor = 0;
1157 while (line[cursor] == ' ' || line[cursor] == '\t')
1158 cursor++;
1159 if (line[cursor] == '#' || line[cursor] == '\n')
1160 continue;
1162 if (strncmp(&line[cursor], ENV_TZ,
1163 strlen(ENV_TZ)) == 0) {
1164 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1165 *tmp = '\0';
1168 if (tz == NULL || strcmp(&line[cursor], get_obj(tz))) {
1169 rel_shared(tz);
1170 tz = create_shared_str(&line[cursor]);
1172 continue;
1175 if (strncmp(&line[cursor], ENV_HOME,
1176 strlen(ENV_HOME)) == 0) {
1177 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1178 *tmp = '\0';
1180 if (home == NULL ||
1181 strcmp(&line[cursor], get_obj(home))) {
1182 rel_shared(home);
1183 home = create_shared_str(
1184 &line[cursor + strlen(ENV_HOME)]);
1186 continue;
1189 if (strncmp(&line[cursor], ENV_SHELL,
1190 strlen(ENV_SHELL)) == 0) {
1191 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1192 *tmp = '\0';
1194 if (shell == NULL ||
1195 strcmp(&line[cursor], get_obj(shell))) {
1196 rel_shared(shell);
1197 shell = create_shared_str(&line[cursor]);
1199 continue;
1202 e = xmalloc(sizeof (struct event));
1203 e->etype = CRONEVENT;
1204 if (!(((e->of.ct.minute = next_field(0, 59)) != NULL) &&
1205 ((e->of.ct.hour = next_field(0, 23)) != NULL) &&
1206 ((e->of.ct.daymon = next_field(1, 31)) != NULL) &&
1207 ((e->of.ct.month = next_field(1, 12)) != NULL) &&
1208 ((e->of.ct.dayweek = next_field(0, 6)) != NULL))) {
1209 free(e);
1210 cte_add(lineno, line);
1211 continue;
1213 while (line[cursor] == ' ' || line[cursor] == '\t')
1214 cursor++;
1215 if (line[cursor] == '\n' || line[cursor] == '\0')
1216 continue;
1217 /* get the command to execute */
1218 start = cursor;
1219 again:
1220 while ((line[cursor] != '%') &&
1221 (line[cursor] != '\n') &&
1222 (line[cursor] != '\0') &&
1223 (line[cursor] != '\\'))
1224 cursor++;
1225 if (line[cursor] == '\\') {
1226 cursor += 2;
1227 goto again;
1229 e->cmd = xmalloc(cursor-start + 1);
1230 (void) strncpy(e->cmd, line + start, cursor-start);
1231 e->cmd[cursor-start] = '\0';
1232 /* see if there is any standard input */
1233 if (line[cursor] == '%') {
1234 e->of.ct.input = xmalloc(strlen(line)-cursor + 1);
1235 (void) strcpy(e->of.ct.input, line + cursor + 1);
1236 for (i = 0; i < strlen(e->of.ct.input); i++) {
1237 if (e->of.ct.input[i] == '%')
1238 e->of.ct.input[i] = '\n';
1240 } else {
1241 e->of.ct.input = NULL;
1243 /* set the timezone of this entry */
1244 e->of.ct.tz = dup_shared(tz);
1245 /* set the shell of this entry */
1246 e->of.ct.shell = dup_shared(shell);
1247 /* set the home of this entry */
1248 e->of.ct.home = dup_shared(home);
1249 /* have the event point to it's owner */
1250 e->u = u;
1251 /* insert this event at the front of this user's event list */
1252 e->link = u->ctevents;
1253 u->ctevents = e;
1254 /* set the time for the first occurance of this event */
1255 e->time = next_time(e, reftime);
1256 /* finally, add this event to the main event list */
1257 switch (el_add(e, e->time, u->ctid)) {
1258 case -1:
1259 ignore_msg("readcron", "cron", e);
1260 break;
1261 case -2: /* event time lower than init time */
1262 reset_needed = 1;
1263 break;
1265 cte_valid();
1266 #ifdef DEBUG
1267 cftime(timebuf, "%+", &e->time);
1268 (void) fprintf(stderr, "inserting cron event %s at %ld (%s)\n",
1269 e->cmd, e->time, timebuf);
1270 #endif
1272 cte_sendmail(u->name); /* mail errors if any to user */
1273 (void) fclose(cf);
1274 rel_shared(tz);
1275 rel_shared(shell);
1276 rel_shared(home);
1280 * Below are the functions for handling of errors in crontabs. Concept is to
1281 * collect faulty lines and send one email at the end of the crontab
1282 * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
1283 * of crontab is aborted. Otherwise reading of crontab is continued to the end
1284 * of the file but no further error logging appears.
1286 static void
1287 cte_init()
1289 if (cte_text == NULL)
1290 cte_text = xmalloc(MAILBUFLEN);
1291 (void) strlcpy(cte_text, cte_intro, MAILBUFLEN);
1292 cte_lp = cte_text + sizeof (cte_intro) - 1;
1293 cte_free = MAILBINITFREE;
1294 cte_nvalid = 0;
1297 static void
1298 cte_add(int lineno, char *ctline)
1300 int len;
1301 char *p;
1303 if (cte_free >= LINELIMIT) {
1304 (void) sprintf(cte_lp, "%4d: ", lineno);
1305 (void) strlcat(cte_lp, ctline, LINELIMIT - 1);
1306 len = strlen(cte_lp);
1307 if (cte_lp[len - 1] != '\n') {
1308 cte_lp[len++] = '\n';
1309 cte_lp[len] = '\0';
1311 for (p = cte_lp; *p; p++) {
1312 if (isprint(*p) || *p == '\n' || *p == '\t')
1313 continue;
1314 *p = '.';
1316 cte_lp += len;
1317 cte_free -= len;
1318 if (cte_free < LINELIMIT) {
1319 size_t buflen = MAILBUFLEN - (cte_lp - cte_text);
1320 (void) strlcpy(cte_lp, cte_trail1, buflen);
1321 if (cte_nvalid == 0)
1322 (void) strlcat(cte_lp, cte_trail2, buflen);
1327 static void
1328 cte_valid()
1330 cte_nvalid++;
1333 static int
1334 cte_istoomany()
1337 * Return TRUE only if all lines are faulty. So evaluation of
1338 * a crontab is not aborted if at least one valid line was found.
1340 return (cte_nvalid == 0 && cte_free < LINELIMIT);
1343 static void
1344 cte_sendmail(char *username)
1346 if (cte_free < MAILBINITFREE)
1347 mail(username, cte_text, ERR_CRONTABENT);
1351 * Send mail with error message to a user
1353 static void
1354 mail(char *usrname, char *mesg, int format)
1356 /* mail mails a user a message. */
1357 FILE *pipe;
1358 char *temp;
1359 struct passwd *ruser_ids;
1360 pid_t fork_val;
1361 int saveerrno = errno;
1362 struct utsname name;
1364 #ifdef TESTING
1365 return;
1366 #endif
1367 (void) uname(&name);
1368 if ((fork_val = fork()) == (pid_t)-1) {
1369 msg("cron cannot fork\n");
1370 return;
1372 if (fork_val == 0) {
1373 child_sigreset();
1374 contract_clear_template();
1375 if ((ruser_ids = getpwnam(usrname)) == NULL)
1376 exit(0);
1377 (void) setuid(ruser_ids->pw_uid);
1378 temp = xmalloc(strlen(MAIL) + strlen(usrname) + 2);
1379 (void) sprintf(temp, "%s %s", MAIL, usrname);
1380 pipe = popen(temp, "w");
1381 if (pipe != NULL) {
1382 (void) fprintf(pipe, "To: %s\n", usrname);
1383 switch (format) {
1384 case ERR_CRONTABENT:
1385 (void) fprintf(pipe, CRONTABERR);
1386 (void) fprintf(pipe, "Your \"crontab\" on %s\n",
1387 name.nodename);
1388 (void) fprintf(pipe, mesg);
1389 (void) fprintf(pipe,
1390 "\nEntries or crontab have been ignored\n");
1391 break;
1392 case ERR_UNIXERR:
1393 (void) fprintf(pipe, "Subject: %s\n\n", mesg);
1394 (void) fprintf(pipe,
1395 "The error on %s was \"%s\"\n",
1396 name.nodename, errmsg(saveerrno));
1397 break;
1399 case ERR_CANTEXECCRON:
1400 (void) fprintf(pipe,
1401 "Subject: Couldn't run your \"cron\" job\n\n");
1402 (void) fprintf(pipe,
1403 "Your \"cron\" job on %s ", name.nodename);
1404 (void) fprintf(pipe, "couldn't be run\n");
1405 (void) fprintf(pipe, "%s\n", mesg);
1406 (void) fprintf(pipe,
1407 "The error was \"%s\"\n", errmsg(saveerrno));
1408 break;
1410 case ERR_CANTEXECAT:
1411 (void) fprintf(pipe,
1412 "Subject: Couldn't run your \"at\" job\n\n");
1413 (void) fprintf(pipe, "Your \"at\" job on %s ",
1414 name.nodename);
1415 (void) fprintf(pipe, "couldn't be run\n");
1416 (void) fprintf(pipe, "%s\n", mesg);
1417 (void) fprintf(pipe,
1418 "The error was \"%s\"\n", errmsg(saveerrno));
1419 break;
1421 default:
1422 break;
1424 (void) pclose(pipe);
1426 free(temp);
1427 exit(0);
1430 contract_abandon_latest(fork_val);
1432 if (cron_pid == getpid()) {
1433 miscpid_insert(fork_val);
1437 static char *
1438 next_field(int lower, int upper)
1441 * next_field returns a pointer to a string which holds the next
1442 * field of a line of a crontab file.
1443 * if (numbers in this field are out of range (lower..upper),
1444 * or there is a syntax error) then
1445 * NULL is returned, and a mail message is sent to the
1446 * user telling them which line the error was in.
1449 char *s;
1450 int num, num2, start;
1452 while ((line[cursor] == ' ') || (line[cursor] == '\t'))
1453 cursor++;
1454 start = cursor;
1455 if (line[cursor] == '\0') {
1456 return (NULL);
1458 if (line[cursor] == '*') {
1459 cursor++;
1460 if ((line[cursor] != ' ') && (line[cursor] != '\t'))
1461 return (NULL);
1462 s = xmalloc(2);
1463 (void) strcpy(s, "*");
1464 return (s);
1466 for (;;) {
1467 if (!isdigit(line[cursor]))
1468 return (NULL);
1469 num = 0;
1470 do {
1471 num = num*10 + (line[cursor]-'0');
1472 } while (isdigit(line[++cursor]));
1473 if ((num < lower) || (num > upper))
1474 return (NULL);
1475 if (line[cursor] == '-') {
1476 if (!isdigit(line[++cursor]))
1477 return (NULL);
1478 num2 = 0;
1479 do {
1480 num2 = num2*10 + (line[cursor]-'0');
1481 } while (isdigit(line[++cursor]));
1482 if ((num2 < lower) || (num2 > upper))
1483 return (NULL);
1485 if ((line[cursor] == ' ') || (line[cursor] == '\t'))
1486 break;
1487 if (line[cursor] == '\0')
1488 return (NULL);
1489 if (line[cursor++] != ',')
1490 return (NULL);
1492 s = xmalloc(cursor-start + 1);
1493 (void) strncpy(s, line + start, cursor-start);
1494 s[cursor-start] = '\0';
1495 return (s);
1498 #define tm_cmp(t1, t2) (\
1499 (t1)->tm_year == (t2)->tm_year && \
1500 (t1)->tm_mon == (t2)->tm_mon && \
1501 (t1)->tm_mday == (t2)->tm_mday && \
1502 (t1)->tm_hour == (t2)->tm_hour && \
1503 (t1)->tm_min == (t2)->tm_min)
1505 #define tm_setup(tp, yr, mon, dy, hr, min, dst) \
1506 (tp)->tm_year = yr; \
1507 (tp)->tm_mon = mon; \
1508 (tp)->tm_mday = dy; \
1509 (tp)->tm_hour = hr; \
1510 (tp)->tm_min = min; \
1511 (tp)->tm_isdst = dst; \
1512 (tp)->tm_sec = 0; \
1513 (tp)->tm_wday = 0; \
1514 (tp)->tm_yday = 0;
1517 * modification for bugid 1104537. the second argument to next_time is
1518 * now the value of time(2) to be used. if this is 0, then use the
1519 * current time. otherwise, the second argument is the time from which to
1520 * calculate things. this is useful to correct situations where you've
1521 * gone backwards in time (I.e. the system's internal clock is correcting
1522 * itself backwards).
1527 static time_t
1528 tz_next_time(struct event *e, time_t tflag)
1531 * returns the integer time for the next occurance of event e.
1532 * the following fields have ranges as indicated:
1533 * PRGM | min hour day of month mon day of week
1534 * ------|-------------------------------------------------------
1535 * cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday)
1536 * time | 0-59 0-23 1-31 0-11 0-6 (0=sunday)
1537 * NOTE: this routine is hard to understand.
1540 struct tm *tm, ref_tm, tmp, tmp1, tmp2;
1541 int tm_mon, tm_mday, tm_wday, wday, m, min, h, hr, carry, day, days;
1542 int d1, day1, carry1, d2, day2, carry2, daysahead, mon, yr, db, wd;
1543 int today;
1544 time_t t, ref_t, t1, t2, zone_start;
1545 int fallback;
1546 extern int days_btwn(int, int, int, int, int, int);
1548 if (tflag == 0) {
1549 t = time(NULL); /* original way of doing things */
1550 } else {
1551 t = tflag;
1554 tm = &ref_tm; /* use a local variable and call localtime_r() */
1555 ref_t = t; /* keep a copy of the reference time */
1557 recalc:
1558 fallback = 0;
1560 (void) localtime_r(&t, tm);
1562 if (daylight) {
1563 tmp = *tm;
1564 tmp.tm_isdst = (tm->tm_isdst > 0 ? 0 : 1);
1565 t1 = xmktime(&tmp);
1567 * see if we will have timezone switch over, and clock will
1568 * fall back. zone_start will hold the time when it happens
1569 * (ie time of PST -> PDT switch over).
1571 if (tm->tm_isdst != tmp.tm_isdst &&
1572 (t1 - t) == (timezone - altzone) &&
1573 tm_cmp(tm, &tmp)) {
1574 zone_start = get_switching_time(tmp.tm_isdst, t);
1575 fallback = 1;
1579 tm_mon = next_ge(tm->tm_mon + 1, e->of.ct.month) - 1; /* 0-11 */
1580 tm_mday = next_ge(tm->tm_mday, e->of.ct.daymon); /* 1-31 */
1581 tm_wday = next_ge(tm->tm_wday, e->of.ct.dayweek); /* 0-6 */
1582 today = TRUE;
1583 if ((strcmp(e->of.ct.daymon, "*") == 0 && tm->tm_wday != tm_wday) ||
1584 (strcmp(e->of.ct.dayweek, "*") == 0 && tm->tm_mday != tm_mday) ||
1585 (tm->tm_mday != tm_mday && tm->tm_wday != tm_wday) ||
1586 (tm->tm_mon != tm_mon)) {
1587 today = FALSE;
1589 m = tm->tm_min + (t == ref_t ? 1 : 0);
1590 if ((tm->tm_hour + 1) <= next_ge(tm->tm_hour, e->of.ct.hour)) {
1591 m = 0;
1593 min = next_ge(m%60, e->of.ct.minute);
1594 carry = (min < m) ? 1 : 0;
1595 h = tm->tm_hour + carry;
1596 hr = next_ge(h%24, e->of.ct.hour);
1597 carry = (hr < h) ? 1 : 0;
1599 if (carry == 0 && today) {
1600 /* this event must occur today */
1601 tm_setup(&tmp, tm->tm_year, tm->tm_mon, tm->tm_mday,
1602 hr, min, tm->tm_isdst);
1603 tmp1 = tmp;
1604 if ((t1 = xmktime(&tmp1)) == (time_t)-1) {
1605 return (0);
1607 if (daylight && tmp.tm_isdst != tmp1.tm_isdst) {
1608 /* In case we are falling back */
1609 if (fallback) {
1610 /* we may need to run the job once more. */
1611 t = zone_start;
1612 goto recalc;
1616 * In case we are not in falling back period,
1617 * calculate the time assuming the DST. If the
1618 * date/time is not altered by mktime, it is the
1619 * time to execute the job.
1621 tmp2 = tmp;
1622 tmp2.tm_isdst = tmp1.tm_isdst;
1623 if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
1624 return (0);
1626 if (tmp1.tm_isdst == tmp2.tm_isdst &&
1627 tm_cmp(&tmp, &tmp2)) {
1629 * We got a valid time.
1631 return (t1);
1632 } else {
1634 * If the date does not match even if
1635 * we assume the alternate timezone, then
1636 * it must be the invalid time. eg
1637 * 2am while switching 1:59am to 3am.
1638 * t1 should point the time before the
1639 * switching over as we've calculate the
1640 * time with assuming alternate zone.
1642 if (tmp1.tm_isdst != tmp2.tm_isdst) {
1643 t = get_switching_time(tmp1.tm_isdst,
1644 t1);
1645 } else {
1646 /* does this really happen? */
1647 t = get_switching_time(tmp1.tm_isdst,
1648 t1 - abs(timezone - altzone));
1650 if (t == (time_t)-1) {
1651 return (0);
1654 goto recalc;
1656 if (tm_cmp(&tmp, &tmp1)) {
1657 /* got valid time */
1658 return (t1);
1659 } else {
1661 * This should never happen, but just in
1662 * case, we fall back to the old code.
1664 if (tm->tm_min > min) {
1665 t += (time_t)(hr-tm->tm_hour-1) * HOUR +
1666 (time_t)(60-tm->tm_min + min) * MINUTE;
1667 } else {
1668 t += (time_t)(hr-tm->tm_hour) * HOUR +
1669 (time_t)(min-tm->tm_min) * MINUTE;
1671 t1 = t;
1672 t -= (time_t)tm->tm_sec;
1673 (void) localtime_r(&t, &tmp);
1674 if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
1675 t -= (timezone - altzone);
1676 return ((t <= ref_t) ? t1 : t);
1681 * Job won't run today, however if we have a switch over within
1682 * one hour and we will have one hour time drifting back in this
1683 * period, we may need to run the job one more time if the job was
1684 * set to run on this hour of clock.
1686 if (fallback) {
1687 t = zone_start;
1688 goto recalc;
1691 min = next_ge(0, e->of.ct.minute);
1692 hr = next_ge(0, e->of.ct.hour);
1695 * calculate the date of the next occurance of this event, which
1696 * will be on a different day than the current
1699 /* check monthly day specification */
1700 d1 = tm->tm_mday + 1;
1701 day1 = next_ge((d1-1)%days_in_mon(tm->tm_mon, tm->tm_year) + 1,
1702 e->of.ct.daymon);
1703 carry1 = (day1 < d1) ? 1 : 0;
1705 /* check weekly day specification */
1706 d2 = tm->tm_wday + 1;
1707 wday = next_ge(d2%7, e->of.ct.dayweek);
1708 if (wday < d2)
1709 daysahead = 7 - d2 + wday;
1710 else
1711 daysahead = wday - d2;
1712 day2 = (d1 + daysahead-1)%days_in_mon(tm->tm_mon, tm->tm_year) + 1;
1713 carry2 = (day2 < d1) ? 1 : 0;
1716 * based on their respective specifications, day1, and day2 give
1717 * the day of the month for the next occurance of this event.
1719 if ((strcmp(e->of.ct.daymon, "*") == 0) &&
1720 (strcmp(e->of.ct.dayweek, "*") != 0)) {
1721 day1 = day2;
1722 carry1 = carry2;
1724 if ((strcmp(e->of.ct.daymon, "*") != 0) &&
1725 (strcmp(e->of.ct.dayweek, "*") == 0)) {
1726 day2 = day1;
1727 carry2 = carry1;
1730 yr = tm->tm_year;
1731 if ((carry1 && carry2) || (tm->tm_mon != tm_mon)) {
1732 /* event does not occur in this month */
1733 m = tm->tm_mon + 1;
1734 mon = next_ge(m%12 + 1, e->of.ct.month) - 1; /* 0..11 */
1735 carry = (mon < m) ? 1 : 0;
1736 yr += carry;
1737 /* recompute day1 and day2 */
1738 day1 = next_ge(1, e->of.ct.daymon);
1739 db = days_btwn(tm->tm_mon, tm->tm_mday, tm->tm_year, mon,
1740 1, yr) + 1;
1741 wd = (tm->tm_wday + db)%7;
1742 /* wd is the day of the week of the first of month mon */
1743 wday = next_ge(wd, e->of.ct.dayweek);
1744 if (wday < wd)
1745 day2 = 1 + 7 - wd + wday;
1746 else
1747 day2 = 1 + wday - wd;
1748 if ((strcmp(e->of.ct.daymon, "*") != 0) &&
1749 (strcmp(e->of.ct.dayweek, "*") == 0))
1750 day2 = day1;
1751 if ((strcmp(e->of.ct.daymon, "*") == 0) &&
1752 (strcmp(e->of.ct.dayweek, "*") != 0))
1753 day1 = day2;
1754 day = (day1 < day2) ? day1 : day2;
1755 } else { /* event occurs in this month */
1756 mon = tm->tm_mon;
1757 if (!carry1 && !carry2)
1758 day = (day1 < day2) ? day1 : day2;
1759 else if (!carry1)
1760 day = day1;
1761 else
1762 day = day2;
1766 * now that we have the min, hr, day, mon, yr of the next event,
1767 * figure out what time that turns out to be.
1769 tm_setup(&tmp, yr, mon, day, hr, min, -1);
1770 tmp2 = tmp;
1771 if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
1772 return (0);
1774 if (tm_cmp(&tmp, &tmp2)) {
1776 * mktime returns clock for the current time zone. If the
1777 * target date was in fallback period, it needs to be adjusted
1778 * to the time comes first.
1779 * Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
1780 * mktime returns the time in PST, but 1:30am in PDT comes
1781 * first. So reverse the tm_isdst, and see if we have such
1782 * time/date.
1784 if (daylight) {
1785 int dst = tmp2.tm_isdst;
1787 tmp2 = tmp;
1788 tmp2.tm_isdst = (dst > 0 ? 0 : 1);
1789 if ((t2 = xmktime(&tmp2)) == (time_t)-1) {
1790 return (0);
1792 if (tm_cmp(&tmp, &tmp2)) {
1794 * same time/date found in the opposite zone.
1795 * check the clock to see which comes early.
1797 if (t2 > ref_t && t2 < t1) {
1798 t1 = t2;
1802 return (t1);
1803 } else {
1805 * mktime has set different time/date for the given date.
1806 * This means that the next job is scheduled to be run on the
1807 * invalid time. There are three possible invalid date/time.
1808 * 1. Non existing day of the month. such as April 31th.
1809 * 2. Feb 29th in the non-leap year.
1810 * 3. Time gap during the DST switch over.
1812 d1 = days_in_mon(mon, yr);
1813 if ((mon != 1 && day > d1) || (mon == 1 && day > 29)) {
1815 * see if we have got a specific date which
1816 * is invalid.
1818 if (strcmp(e->of.ct.dayweek, "*") == 0 &&
1819 mon == (next_ge((mon + 1)%12 + 1,
1820 e->of.ct.month) - 1) &&
1821 day <= next_ge(1, e->of.ct.daymon)) {
1822 /* job never run */
1823 return (0);
1826 * Since the day has gone invalid, we need to go to
1827 * next month, and recalcuate the first occurrence.
1828 * eg the cron tab such as:
1829 * 0 0 1,15,31 1,2,3,4,5 * /usr/bin....
1830 * 2/31 is invalid, so the next job is 3/1.
1832 tmp2 = tmp;
1833 tmp2.tm_min = 0;
1834 tmp2.tm_hour = 0;
1835 tmp2.tm_mday = 1; /* 1st day of the month */
1836 if (mon == 11) {
1837 tmp2.tm_mon = 0;
1838 tmp2.tm_year = yr + 1;
1839 } else {
1840 tmp2.tm_mon = mon + 1;
1842 if ((t = xmktime(&tmp2)) == (time_t)-1) {
1843 return (0);
1845 } else if (mon == 1 && day > d1) {
1847 * ie 29th in the non-leap year. Forwarding the
1848 * clock to Feb 29th 00:00 (March 1st), and recalculate
1849 * the next time.
1851 tmp2 = tmp;
1852 tmp2.tm_min = 0;
1853 tmp2.tm_hour = 0;
1854 if ((t = xmktime(&tmp2)) == (time_t)-1) {
1855 return (0);
1857 } else if (daylight) {
1859 * Non existing time, eg 2am PST during summer time
1860 * switch.
1861 * We need to get the correct isdst which we are
1862 * swithing to, by adding time difference to make sure
1863 * that t2 is in the zone being switched.
1865 t2 = t1;
1866 t2 += abs(timezone - altzone);
1867 (void) localtime_r(&t2, &tmp2);
1868 zone_start = get_switching_time(tmp2.tm_isdst,
1869 t1 - abs(timezone - altzone));
1870 if (zone_start == (time_t)-1) {
1871 return (0);
1873 t = zone_start;
1874 } else {
1876 * This should never happen, but fall back to the
1877 * old code.
1879 days = days_btwn(tm->tm_mon,
1880 tm->tm_mday, tm->tm_year, mon, day, yr);
1881 t += (time_t)(23-tm->tm_hour)*HOUR
1882 + (time_t)(60-tm->tm_min)*MINUTE
1883 + (time_t)hr*HOUR + (time_t)min*MINUTE
1884 + (time_t)days*DAY;
1885 t1 = t;
1886 t -= (time_t)tm->tm_sec;
1887 (void) localtime_r(&t, &tmp);
1888 if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
1889 t -= (timezone - altzone);
1890 return (t <= ref_t ? t1 : t);
1892 goto recalc;
1894 /*NOTREACHED*/
1897 static time_t
1898 next_time(struct event *e, time_t tflag)
1900 if (e->of.ct.tz != NULL) {
1901 time_t ret;
1903 (void) putenv((char *)get_obj(e->of.ct.tz));
1904 tzset();
1905 ret = tz_next_time(e, tflag);
1906 (void) putenv(tzone);
1907 tzset();
1908 return (ret);
1909 } else {
1910 return (tz_next_time(e, tflag));
1915 * This returns TOD in time_t that zone switch will happen, and this
1916 * will be called when clock fallback is about to happen.
1917 * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
1918 * will fall back to 1:00 PDT. So this function will be called only
1919 * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
1920 * First goes through the common time differences to see if zone
1921 * switch happens at those minutes later. If not, check every minutes
1922 * until 6 hours ahead see if it happens(We might have 45minutes
1923 * fallback).
1925 static time_t
1926 get_switching_time(int to_dst, time_t t_ref)
1928 time_t t, t1;
1929 struct tm tmp, tmp1;
1930 int hints[] = { 60, 120, 30, 90, 0}; /* minutes */
1931 int i;
1933 (void) localtime_r(&t_ref, &tmp);
1934 tmp1 = tmp;
1935 tmp1.tm_sec = 0;
1936 tmp1.tm_min = 0;
1937 if ((t = xmktime(&tmp1)) == (time_t)-1)
1938 return ((time_t)-1);
1940 /* fast path */
1941 for (i = 0; hints[i] != 0; i++) {
1942 t1 = t + hints[i] * 60;
1943 (void) localtime_r(&t1, &tmp1);
1944 if (tmp1.tm_isdst == to_dst) {
1945 t1--;
1946 (void) localtime_r(&t1, &tmp1);
1947 if (tmp1.tm_isdst != to_dst) {
1948 return (t1 + 1);
1953 /* ugly, but don't know other than this. */
1954 tmp1 = tmp;
1955 tmp1.tm_sec = 0;
1956 if ((t = xmktime(&tmp1)) == (time_t)-1)
1957 return ((time_t)-1);
1958 while (t < (t_ref + 6*60*60)) { /* 6 hours should be enough */
1959 t += 60; /* at least one minute, I assume */
1960 (void) localtime_r(&t, &tmp);
1961 if (tmp.tm_isdst == to_dst)
1962 return (t);
1964 return ((time_t)-1);
1967 static time_t
1968 xmktime(struct tm *tmp)
1970 time_t ret;
1972 if ((ret = mktime(tmp)) == (time_t)-1) {
1973 if (errno == EOVERFLOW) {
1974 return ((time_t)-1);
1976 crabort("internal error: mktime failed",
1977 REMOVE_FIFO|CONSOLE_MSG);
1979 return (ret);
1982 #define DUMMY 100
1984 static int
1985 next_ge(int current, char *list)
1988 * list is a character field as in a crontab file;
1989 * for example: "40, 20, 50-10"
1990 * next_ge returns the next number in the list that is
1991 * greater than or equal to current. if no numbers of list
1992 * are >= current, the smallest element of list is returned.
1993 * NOTE: current must be in the appropriate range.
1996 char *ptr;
1997 int n, n2, min, min_gt;
1999 if (strcmp(list, "*") == 0)
2000 return (current);
2001 ptr = list;
2002 min = DUMMY;
2003 min_gt = DUMMY;
2004 for (;;) {
2005 if ((n = (int)num(&ptr)) == current)
2006 return (current);
2007 if (n < min)
2008 min = n;
2009 if ((n > current) && (n < min_gt))
2010 min_gt = n;
2011 if (*ptr == '-') {
2012 ptr++;
2013 if ((n2 = (int)num(&ptr)) > n) {
2014 if ((current > n) && (current <= n2))
2015 return (current);
2016 } else { /* range that wraps around */
2017 if (current > n)
2018 return (current);
2019 if (current <= n2)
2020 return (current);
2023 if (*ptr == '\0')
2024 break;
2025 ptr += 1;
2027 if (min_gt != DUMMY)
2028 return (min_gt);
2029 else
2030 return (min);
2033 static void
2034 free_if_unused(struct usr *u)
2036 struct usr *cur, *prev;
2038 * To make sure a usr structure is idle we must check that
2039 * there are no at jobs queued for the user; the user does
2040 * not have a crontab, and also that there are no running at
2041 * or cron jobs (since the runinfo structure also has a
2042 * pointer to the usr structure).
2044 if (!u->ctexists && u->atevents == NULL &&
2045 u->cruncnt == 0 && u->aruncnt == 0) {
2046 #ifdef DEBUG
2047 (void) fprintf(stderr, "%s removed from usr list\n", u->name);
2048 #endif
2049 for (cur = uhead, prev = NULL;
2050 cur != u;
2051 prev = cur, cur = cur->nextusr) {
2052 if (cur == NULL) {
2053 return;
2057 if (prev == NULL)
2058 uhead = u->nextusr;
2059 else
2060 prev->nextusr = u->nextusr;
2061 free(u->name);
2062 free(u->home);
2063 free(u);
2067 static void
2068 del_atjob(char *name, char *usrname)
2071 struct event *e, *eprev;
2072 struct usr *u;
2074 if ((u = find_usr(usrname)) == NULL)
2075 return;
2076 e = u->atevents;
2077 eprev = NULL;
2078 while (e != NULL) {
2079 if (strcmp(name, e->cmd) == 0) {
2080 if (next_event == e)
2081 next_event = NULL;
2082 if (eprev == NULL)
2083 u->atevents = e->link;
2084 else
2085 eprev->link = e->link;
2086 el_remove(e->of.at.eventid, 1);
2087 free(e->cmd);
2088 free(e);
2089 break;
2090 } else {
2091 eprev = e;
2092 e = e->link;
2096 free_if_unused(u);
2099 static void
2100 del_ctab(char *name)
2103 struct usr *u;
2105 if ((u = find_usr(name)) == NULL)
2106 return;
2107 rm_ctevents(u);
2108 el_remove(u->ctid, 0);
2109 u->ctid = 0;
2110 u->ctexists = 0;
2112 free_if_unused(u);
2115 static void
2116 rm_ctevents(struct usr *u)
2118 struct event *e2, *e3;
2121 * see if the next event (to be run by cron) is a cronevent
2122 * owned by this user.
2125 if ((next_event != NULL) &&
2126 (next_event->etype == CRONEVENT) &&
2127 (next_event->u == u)) {
2128 next_event = NULL;
2130 e2 = u->ctevents;
2131 while (e2 != NULL) {
2132 free(e2->cmd);
2133 rel_shared(e2->of.ct.tz);
2134 rel_shared(e2->of.ct.shell);
2135 rel_shared(e2->of.ct.home);
2136 free(e2->of.ct.minute);
2137 free(e2->of.ct.hour);
2138 free(e2->of.ct.daymon);
2139 free(e2->of.ct.month);
2140 free(e2->of.ct.dayweek);
2141 free(e2->of.ct.input);
2142 e3 = e2->link;
2143 free(e2);
2144 e2 = e3;
2146 u->ctevents = NULL;
2150 static struct usr *
2151 find_usr(char *uname)
2153 struct usr *u;
2155 u = uhead;
2156 while (u != NULL) {
2157 if (strcmp(u->name, uname) == 0)
2158 return (u);
2159 u = u->nextusr;
2161 return (NULL);
2165 * Execute cron command or at/batch job.
2166 * If ever a premature return is added to this function pay attention to
2167 * free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
2169 static int
2170 ex(struct event *e)
2172 int r;
2173 int fd;
2174 pid_t rfork;
2175 FILE *atcmdfp;
2176 char mailvar[4];
2177 char *at_cmdfile = NULL;
2178 struct stat buf;
2179 struct queue *qp;
2180 struct runinfo *rp;
2181 struct project proj, *pproj = NULL;
2182 union {
2183 struct {
2184 char buf[PROJECT_BUFSZ];
2185 char buf2[PROJECT_BUFSZ];
2186 } p;
2187 char error[CANT_STR_LEN + PATH_MAX];
2188 } bufs;
2189 char *tmpfile;
2190 FILE *fptr;
2191 time_t dhltime;
2192 projid_t projid;
2193 int projflag = 0;
2194 char *home;
2195 char *sh;
2197 qp = &qt[e->etype]; /* set pointer to queue defs */
2198 if (qp->nrun >= qp->njob) {
2199 msg("%c queue max run limit reached", e->etype + 'a');
2200 resched(qp->nwait);
2201 return (0);
2204 rp = rinfo_get(0); /* allocating a new runinfo struct */
2207 * the tempnam() function uses malloc(3C) to allocate space for the
2208 * constructed file name, and returns a pointer to this area, which
2209 * is assigned to rp->outfile. Here rp->outfile is not overwritten.
2212 rp->outfile = tempnam(TMPDIR, PFX);
2213 rp->jobtype = e->etype;
2214 if (e->etype == CRONEVENT) {
2215 rp->jobname = xmalloc(strlen(e->cmd) + 1);
2216 (void) strcpy(rp->jobname, e->cmd);
2217 /* "cron" jobs only produce mail if there's output */
2218 rp->mailwhendone = 0;
2219 } else {
2220 at_cmdfile = xmalloc(strlen(ATDIR) + strlen(e->cmd) + 2);
2221 (void) sprintf(at_cmdfile, "%s/%s", ATDIR, e->cmd);
2222 if ((atcmdfp = fopen(at_cmdfile, "r")) == NULL) {
2223 if (errno == ENAMETOOLONG) {
2224 if (chdir(ATDIR) == 0)
2225 cron_unlink(e->cmd);
2226 } else {
2227 cron_unlink(at_cmdfile);
2229 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECAT);
2230 free(at_cmdfile);
2231 rinfo_free(rp);
2232 return (0);
2234 rp->jobname = xmalloc(strlen(at_cmdfile) + 1);
2235 (void) strcpy(rp->jobname, at_cmdfile);
2238 * Skip over the first two lines.
2240 (void) fscanf(atcmdfp, "%*[^\n]\n");
2241 (void) fscanf(atcmdfp, "%*[^\n]\n");
2242 if (fscanf(atcmdfp, ": notify by mail: %3s%*[^\n]\n",
2243 mailvar) == 1) {
2245 * Check to see if we should always send mail
2246 * to the owner.
2248 rp->mailwhendone = (strcmp(mailvar, "yes") == 0);
2249 } else {
2250 rp->mailwhendone = 0;
2253 if (fscanf(atcmdfp, "\n: project: %d\n", &projid) == 1) {
2254 projflag = 1;
2256 (void) fclose(atcmdfp);
2260 * we make sure that the system time
2261 * hasn't drifted backwards. if it has, el_add() is now
2262 * called, to make sure that the event queue is back in order,
2263 * and we set the delayed flag. cron will pick up the request
2264 * later on at the proper time.
2266 dhltime = time(NULL);
2267 if ((dhltime - e->time) < 0) {
2268 msg("clock time drifted backwards!\n");
2269 if (next_event->etype == CRONEVENT) {
2270 msg("correcting cron event\n");
2271 next_event->time = next_time(next_event, dhltime);
2272 switch (el_add(next_event, next_event->time,
2273 (next_event->u)->ctid)) {
2274 case -1:
2275 ignore_msg("ex", "cron", next_event);
2276 break;
2277 case -2: /* event time lower than init time */
2278 reset_needed = 1;
2279 break;
2281 } else { /* etype == ATEVENT */
2282 msg("correcting batch event\n");
2283 if (el_add(next_event, next_event->time,
2284 next_event->of.at.eventid) < 0) {
2285 ignore_msg("ex", "at", next_event);
2288 delayed++;
2289 t_old = time(NULL);
2290 free(at_cmdfile);
2291 rinfo_free(rp);
2292 return (0);
2295 if ((rfork = fork()) == (pid_t)-1) {
2296 reap_child();
2297 if ((rfork = fork()) == (pid_t)-1) {
2298 msg("cannot fork");
2299 free(at_cmdfile);
2300 rinfo_free(rp);
2301 resched(60);
2302 (void) sleep(30);
2303 return (0);
2306 if (rfork) { /* parent process */
2307 contract_abandon_latest(rfork);
2309 ++qp->nrun;
2310 rp->pid = rfork;
2311 rp->que = e->etype;
2312 if (e->etype != CRONEVENT)
2313 (e->u)->aruncnt++;
2314 else
2315 (e->u)->cruncnt++;
2316 rp->rusr = (e->u);
2317 logit(BCHAR, rp, 0);
2318 free(at_cmdfile);
2320 return (0);
2323 child_sigreset();
2324 contract_clear_template();
2326 if (e->etype != CRONEVENT) {
2327 /* open jobfile as stdin to shell */
2328 if (stat(at_cmdfile, &buf)) {
2329 if (errno == ENAMETOOLONG) {
2330 if (chdir(ATDIR) == 0)
2331 cron_unlink(e->cmd);
2332 } else
2333 cron_unlink(at_cmdfile);
2334 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2335 exit(1);
2337 if (!(buf.st_mode&ISUID)) {
2339 * if setuid bit off, original owner has
2340 * given this file to someone else
2342 cron_unlink(at_cmdfile);
2343 exit(1);
2345 if ((fd = open(at_cmdfile, O_RDONLY)) == -1) {
2346 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2347 cron_unlink(at_cmdfile);
2348 exit(1);
2350 if (fd != 0) {
2351 (void) dup2(fd, 0);
2352 (void) close(fd);
2355 * retrieve the project id of the at job and convert it
2356 * to a project name. fail if it's not a valid project
2357 * or if the user isn't a member of the project.
2359 if (projflag == 1) {
2360 if ((pproj = getprojbyid(projid, &proj,
2361 (void *)&bufs.p.buf,
2362 sizeof (bufs.p.buf))) == NULL ||
2363 !inproj(e->u->name, pproj->pj_name,
2364 bufs.p.buf2, sizeof (bufs.p.buf2))) {
2365 cron_unlink(at_cmdfile);
2366 mail((e->u)->name, BADPROJID, ERR_CANTEXECAT);
2367 exit(1);
2373 * Put process in a new session, and create a new task.
2375 if (setsid() < 0) {
2376 msg("setsid failed with errno = %d. job failed (%s)"
2377 " for user %s", errno, e->cmd, e->u->name);
2378 if (e->etype != CRONEVENT)
2379 cron_unlink(at_cmdfile);
2380 exit(1);
2384 * set correct user identification and check their account
2386 r = set_user_cred(e->u, pproj);
2387 if (r == VUC_EXPIRED) {
2388 msg("user (%s) account is expired", e->u->name);
2389 audit_cron_user_acct_expired(e->u->name);
2390 clean_out_user(e->u);
2391 exit(1);
2393 if (r == VUC_NEW_AUTH) {
2394 msg("user (%s) password has expired", e->u->name);
2395 audit_cron_user_acct_expired(e->u->name);
2396 clean_out_user(e->u);
2397 exit(1);
2399 if (r != VUC_OK) {
2400 msg("bad user (%s)", e->u->name);
2401 audit_cron_bad_user(e->u->name);
2402 clean_out_user(e->u);
2403 exit(1);
2406 * check user and initialize the supplementary group access list.
2407 * bugid 1230784: deleted from parent to avoid cron hang. Now
2408 * only child handles the call.
2411 if (verify_user_cred(e->u) != VUC_OK ||
2412 setgid(e->u->gid) == -1 ||
2413 initgroups(e->u->name, e->u->gid) == -1) {
2414 msg("bad user (%s) or setgid failed (%s)",
2415 e->u->name, e->u->name);
2416 audit_cron_bad_user(e->u->name);
2417 clean_out_user(e->u);
2418 exit(1);
2421 if ((e->u)->uid == 0) { /* set default path */
2422 /* path settable in defaults file */
2423 envinit[2] = supath;
2424 } else {
2425 envinit[2] = path;
2428 if (e->etype != CRONEVENT) {
2429 r = audit_cron_session(e->u->name, NULL,
2430 e->u->uid, e->u->gid, at_cmdfile);
2431 cron_unlink(at_cmdfile);
2432 } else {
2433 r = audit_cron_session(e->u->name, CRONDIR,
2434 e->u->uid, e->u->gid, NULL);
2436 if (r != 0) {
2437 msg("cron audit problem. job failed (%s) for user %s",
2438 e->cmd, e->u->name);
2439 exit(1);
2442 audit_cron_new_job(e->cmd, e->etype, (void *)e);
2444 if (setuid(e->u->uid) == -1) {
2445 msg("setuid failed (%s)", e->u->name);
2446 clean_out_user(e->u);
2447 exit(1);
2450 if (e->etype == CRONEVENT) {
2451 /* check for standard input to command */
2452 if (e->of.ct.input != NULL) {
2453 if ((tmpfile = strdup(TMPINFILE)) == NULL) {
2454 mail((e->u)->name, MALLOCERR,
2455 ERR_CANTEXECCRON);
2456 exit(1);
2458 if ((fd = mkstemp(tmpfile)) == -1 ||
2459 (fptr = fdopen(fd, "w")) == NULL) {
2460 mail((e->u)->name, NOSTDIN,
2461 ERR_CANTEXECCRON);
2462 cron_unlink(tmpfile);
2463 free(tmpfile);
2464 exit(1);
2466 if ((fwrite(e->of.ct.input, sizeof (char),
2467 strlen(e->of.ct.input), fptr)) !=
2468 strlen(e->of.ct.input)) {
2469 mail((e->u)->name, NOSTDIN, ERR_CANTEXECCRON);
2470 cron_unlink(tmpfile);
2471 free(tmpfile);
2472 (void) close(fd);
2473 (void) fclose(fptr);
2474 exit(1);
2476 if (fseek(fptr, (off_t)0, SEEK_SET) != -1) {
2477 if (fd != 0) {
2478 (void) dup2(fd, 0);
2479 (void) close(fd);
2482 cron_unlink(tmpfile);
2483 free(tmpfile);
2484 (void) fclose(fptr);
2485 } else if ((fd = open("/dev/null", O_RDONLY)) > 0) {
2486 (void) dup2(fd, 0);
2487 (void) close(fd);
2491 /* redirect stdout and stderr for the shell */
2492 if ((fd = open(rp->outfile, O_WRONLY|O_CREAT|O_EXCL, OUTMODE)) == 1)
2493 fd = open("/dev/null", O_WRONLY);
2495 if (fd >= 0 && fd != 1)
2496 (void) dup2(fd, 1);
2498 if (fd >= 0 && fd != 2) {
2499 (void) dup2(fd, 2);
2500 if (fd != 1)
2501 (void) close(fd);
2504 if (e->etype == CRONEVENT && e->of.ct.home != NULL) {
2505 home = (char *)get_obj(e->of.ct.home);
2506 } else {
2507 home = (e->u)->home;
2509 (void) strlcat(homedir, home, sizeof (homedir));
2510 (void) strlcat(logname, (e->u)->name, sizeof (logname));
2511 environ = envinit;
2512 if (chdir(home) == -1) {
2513 snprintf(bufs.error, sizeof (bufs.error), CANTCDHOME, home);
2514 mail((e->u)->name, bufs.error,
2515 e->etype == CRONEVENT ? ERR_CANTEXECCRON :
2516 ERR_CANTEXECAT);
2517 exit(1);
2519 #ifdef TESTING
2520 exit(1);
2521 #endif
2523 * make sure that all file descriptors EXCEPT 0, 1 and 2
2524 * will be closed.
2526 closefrom(3);
2528 if ((e->u)->uid != 0)
2529 (void) nice(qp->nice);
2530 if (e->etype == CRONEVENT) {
2531 if (e->of.ct.tz) {
2532 (void) putenv((char *)get_obj(e->of.ct.tz));
2534 if (e->of.ct.shell) {
2535 char *name;
2537 sh = (char *)get_obj(e->of.ct.shell);
2538 name = strrchr(sh, '/');
2539 if (name == NULL)
2540 name = sh;
2541 else
2542 name++;
2544 (void) putenv(sh);
2545 sh += strlen(ENV_SHELL);
2546 (void) execl(sh, name, "-c", e->cmd, 0);
2547 } else {
2548 (void) execl(SHELL, "sh", "-c", e->cmd, 0);
2549 sh = SHELL;
2551 } else { /* type == ATEVENT */
2552 (void) execl(SHELL, "sh", 0);
2553 sh = SHELL;
2555 snprintf(bufs.error, sizeof (bufs.error), CANTEXECSH, sh);
2556 mail((e->u)->name, bufs.error,
2557 e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT);
2558 exit(1);
2559 /*NOTREACHED*/
2563 * Main idle loop.
2564 * When timed out to run the job, return 0.
2565 * If for some reasons we need to reschedule jobs, return 1.
2567 static int
2568 idle(long t)
2570 time_t now;
2572 refresh = 0;
2574 while (t > 0L) {
2575 if (msg_wait(t) != 0) {
2576 /* we need to run next job immediately */
2577 return (0);
2580 reap_child();
2582 if (refresh) {
2583 /* We got THAW or REFRESH message */
2584 return (1);
2587 now = time(NULL);
2588 if (last_time > now) {
2589 /* clock has been reset to backward */
2590 return (1);
2593 if (next_event == NULL && !el_empty()) {
2594 next_event = (struct event *)el_first();
2597 if (next_event == NULL)
2598 t = INFINITY;
2599 else
2600 t = (long)next_event->time - now;
2602 return (0);
2606 * This used to be in the idle(), but moved to the separate function.
2607 * This called from various place when cron needs to reap the
2608 * child. It includes the situation that cron hit maxrun, and needs
2609 * to reschedule the job.
2611 static void
2612 reap_child()
2614 pid_t pid;
2615 int prc;
2616 struct runinfo *rp;
2618 for (;;) {
2619 pid = waitpid((pid_t)-1, &prc, WNOHANG);
2620 if (pid <= 0)
2621 break;
2622 #ifdef DEBUG
2623 fprintf(stderr,
2624 "wait returned %x for process %d\n", prc, pid);
2625 #endif
2626 if ((rp = rinfo_get(pid)) == NULL) {
2627 if (miscpid_delete(pid) == 0) {
2628 /* not found in anywhere */
2629 msg(PIDERR, pid);
2631 } else if (rp->que == ZOMB) {
2632 (void) unlink(rp->outfile);
2633 rinfo_free(rp);
2634 } else {
2635 cleanup(rp, prc);
2640 static void
2641 cleanup(struct runinfo *pr, int rc)
2643 int nextfork = 1;
2644 struct usr *p;
2645 struct stat buf;
2647 logit(ECHAR, pr, rc);
2648 --qt[pr->que].nrun;
2649 p = pr->rusr;
2650 if (pr->que != CRONEVENT)
2651 --p->aruncnt;
2652 else
2653 --p->cruncnt;
2655 if (lstat(pr->outfile, &buf) == 0) {
2656 if (!S_ISLNK(buf.st_mode) &&
2657 (buf.st_size > 0 || pr->mailwhendone)) {
2658 /* mail user stdout and stderr */
2659 for (;;) {
2660 if ((pr->pid = fork()) < 0) {
2662 * if fork fails try forever in doubling
2663 * retry times, up to 16 seconds
2665 (void) sleep(nextfork);
2666 if (nextfork < 16)
2667 nextfork += nextfork;
2668 continue;
2669 } else if (pr->pid == 0) {
2670 child_sigreset();
2671 contract_clear_template();
2673 mail_result(p, pr, buf.st_size);
2674 /* NOTREACHED */
2675 } else {
2676 contract_abandon_latest(pr->pid);
2677 pr->que = ZOMB;
2678 break;
2681 } else {
2682 (void) unlink(pr->outfile);
2683 rinfo_free(pr);
2685 } else {
2686 rinfo_free(pr);
2689 free_if_unused(p);
2693 * Mail stdout and stderr of a job to user. Get uid for real user and become
2694 * that person. We do this so that mail won't come from root since this
2695 * could be a security hole. If failure, quit - don't send mail as root.
2697 static void
2698 mail_result(struct usr *p, struct runinfo *pr, size_t filesize)
2700 struct passwd *ruser_ids;
2701 FILE *mailpipe;
2702 FILE *st;
2703 struct utsname name;
2704 int nbytes;
2705 char iobuf[BUFSIZ];
2706 char *cmd;
2707 char *lowname = (pr->jobtype == CRONEVENT ? "cron" : "at");
2709 (void) uname(&name);
2710 if ((ruser_ids = getpwnam(p->name)) == NULL)
2711 exit(0);
2712 (void) setuid(ruser_ids->pw_uid);
2714 cmd = xmalloc(strlen(MAIL) + strlen(p->name)+2);
2715 (void) sprintf(cmd, "%s %s", MAIL, p->name);
2716 mailpipe = popen(cmd, "w");
2717 free(cmd);
2718 if (mailpipe == NULL)
2719 exit(127);
2720 (void) fprintf(mailpipe, "To: %s\n", p->name);
2721 (void) fprintf(mailpipe, "Subject: %s <%s@%s> %s\n",
2722 (pr->jobtype == CRONEVENT ? "Cron" : "At"),
2723 p->name, name.nodename, pr->jobname);
2726 * RFC3834 (Section 5) defines the Auto-Submitted header to prevent
2727 * vacation replies, et al, from being sent in response to
2728 * machine-generated mail.
2730 (void) fprintf(mailpipe, "Auto-Submitted: auto-generated\n");
2733 * Additional headers for mail filtering and diagnostics:
2735 (void) fprintf(mailpipe, "X-Mailer: cron (%s %s)\n", name.sysname,
2736 name.release);
2737 (void) fprintf(mailpipe, "X-Cron-User: %s\n", p->name);
2738 (void) fprintf(mailpipe, "X-Cron-Host: %s\n", name.nodename);
2739 (void) fprintf(mailpipe, "X-Cron-Job-Name: %s\n", pr->jobname);
2740 (void) fprintf(mailpipe, "X-Cron-Job-Type: %s\n", lowname);
2743 * Message Body:
2745 * (Temporary file is fopen'ed with "r", secure open.)
2747 (void) fprintf(mailpipe, "\n");
2748 if (filesize > 0 &&
2749 (st = fopen(pr->outfile, "r")) != NULL) {
2750 while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0)
2751 (void) fwrite(iobuf, sizeof (char), nbytes, mailpipe);
2752 (void) fclose(st);
2753 } else {
2754 (void) fprintf(mailpipe, "Job completed with no output.\n");
2756 (void) pclose(mailpipe);
2757 exit(0);
2760 static int
2761 msg_wait(long tim)
2763 struct message msg;
2764 int cnt;
2765 time_t reftime;
2766 fd_set fds;
2767 struct timespec tout, *toutp;
2768 static int pending_msg;
2769 static time_t pending_reftime;
2771 if (pending_msg) {
2772 process_msg(&msgbuf, pending_reftime);
2773 pending_msg = 0;
2774 return (0);
2777 FD_ZERO(&fds);
2778 FD_SET(msgfd, &fds);
2780 toutp = NULL;
2781 if (tim != INFINITY) {
2782 #ifdef CRON_MAXSLEEP
2784 * CRON_MAXSLEEP can be defined to have cron periodically wake
2785 * up, so that cron can detect a change of TOD and adjust the
2786 * sleep time more frequently.
2788 tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim;
2789 #endif
2790 tout.tv_nsec = 0;
2791 tout.tv_sec = tim;
2792 toutp = &tout;
2795 cnt = pselect(msgfd + 1, &fds, NULL, NULL, toutp, &defmask);
2796 if (cnt == -1 && errno != EINTR)
2797 perror("! pselect");
2799 /* pselect timeout or interrupted */
2800 if (cnt <= 0)
2801 return (0);
2803 errno = 0;
2804 if ((cnt = read(msgfd, &msg, sizeof (msg))) != sizeof (msg)) {
2805 if (cnt != -1 || errno != EAGAIN)
2806 perror("! read");
2807 return (0);
2809 reftime = time(NULL);
2810 if (next_event != NULL && reftime >= next_event->time) {
2812 * we need to run the job before reloading crontab.
2814 (void) memcpy(&msgbuf, &msg, sizeof (msg));
2815 pending_msg = 1;
2816 pending_reftime = reftime;
2817 return (1);
2819 process_msg(&msg, reftime);
2820 return (0);
2824 * process the message supplied via pipe. This will be called either
2825 * immediately after cron read the message from pipe, or idle time
2826 * if the message was pending due to the job execution.
2828 static void
2829 process_msg(struct message *pmsg, time_t reftime)
2831 if (pmsg->etype == 0)
2832 return;
2834 switch (pmsg->etype) {
2835 case AT:
2836 if (pmsg->action == DELETE)
2837 del_atjob(pmsg->fname, pmsg->logname);
2838 else
2839 mod_atjob(pmsg->fname, (time_t)0);
2840 break;
2841 case CRON:
2842 if (pmsg->action == DELETE)
2843 del_ctab(pmsg->fname);
2844 else
2845 mod_ctab(pmsg->fname, reftime);
2846 break;
2847 case REFRESH:
2848 refresh = 1;
2849 pmsg->etype = 0;
2850 return;
2851 default:
2852 msg("message received - bad format");
2853 break;
2855 if (next_event != NULL) {
2856 if (next_event->etype == CRONEVENT) {
2857 switch (el_add(next_event, next_event->time,
2858 (next_event->u)->ctid)) {
2859 case -1:
2860 ignore_msg("process_msg", "cron", next_event);
2861 break;
2862 case -2: /* event time lower than init time */
2863 reset_needed = 1;
2864 break;
2866 } else { /* etype == ATEVENT */
2867 if (el_add(next_event, next_event->time,
2868 next_event->of.at.eventid) < 0) {
2869 ignore_msg("process_msg", "at", next_event);
2872 next_event = NULL;
2874 (void) fflush(stdout);
2875 pmsg->etype = 0;
2879 * Allocate a new or find an existing runinfo structure
2881 static struct runinfo *
2882 rinfo_get(pid_t pid)
2884 struct runinfo *rp;
2886 if (pid == 0) { /* allocate a new entry */
2887 rp = xcalloc(1, sizeof (struct runinfo));
2888 rp->next = rthead; /* link the entry into the list */
2889 rthead = rp;
2890 return (rp);
2892 /* search the list for an existing entry */
2893 for (rp = rthead; rp != NULL; rp = rp->next) {
2894 if (rp->pid == pid)
2895 break;
2897 return (rp);
2901 * Free a runinfo structure and its associated memory
2903 static void
2904 rinfo_free(struct runinfo *entry)
2906 struct runinfo **rpp;
2907 struct runinfo *rp;
2909 #ifdef DEBUG
2910 (void) fprintf(stderr, "freeing job %s\n", entry->jobname);
2911 #endif
2912 for (rpp = &rthead; (rp = *rpp) != NULL; rpp = &rp->next) {
2913 if (rp == entry) {
2914 *rpp = rp->next; /* unlink the entry */
2915 free(rp->outfile);
2916 free(rp->jobname);
2917 free(rp);
2918 break;
2923 /* ARGSUSED */
2924 static void
2925 thaw_handler(int sig)
2927 refresh = 1;
2931 /* ARGSUSED */
2932 static void
2933 cronend(int sig)
2935 crabort("SIGTERM", REMOVE_FIFO);
2938 /*ARGSUSED*/
2939 static void
2940 child_handler(int sig)
2945 static void
2946 child_sigreset(void)
2948 (void) signal(SIGCLD, SIG_DFL);
2949 (void) sigprocmask(SIG_SETMASK, &defmask, NULL);
2953 * crabort() - handle exits out of cron
2955 static void
2956 crabort(char *mssg, int action)
2958 int c;
2960 if (action & REMOVE_FIFO) {
2961 /* FIFO vanishes when cron finishes */
2962 if (unlink(FIFO) < 0)
2963 perror("cron could not unlink FIFO");
2966 if (action & CONSOLE_MSG) {
2967 /* write error msg to console */
2968 if ((c = open(CONSOLE, O_WRONLY)) >= 0) {
2969 (void) write(c, "cron aborted: ", 14);
2970 (void) write(c, mssg, strlen(mssg));
2971 (void) write(c, "\n", 1);
2972 (void) close(c);
2976 /* always log the message */
2977 msg(mssg);
2978 msg("******* CRON ABORTED ********");
2979 exit(1);
2983 * msg() - time-stamped error reporting function
2985 /*PRINTFLIKE1*/
2986 static void
2987 msg(char *fmt, ...)
2989 va_list args;
2990 time_t t;
2992 t = time(NULL);
2994 (void) fflush(stdout);
2996 (void) fprintf(stderr, "! ");
2998 va_start(args, fmt);
2999 (void) vfprintf(stderr, fmt, args);
3000 va_end(args);
3002 (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
3003 (void) fprintf(stderr, " %s\n", timebuf);
3005 (void) fflush(stderr);
3008 static void
3009 ignore_msg(char *func_name, char *job_type, struct event *event)
3011 msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)",
3012 func_name, job_type,
3013 event->u->name ? event->u->name : "unknown",
3014 event->cmd ? event->cmd : "unknown",
3015 event->time);
3018 static void
3019 logit(int cc, struct runinfo *rp, int rc)
3021 time_t t;
3022 int ret;
3024 if (!log)
3025 return;
3027 t = time(NULL);
3028 if (cc == BCHAR)
3029 (void) printf("%c CMD: %s\n", cc, next_event->cmd);
3030 (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
3031 (void) printf("%c %s %u %c %s",
3032 cc, (rp->rusr)->name, rp->pid, QUE(rp->que), timebuf);
3033 if ((ret = TSTAT(rc)) != 0)
3034 (void) printf(" ts=%d", ret);
3035 if ((ret = RCODE(rc)) != 0)
3036 (void) printf(" rc=%d", ret);
3037 (void) putchar('\n');
3038 (void) fflush(stdout);
3041 static void
3042 resched(int delay)
3044 time_t nt;
3046 /* run job at a later time */
3047 nt = next_event->time + delay;
3048 if (next_event->etype == CRONEVENT) {
3049 next_event->time = next_time(next_event, (time_t)0);
3050 if (nt < next_event->time)
3051 next_event->time = nt;
3052 switch (el_add(next_event, next_event->time,
3053 (next_event->u)->ctid)) {
3054 case -1:
3055 ignore_msg("resched", "cron", next_event);
3056 break;
3057 case -2: /* event time lower than init time */
3058 reset_needed = 1;
3059 break;
3061 delayed = 1;
3062 msg("rescheduling a cron job");
3063 return;
3065 add_atevent(next_event->u, next_event->cmd, nt, next_event->etype);
3066 msg("rescheduling at job");
3069 static void
3070 quedefs(int action)
3072 int i;
3073 int j;
3074 char qbuf[QBUFSIZ];
3075 FILE *fd;
3077 /* set up default queue definitions */
3078 for (i = 0; i < NQUEUE; i++) {
3079 qt[i].njob = qd.njob;
3080 qt[i].nice = qd.nice;
3081 qt[i].nwait = qd.nwait;
3083 if (action == DEFAULT)
3084 return;
3085 if ((fd = fopen(QUEDEFS, "r")) == NULL) {
3086 msg("cannot open quedefs file");
3087 msg("using default queue definitions");
3088 return;
3090 while (fgets(qbuf, QBUFSIZ, fd) != NULL) {
3091 if ((j = qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.')
3092 continue;
3093 parsqdef(&qbuf[2]);
3094 qt[j].njob = qq.njob;
3095 qt[j].nice = qq.nice;
3096 qt[j].nwait = qq.nwait;
3098 (void) fclose(fd);
3101 static void
3102 parsqdef(char *name)
3104 int i;
3106 qq = qd;
3107 while (*name) {
3108 i = 0;
3109 while (isdigit(*name)) {
3110 i *= 10;
3111 i += *name++ - '0';
3113 switch (*name++) {
3114 case JOBF:
3115 qq.njob = i;
3116 break;
3117 case NICEF:
3118 qq.nice = i;
3119 break;
3120 case WAITF:
3121 qq.nwait = i;
3122 break;
3128 * defaults - read defaults from /etc/default/cron
3130 static void
3131 defaults()
3133 int flags;
3134 char *deflog;
3135 char *hz, *tz;
3138 * get HZ value for environment
3140 if ((hz = getenv("HZ")) == NULL)
3141 (void) sprintf(hzname, "HZ=%d", HZ);
3142 else
3143 (void) snprintf(hzname, sizeof (hzname), "HZ=%s", hz);
3145 * get TZ value for environment
3147 (void) snprintf(tzone, sizeof (tzone), "TZ=%s",
3148 ((tz = getenv("TZ")) != NULL) ? tz : DEFTZ);
3150 if (defopen(DEFFILE) == 0) {
3151 /* ignore case */
3152 flags = defcntl(DC_GETFLAGS, 0);
3153 TURNOFF(flags, DC_CASE);
3154 (void) defcntl(DC_SETFLAGS, flags);
3156 if (((deflog = defread("CRONLOG=")) == NULL) ||
3157 (*deflog == 'N') || (*deflog == 'n'))
3158 log = 0;
3159 else
3160 log = 1;
3161 /* fix for 1087611 - allow paths to be set in defaults file */
3162 if ((Def_path = defread("PATH=")) != NULL) {
3163 (void) strlcat(path, Def_path, LINE_MAX);
3164 } else {
3165 (void) strlcpy(path, NONROOTPATH, LINE_MAX);
3167 if ((Def_supath = defread("SUPATH=")) != NULL) {
3168 (void) strlcat(supath, Def_supath, LINE_MAX);
3169 } else {
3170 (void) strlcpy(supath, ROOTPATH, LINE_MAX);
3172 (void) defopen(NULL);
3177 * Determine if a user entry for a job is still ok. The method used here
3178 * is a lot (about 75x) faster than using setgrent() / getgrent()
3179 * endgrent(). It should be safe because we use the sysconf to determine
3180 * the max, and it tolerates the max being 0.
3183 static int
3184 verify_user_cred(struct usr *u)
3186 struct passwd *pw;
3187 size_t numUsrGrps = 0;
3188 size_t numOrigGrps = 0;
3189 size_t i;
3190 int retval;
3193 * Maximum number of groups a user may be in concurrently. This
3194 * is a value which we obtain at runtime through a sysconf()
3195 * call.
3198 static size_t nGroupsMax = (size_t)-1;
3201 * Arrays for cron user's group list, constructed at startup to
3202 * be nGroupsMax elements long, used for verifying user
3203 * credentials prior to execution.
3206 static gid_t *UsrGrps;
3207 static gid_t *OrigGrps;
3209 if ((pw = getpwnam(u->name)) == NULL)
3210 return (VUC_BADUSER);
3211 if (u->home != NULL) {
3212 if (strcmp(u->home, pw->pw_dir) != 0) {
3213 free(u->home);
3214 u->home = xmalloc(strlen(pw->pw_dir) + 1);
3215 (void) strcpy(u->home, pw->pw_dir);
3217 } else {
3218 u->home = xmalloc(strlen(pw->pw_dir) + 1);
3219 (void) strcpy(u->home, pw->pw_dir);
3221 if (u->uid != pw->pw_uid)
3222 u->uid = pw->pw_uid;
3223 if (u->gid != pw->pw_gid)
3224 u->gid = pw->pw_gid;
3227 * Create the group id lists needed for job credential
3228 * verification.
3231 if (nGroupsMax == (size_t)-1) {
3232 if ((nGroupsMax = sysconf(_SC_NGROUPS_MAX)) > 0) {
3233 UsrGrps = xcalloc(nGroupsMax, sizeof (gid_t));
3234 OrigGrps = xcalloc(nGroupsMax, sizeof (gid_t));
3237 #ifdef DEBUG
3238 (void) fprintf(stderr, "nGroupsMax = %ld\n", nGroupsMax);
3239 #endif
3242 #ifdef DEBUG
3243 (void) fprintf(stderr, "verify_user_cred (%s-%d)\n", pw->pw_name,
3244 pw->pw_uid);
3245 (void) fprintf(stderr, "verify_user_cred: pw->pw_gid = %d, "
3246 "u->gid = %d\n", pw->pw_gid, u->gid);
3247 #endif
3249 retval = (u->gid == pw->pw_gid) ? VUC_OK : VUC_NOTINGROUP;
3251 if (nGroupsMax > 0) {
3252 numOrigGrps = getgroups(nGroupsMax, OrigGrps);
3254 (void) initgroups(pw->pw_name, pw->pw_gid);
3255 numUsrGrps = getgroups(nGroupsMax, UsrGrps);
3257 for (i = 0; i < numUsrGrps; i++) {
3258 if (UsrGrps[i] == u->gid) {
3259 retval = VUC_OK;
3260 break;
3264 if (OrigGrps) {
3265 (void) setgroups(numOrigGrps, OrigGrps);
3269 #ifdef DEBUG
3270 (void) fprintf(stderr, "verify_user_cred: VUC = %d\n", retval);
3271 #endif
3273 return (retval);
3276 static int
3277 set_user_cred(const struct usr *u, struct project *pproj)
3279 static char *progname = "cron";
3280 int r = 0, rval = 0;
3282 if ((r = pam_start(progname, u->name, &pam_conv, &pamh))
3283 != PAM_SUCCESS) {
3284 #ifdef DEBUG
3285 msg("pam_start returns %d\n", r);
3286 #endif
3287 rval = VUC_BADUSER;
3288 goto set_eser_cred_exit;
3291 r = pam_acct_mgmt(pamh, 0);
3292 #ifdef DEBUG
3293 msg("pam_acc_mgmt returns %d\n", r);
3294 #endif
3295 if (r == PAM_ACCT_EXPIRED) {
3296 rval = VUC_EXPIRED;
3297 goto set_eser_cred_exit;
3299 if (r == PAM_NEW_AUTHTOK_REQD) {
3300 rval = VUC_NEW_AUTH;
3301 goto set_eser_cred_exit;
3303 if (r != PAM_SUCCESS) {
3304 rval = VUC_BADUSER;
3305 goto set_eser_cred_exit;
3308 if (pproj != NULL) {
3309 size_t sz = sizeof (PROJECT) + strlen(pproj->pj_name);
3310 char *buf = alloca(sz);
3312 (void) snprintf(buf, sz, PROJECT "%s", pproj->pj_name);
3313 (void) pam_set_item(pamh, PAM_RESOURCE, buf);
3316 r = pam_setcred(pamh, PAM_ESTABLISH_CRED);
3317 if (r != PAM_SUCCESS)
3318 rval = VUC_BADUSER;
3320 set_eser_cred_exit:
3321 (void) pam_end(pamh, r);
3322 return (rval);
3325 static void
3326 clean_out_user(struct usr *u)
3328 if (next_event->u == u) {
3329 next_event = NULL;
3332 clean_out_ctab(u);
3333 clean_out_atjobs(u);
3334 free_if_unused(u);
3337 static void
3338 clean_out_atjobs(struct usr *u)
3340 struct event *ev, *pv;
3342 for (pv = NULL, ev = u->atevents;
3343 ev != NULL;
3344 pv = ev, ev = ev->link, free(pv)) {
3345 el_remove(ev->of.at.eventid, 1);
3346 if (cwd == AT)
3347 cron_unlink(ev->cmd);
3348 else {
3349 char buf[PATH_MAX];
3350 if (strlen(ATDIR) + strlen(ev->cmd) + 2
3351 < PATH_MAX) {
3352 (void) sprintf(buf, "%s/%s", ATDIR, ev->cmd);
3353 cron_unlink(buf);
3356 free(ev->cmd);
3359 u->atevents = NULL;
3362 static void
3363 clean_out_ctab(struct usr *u)
3365 rm_ctevents(u);
3366 el_remove(u->ctid, 0);
3367 u->ctid = 0;
3368 u->ctexists = 0;
3371 static void
3372 cron_unlink(char *name)
3374 int r;
3376 r = unlink(name);
3377 if (r == 0 || (r == -1 && errno == ENOENT)) {
3378 (void) audit_cron_delete_anc_file(name, NULL);
3382 static void
3383 create_anc_ctab(struct event *e)
3385 if (audit_cron_create_anc_file(e->u->name,
3386 (cwd == CRON) ? NULL:CRONDIR,
3387 e->u->name, e->u->uid) == -1) {
3388 process_anc_files(CRON_ANC_DELETE);
3389 crabort("cannot create ancillary files for crontabs",
3390 REMOVE_FIFO|CONSOLE_MSG);
3394 static void
3395 delete_anc_ctab(struct event *e)
3397 (void) audit_cron_delete_anc_file(e->u->name,
3398 (cwd == CRON) ? NULL:CRONDIR);
3401 static void
3402 create_anc_atjob(struct event *e)
3404 if (!e->of.at.exists)
3405 return;
3407 if (audit_cron_create_anc_file(e->cmd,
3408 (cwd == AT) ? NULL:ATDIR,
3409 e->u->name, e->u->uid) == -1) {
3410 process_anc_files(CRON_ANC_DELETE);
3411 crabort("cannot create ancillary files for atjobs",
3412 REMOVE_FIFO|CONSOLE_MSG);
3416 static void
3417 delete_anc_atjob(struct event *e)
3419 if (!e->of.at.exists)
3420 return;
3422 (void) audit_cron_delete_anc_file(e->cmd,
3423 (cwd == AT) ? NULL:ATDIR);
3427 static void
3428 process_anc_files(int del)
3430 struct usr *u = uhead;
3431 struct event *e;
3433 if (!audit_cron_mode())
3434 return;
3436 for (;;) {
3437 if (u->ctexists && u->ctevents != NULL) {
3438 e = u->ctevents;
3439 for (;;) {
3440 if (del)
3441 delete_anc_ctab(e);
3442 else
3443 create_anc_ctab(e);
3444 if ((e = e->link) == NULL)
3445 break;
3449 if (u->atevents != NULL) {
3450 e = u->atevents;
3451 for (;;) {
3452 if (del)
3453 delete_anc_atjob(e);
3454 else
3455 create_anc_atjob(e);
3456 if ((e = e->link) == NULL)
3457 break;
3461 if ((u = u->nextusr) == NULL)
3462 break;
3466 /*ARGSUSED*/
3467 static int
3468 cron_conv(int num_msg, struct pam_message **msgs,
3469 struct pam_response **response, void *appdata_ptr)
3471 struct pam_message **m = msgs;
3472 int i;
3474 for (i = 0; i < num_msg; i++) {
3475 switch (m[i]->msg_style) {
3476 case PAM_ERROR_MSG:
3477 case PAM_TEXT_INFO:
3478 if (m[i]->msg != NULL) {
3479 (void) msg("%s\n", m[i]->msg);
3481 break;
3483 default:
3484 break;
3487 return (0);
3491 * Cron creates process for other than job. Mail process is the
3492 * one which rinfo does not cover. Therefore, miscpid will keep
3493 * track of the pids executed from cron. Otherwise, we will see
3494 * "unexpected pid returned.." messages appear in the log file.
3496 static void
3497 miscpid_insert(pid_t pid)
3499 struct miscpid *mp;
3501 mp = xmalloc(sizeof (*mp));
3502 mp->pid = pid;
3503 mp->next = miscpid_head;
3504 miscpid_head = mp;
3507 static int
3508 miscpid_delete(pid_t pid)
3510 struct miscpid *mp, *omp;
3511 int found = 0;
3513 omp = NULL;
3514 for (mp = miscpid_head; mp != NULL; mp = mp->next) {
3515 if (mp->pid == pid) {
3516 found = 1;
3517 break;
3519 omp = mp;
3521 if (found) {
3522 if (omp != NULL)
3523 omp->next = mp->next;
3524 else
3525 miscpid_head = NULL;
3526 free(mp);
3528 return (found);
3532 * Establish contract terms such that all children are in abandoned
3533 * process contracts.
3535 static void
3536 contract_set_template(void)
3538 int fd;
3540 if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3541 crabort("cannot open process contract template",
3542 REMOVE_FIFO | CONSOLE_MSG);
3544 if (ct_pr_tmpl_set_param(fd, 0) ||
3545 ct_tmpl_set_informative(fd, 0) ||
3546 ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR))
3547 crabort("cannot establish contract template terms",
3548 REMOVE_FIFO | CONSOLE_MSG);
3550 if (ct_tmpl_activate(fd))
3551 crabort("cannot activate contract template",
3552 REMOVE_FIFO | CONSOLE_MSG);
3554 (void) close(fd);
3558 * Clear active process contract template.
3560 static void
3561 contract_clear_template(void)
3563 int fd;
3565 if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3566 crabort("cannot open process contract template",
3567 REMOVE_FIFO | CONSOLE_MSG);
3569 if (ct_tmpl_clear(fd))
3570 crabort("cannot clear contract template",
3571 REMOVE_FIFO | CONSOLE_MSG);
3573 (void) close(fd);
3577 * Abandon latest process contract unconditionally. If we have leaked [some
3578 * critical amount], exit such that the kernel reaps our contracts.
3580 static void
3581 contract_abandon_latest(pid_t pid)
3583 int r;
3584 ctid_t id;
3585 static uint_t cts_lost;
3587 if (cts_lost > MAX_LOST_CONTRACTS)
3588 crabort("repeated failure to abandon contracts",
3589 REMOVE_FIFO | CONSOLE_MSG);
3591 if (r = contract_latest(&id)) {
3592 msg("could not obtain latest contract for "
3593 "PID %ld: %s", pid, strerror(r));
3594 cts_lost++;
3595 return;
3598 if (r = contract_abandon_id(id)) {
3599 msg("could not abandon latest contract %ld: %s", id,
3600 strerror(r));
3601 cts_lost++;
3602 return;
3606 static struct shared *
3607 create_shared(void *obj, void * (*obj_alloc)(void *obj),
3608 void (*obj_free)(void *))
3610 struct shared *out;
3612 if ((out = xmalloc(sizeof (struct shared))) == NULL) {
3613 return (NULL);
3615 if ((out->obj = obj_alloc(obj)) == NULL) {
3616 free(out);
3617 return (NULL);
3619 out->count = 1;
3620 out->free = obj_free;
3622 return (out);
3625 static struct shared *
3626 create_shared_str(char *str)
3628 return (create_shared(str, (void *(*)(void *))strdup, free));
3631 static struct shared *
3632 dup_shared(struct shared *obj)
3634 if (obj != NULL) {
3635 obj->count++;
3637 return (obj);
3640 static void
3641 rel_shared(struct shared *obj)
3643 if (obj && (--obj->count) == 0) {
3644 obj->free(obj->obj);
3645 free(obj);
3649 static void *
3650 get_obj(struct shared *obj)
3652 return (obj->obj);