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