Merge commit 'b1e7e97d3b60469b243b3b2e22c7d8cbd11c7c90'
[unleashed.git] / usr / src / cmd / cron / cron.c
blob89da3f1fe087e9f6d68e68fe15df4eba2cc29b92
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(8) needs to re-initialize */
355 static int refresh;
356 static sigset_t defmask, sigmask;
358 static int cron_conv(int, struct pam_message **,
359 struct pam_response **, void *);
361 static struct pam_conv pam_conv = {cron_conv, NULL};
362 static pam_handle_t *pamh; /* Authentication handle */
365 * Function to help check a user's credentials.
368 static int verify_user_cred(struct usr *u);
371 * Values returned by verify_user_cred and set_user_cred:
374 #define VUC_OK 0
375 #define VUC_BADUSER 1
376 #define VUC_NOTINGROUP 2
377 #define VUC_EXPIRED 3
378 #define VUC_NEW_AUTH 4
381 * Functions to remove a user or job completely from the running database.
383 static void clean_out_atjobs(struct usr *u);
384 static void clean_out_ctab(struct usr *u);
385 static void clean_out_user(struct usr *u);
386 static void cron_unlink(char *name);
389 * functions in elm.c
391 extern void el_init(int, time_t, time_t, int);
392 extern int el_add(void *, time_t, int);
393 extern void el_remove(int, int);
394 extern int el_empty(void);
395 extern void *el_first(void);
396 extern void el_delete(void);
398 static int valid_entry(char *, int);
399 static struct usr *create_ulist(char *, int);
400 static void init_cronevent(char *, int);
401 static void init_atevent(char *, time_t, int, int);
402 static void update_atevent(struct usr *, char *, time_t, int);
405 main(int argc, char *argv[])
407 time_t t;
408 time_t ne_time; /* amt of time until next event execution */
409 time_t newtime, lastmtime = 0L;
410 struct usr *u;
411 struct event *e, *e2, *eprev;
412 struct stat buf;
413 pid_t rfork;
414 struct sigaction act;
417 * reset_needed is set to 1 whenever el_add() finds out that a cron
418 * job is scheduled to be run before the time when cron(8) daemon
419 * initialized.
420 * Other cases where a reset is needed is when ex() finds that the
421 * event to be executed is being run at the wrong time, or when idle()
422 * determines that time was reset.
423 * We immediately return to the top of the while (TRUE) loop in
424 * main() where the event list is cleared and rebuilt, and reset_needed
425 * is set back to 0.
427 reset_needed = 0;
430 * Only the privileged user can run this command.
432 if (getuid() != 0)
433 crabort(NOTALLOWED, 0);
435 begin:
436 (void) setlocale(LC_ALL, "");
437 /* fork unless 'nofork' is specified */
438 if ((argc <= 1) || (strcmp(argv[1], "nofork"))) {
439 if (rfork = fork()) {
440 if (rfork == (pid_t)-1) {
441 (void) sleep(30);
442 goto begin;
444 return (0);
446 didfork++;
447 (void) setpgrp(); /* detach cron from console */
450 (void) umask(022);
451 (void) signal(SIGHUP, SIG_IGN);
452 (void) signal(SIGINT, SIG_IGN);
453 (void) signal(SIGQUIT, SIG_IGN);
454 (void) signal(SIGTERM, cronend);
456 defaults();
457 initialize(1);
458 quedefs(DEFAULT); /* load default queue definitions */
459 cron_pid = getpid();
460 msg("*** cron started *** pid = %d", cron_pid);
462 /* setup THAW handler */
463 act.sa_handler = thaw_handler;
464 act.sa_flags = 0;
465 (void) sigemptyset(&act.sa_mask);
466 (void) sigaction(SIGTHAW, &act, NULL);
468 /* setup CHLD handler */
469 act.sa_handler = child_handler;
470 act.sa_flags = 0;
471 (void) sigemptyset(&act.sa_mask);
472 (void) sigaddset(&act.sa_mask, SIGCLD);
473 (void) sigaction(SIGCLD, &act, NULL);
475 (void) sigemptyset(&defmask);
476 (void) sigemptyset(&sigmask);
477 (void) sigaddset(&sigmask, SIGCLD);
478 (void) sigaddset(&sigmask, SIGTHAW);
479 (void) sigprocmask(SIG_BLOCK, &sigmask, NULL);
481 t_old = init_time;
482 last_time = t_old;
483 for (;;) { /* MAIN LOOP */
484 t = time(NULL);
485 if ((t_old > t) || (t-last_time > CUSHION) || reset_needed) {
486 reset_needed = 0;
488 * the time was set backwards or forward or
489 * refresh is requested.
491 if (refresh)
492 msg("re-scheduling jobs");
493 else
494 msg("time was reset, re-initializing");
495 el_delete();
496 u = uhead;
497 while (u != NULL) {
498 rm_ctevents(u);
499 e = u->atevents;
500 while (e != NULL) {
501 free(e->cmd);
502 e2 = e->link;
503 free(e);
504 e = e2;
506 u->atevents = NULL;
507 u = u->nextusr;
509 (void) close(msgfd);
510 initialize(0);
511 t = time(NULL);
512 last_time = t;
514 * reset_needed might have been set in the functions
515 * call path from initialize()
517 if (reset_needed) {
518 continue;
521 t_old = t;
523 if (next_event == NULL && !el_empty()) {
524 next_event = (struct event *)el_first();
526 if (next_event == NULL) {
527 ne_time = INFINITY;
528 } else {
529 ne_time = next_event->time - t;
530 #ifdef DEBUG
531 cftime(timebuf, "%+", &next_event->time);
532 (void) fprintf(stderr, "next_time=%ld %s\n",
533 next_event->time, timebuf);
534 #endif
536 if (ne_time > 0) {
538 * reset_needed may be set in the functions call path
539 * from idle()
541 if (idle(ne_time) || reset_needed) {
542 reset_needed = 1;
543 continue;
547 if (stat(QUEDEFS, &buf)) {
548 msg("cannot stat QUEDEFS file");
549 } else if (lastmtime != buf.st_mtime) {
550 quedefs(LOAD);
551 lastmtime = buf.st_mtime;
554 last_time = next_event->time; /* save execution time */
557 * reset_needed may be set in the functions call path
558 * from ex()
560 if (ex(next_event) || reset_needed) {
561 reset_needed = 1;
562 continue;
565 switch (next_event->etype) {
566 case CRONEVENT:
567 /* add cronevent back into the main event list */
568 if (delayed) {
569 delayed = 0;
570 break;
574 * check if time(0)< last_time. if so, then the
575 * system clock has gone backwards. to prevent this
576 * job from being started twice, we reschedule this
577 * job for the >>next time after last_time<<, and
578 * then set next_event->time to this. note that
579 * crontab's resolution is 1 minute.
582 if (last_time > time(NULL)) {
583 msg(CLOCK_DRIFT);
585 * bump up to next 30 second
586 * increment
587 * 1 <= newtime <= 30
589 newtime = 30 - (last_time % 30);
590 newtime += last_time;
593 * get the next scheduled event,
594 * not the one that we just
595 * kicked off!
597 next_event->time =
598 next_time(next_event, newtime);
599 t_old = time(NULL);
600 } else {
601 next_event->time =
602 next_time(next_event, (time_t)0);
604 #ifdef DEBUG
605 cftime(timebuf, "%+", &next_event->time);
606 (void) fprintf(stderr,
607 "pushing back cron event %s at %ld (%s)\n",
608 next_event->cmd, next_event->time, timebuf);
609 #endif
611 switch (el_add(next_event, next_event->time,
612 (next_event->u)->ctid)) {
613 case -1:
614 ignore_msg("main", "cron", next_event);
615 break;
616 case -2: /* event time lower than init time */
617 reset_needed = 1;
618 break;
620 break;
621 default:
622 /* remove at or batch job from system */
623 if (delayed) {
624 delayed = 0;
625 break;
627 eprev = NULL;
628 e = (next_event->u)->atevents;
629 while (e != NULL) {
630 if (e == next_event) {
631 if (eprev == NULL)
632 (e->u)->atevents = e->link;
633 else
634 eprev->link = e->link;
635 free(e->cmd);
636 free(e);
637 break;
638 } else {
639 eprev = e;
640 e = e->link;
643 break;
645 next_event = NULL;
648 /*NOTREACHED*/
651 static void
652 initialize(int firstpass)
654 #ifdef DEBUG
655 (void) fprintf(stderr, "in initialize\n");
656 #endif
657 if (firstpass) {
658 /* for mail(1), make sure messages come from root */
659 if (putenv("LOGNAME=root") != 0) {
660 crabort("cannot expand env variable",
661 REMOVE_FIFO|CONSOLE_MSG);
663 if (access(FIFO, R_OK) == -1) {
664 if (errno == ENOENT) {
665 if (mknod(FIFO, S_IFIFO|0600, 0) != 0)
666 crabort("cannot create fifo queue",
667 REMOVE_FIFO|CONSOLE_MSG);
668 } else {
669 if (NOFORK) {
670 /* didn't fork... init(8) is waiting */
671 (void) sleep(60);
673 perror("FIFO");
674 crabort("cannot access fifo queue",
675 REMOVE_FIFO|CONSOLE_MSG);
677 } else {
678 if (NOFORK) {
679 /* didn't fork... init(8) is waiting */
680 (void) sleep(60);
682 * the wait is painful, but we don't want
683 * init respawning this quickly
686 crabort("cannot start cron; FIFO exists", CONSOLE_MSG);
690 if ((msgfd = open(FIFO, O_RDWR)) < 0) {
691 perror("! open");
692 crabort("cannot open fifo queue", REMOVE_FIFO|CONSOLE_MSG);
695 init_time = time(NULL);
696 el_init(8, init_time, (time_t)(60*60*24), 10);
698 init_time = time(NULL);
699 el_init(8, init_time, (time_t)(60*60*24), 10);
702 * read directories, create users list, and add events to the
703 * main event list. Only zero user list on firstpass.
705 if (firstpass)
706 uhead = NULL;
707 read_dirs(firstpass);
708 next_event = NULL;
710 if (!firstpass)
711 return;
713 /* stdout is log file */
714 if (freopen(ACCTFILE, "a", stdout) == NULL)
715 (void) fprintf(stderr, "cannot open %s\n", ACCTFILE);
717 /* log should be root-only */
718 (void) fchmod(1, S_IRUSR|S_IWUSR);
720 /* stderr also goes to ACCTFILE */
721 (void) close(fileno(stderr));
722 (void) dup(1);
723 /* null for stdin */
724 (void) freopen("/dev/null", "r", stdin);
726 contract_set_template();
729 static void
730 read_dirs(int first)
732 DIR *dir;
733 struct dirent *dp;
734 char *ptr;
735 int jobtype;
736 time_t tim;
739 if (chdir(CRONDIR) == -1)
740 crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG);
741 cwd = CRON;
742 if ((dir = opendir(".")) == NULL)
743 crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG);
744 while ((dp = readdir(dir)) != NULL) {
745 if (!valid_entry(dp->d_name, CRONEVENT))
746 continue;
747 init_cronevent(dp->d_name, first);
749 (void) closedir(dir);
751 if (chdir(ATDIR) == -1) {
752 msg("cannot chdir to at directory");
753 return;
755 if ((dir = opendir(".")) == NULL) {
756 msg("cannot read at at directory");
757 return;
759 cwd = AT;
760 while ((dp = readdir(dir)) != NULL) {
761 if (!valid_entry(dp->d_name, ATEVENT))
762 continue;
763 ptr = dp->d_name;
764 if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
765 continue;
766 ptr++;
767 if (!isalpha(*ptr))
768 continue;
769 jobtype = *ptr - 'a';
770 if (jobtype >= NQUEUE) {
771 cron_unlink(dp->d_name);
772 continue;
774 init_atevent(dp->d_name, tim, jobtype, first);
776 (void) closedir(dir);
779 static int
780 valid_entry(char *name, int type)
782 struct stat buf;
784 if (strcmp(name, ".") == 0 ||
785 strcmp(name, "..") == 0)
786 return (0);
788 if (stat(name, &buf)) {
789 mail(name, BADSTAT, ERR_UNIXERR);
790 cron_unlink(name);
791 return (0);
793 if (!S_ISREG(buf.st_mode)) {
794 mail(name, BADTYPE, ERR_NOTREG);
795 cron_unlink(name);
796 return (0);
798 if (type == ATEVENT) {
799 if (!(buf.st_mode & ISUID)) {
800 cron_unlink(name);
801 return (0);
804 return (1);
807 struct usr *
808 create_ulist(char *name, int type)
810 struct usr *u;
812 u = xcalloc(1, sizeof (struct usr));
813 u->name = xstrdup(name);
814 if (type == CRONEVENT) {
815 u->ctexists = TRUE;
816 u->ctid = ecid++;
817 } else {
818 u->ctexists = FALSE;
819 u->ctid = 0;
821 u->uid = (uid_t)-1;
822 u->gid = (uid_t)-1;
823 u->nextusr = uhead;
824 uhead = u;
825 return (u);
828 void
829 init_cronevent(char *name, int first)
831 struct usr *u;
833 if (first) {
834 u = create_ulist(name, CRONEVENT);
835 readcron(u, 0);
836 } else {
837 if ((u = find_usr(name)) == NULL) {
838 u = create_ulist(name, CRONEVENT);
839 readcron(u, 0);
840 } else {
841 u->ctexists = TRUE;
842 rm_ctevents(u);
843 el_remove(u->ctid, 0);
844 readcron(u, 0);
849 void
850 init_atevent(char *name, time_t tim, int jobtype, int first)
852 struct usr *u;
854 if (first) {
855 u = create_ulist(name, ATEVENT);
856 add_atevent(u, name, tim, jobtype);
857 } else {
858 if ((u = find_usr(name)) == NULL) {
859 u = create_ulist(name, ATEVENT);
860 add_atevent(u, name, tim, jobtype);
861 } else {
862 update_atevent(u, name, tim, jobtype);
867 static void
868 mod_ctab(char *name, time_t reftime)
870 struct passwd *pw;
871 struct stat buf;
872 struct usr *u;
873 char namebuf[LINE_MAX];
874 char *pname;
876 if ((pw = getpwnam(name)) == NULL) {
877 msg("No such user as %s - cron entries not created", name);
878 return;
880 if (cwd != CRON) {
881 if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
882 CRONDIR, name) >= sizeof (namebuf)) {
883 msg("Too long path name %s - cron entries not created",
884 namebuf);
885 return;
887 pname = namebuf;
888 } else {
889 pname = name;
892 * a warning message is given by the crontab command so there is
893 * no need to give one here...... use this code if you only want
894 * users with a login shell of /usr/bin/sh to use cron
896 #ifdef BOURNESHELLONLY
897 if ((strcmp(pw->pw_shell, "") != 0) &&
898 (strcmp(pw->pw_shell, SHELL) != 0)) {
899 mail(name, BADSHELL, ERR_CANTEXECCRON);
900 cron_unlink(pname);
901 return;
903 #endif
904 if (stat(pname, &buf)) {
905 mail(name, BADSTAT, ERR_UNIXERR);
906 cron_unlink(pname);
907 return;
909 if (!S_ISREG(buf.st_mode)) {
910 mail(name, BADTYPE, ERR_CRONTABENT);
911 return;
913 if ((u = find_usr(name)) == NULL) {
914 #ifdef DEBUG
915 (void) fprintf(stderr, "new user (%s) with a crontab\n", name);
916 #endif
917 u = create_ulist(name, CRONEVENT);
918 u->home = xmalloc(strlen(pw->pw_dir) + 1);
919 (void) strcpy(u->home, pw->pw_dir);
920 u->uid = pw->pw_uid;
921 u->gid = pw->pw_gid;
922 readcron(u, reftime);
923 } else {
924 u->uid = pw->pw_uid;
925 u->gid = pw->pw_gid;
926 if (u->home != NULL) {
927 if (strcmp(u->home, pw->pw_dir) != 0) {
928 free(u->home);
929 u->home = xmalloc(strlen(pw->pw_dir) + 1);
930 (void) strcpy(u->home, pw->pw_dir);
932 } else {
933 u->home = xmalloc(strlen(pw->pw_dir) + 1);
934 (void) strcpy(u->home, pw->pw_dir);
936 u->ctexists = TRUE;
937 if (u->ctid == 0) {
938 #ifdef DEBUG
939 (void) fprintf(stderr, "%s now has a crontab\n",
940 u->name);
941 #endif
942 /* user didnt have a crontab last time */
943 u->ctid = ecid++;
944 u->ctevents = NULL;
945 readcron(u, reftime);
946 return;
948 #ifdef DEBUG
949 (void) fprintf(stderr, "%s has revised his crontab\n", u->name);
950 #endif
951 rm_ctevents(u);
952 el_remove(u->ctid, 0);
953 readcron(u, reftime);
957 /* ARGSUSED */
958 static void
959 mod_atjob(char *name, time_t reftime)
961 char *ptr;
962 time_t tim;
963 struct passwd *pw;
964 struct stat buf;
965 struct usr *u;
966 char namebuf[PATH_MAX];
967 char *pname;
968 int jobtype;
970 ptr = name;
971 if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
972 return;
973 ptr++;
974 if (!isalpha(*ptr))
975 return;
976 jobtype = *ptr - 'a';
978 if (cwd != AT) {
979 if (snprintf(namebuf, sizeof (namebuf), "%s/%s", ATDIR, name)
980 >= sizeof (namebuf)) {
981 return;
983 pname = namebuf;
984 } else {
985 pname = name;
987 if (stat(pname, &buf) || jobtype >= NQUEUE) {
988 cron_unlink(pname);
989 return;
991 if (!(buf.st_mode & ISUID) || !S_ISREG(buf.st_mode)) {
992 cron_unlink(pname);
993 return;
995 if ((pw = getpwuid(buf.st_uid)) == NULL) {
996 cron_unlink(pname);
997 return;
1000 * a warning message is given by the at command so there is no
1001 * need to give one here......use this code if you only want
1002 * users with a login shell of /usr/bin/sh to use cron
1004 #ifdef BOURNESHELLONLY
1005 if ((strcmp(pw->pw_shell, "") != 0) &&
1006 (strcmp(pw->pw_shell, SHELL) != 0)) {
1007 mail(pw->pw_name, BADSHELL, ERR_CANTEXECAT);
1008 cron_unlink(pname);
1009 return;
1011 #endif
1012 if ((u = find_usr(pw->pw_name)) == NULL) {
1013 #ifdef DEBUG
1014 (void) fprintf(stderr, "new user (%s) with an at job = %s\n",
1015 pw->pw_name, name);
1016 #endif
1017 u = create_ulist(pw->pw_name, ATEVENT);
1018 u->home = xstrdup(pw->pw_dir);
1019 u->uid = pw->pw_uid;
1020 u->gid = pw->pw_gid;
1021 add_atevent(u, name, tim, jobtype);
1022 } else {
1023 u->uid = pw->pw_uid;
1024 u->gid = pw->pw_gid;
1025 free(u->home);
1026 u->home = xstrdup(pw->pw_dir);
1027 update_atevent(u, name, tim, jobtype);
1031 static void
1032 add_atevent(struct usr *u, char *job, time_t tim, int jobtype)
1034 struct event *e;
1036 e = xmalloc(sizeof (struct event));
1037 e->etype = jobtype;
1038 e->cmd = xmalloc(strlen(job) + 1);
1039 (void) strcpy(e->cmd, job);
1040 e->u = u;
1041 e->link = u->atevents;
1042 u->atevents = e;
1043 e->of.at.exists = TRUE;
1044 e->of.at.eventid = ecid++;
1045 if (tim < init_time) /* old job */
1046 e->time = init_time;
1047 else
1048 e->time = tim;
1049 #ifdef DEBUG
1050 (void) fprintf(stderr, "add_atevent: user=%s, job=%s, time=%ld\n",
1051 u->name, e->cmd, e->time);
1052 #endif
1053 if (el_add(e, e->time, e->of.at.eventid) < 0) {
1054 ignore_msg("add_atevent", "at", e);
1058 void
1059 update_atevent(struct usr *u, char *name, time_t tim, int jobtype)
1061 struct event *e;
1063 e = u->atevents;
1064 while (e != NULL) {
1065 if (strcmp(e->cmd, name) == 0) {
1066 e->of.at.exists = TRUE;
1067 break;
1068 } else {
1069 e = e->link;
1072 if (e == NULL) {
1073 #ifdef DEBUG
1074 (void) fprintf(stderr, "%s has a new at job = %s\n",
1075 u->name, name);
1076 #endif
1077 add_atevent(u, name, tim, jobtype);
1081 static char line[CTLINESIZE]; /* holds a line from a crontab file */
1082 static int cursor; /* cursor for the above line */
1084 static void
1085 readcron(struct usr *u, time_t reftime)
1088 * readcron reads in a crontab file for a user (u). The list of
1089 * events for user u is built, and u->events is made to point to
1090 * this list. Each event is also entered into the main event
1091 * list.
1093 FILE *cf; /* cf will be a user's crontab file */
1094 struct event *e;
1095 int start;
1096 unsigned int i;
1097 char namebuf[PATH_MAX];
1098 char *pname;
1099 struct shared *tz = NULL;
1100 struct shared *home = NULL;
1101 struct shared *shell = NULL;
1102 int lineno = 0;
1104 /* read the crontab file */
1105 cte_init(); /* Init error handling */
1106 if (cwd != CRON) {
1107 if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
1108 CRONDIR, u->name) >= sizeof (namebuf)) {
1109 return;
1111 pname = namebuf;
1112 } else {
1113 pname = u->name;
1115 if ((cf = fopen(pname, "r")) == NULL) {
1116 mail(u->name, NOREAD, ERR_UNIXERR);
1117 return;
1119 while (fgets(line, CTLINESIZE, cf) != NULL) {
1120 char *tmp;
1121 /* process a line of a crontab file */
1122 lineno++;
1123 if (cte_istoomany())
1124 break;
1125 cursor = 0;
1126 while (line[cursor] == ' ' || line[cursor] == '\t')
1127 cursor++;
1128 if (line[cursor] == '#' || line[cursor] == '\n')
1129 continue;
1131 if (strncmp(&line[cursor], ENV_TZ,
1132 strlen(ENV_TZ)) == 0) {
1133 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1134 *tmp = '\0';
1137 if (tz == NULL || strcmp(&line[cursor], get_obj(tz))) {
1138 rel_shared(tz);
1139 tz = create_shared_str(&line[cursor]);
1141 continue;
1144 if (strncmp(&line[cursor], ENV_HOME,
1145 strlen(ENV_HOME)) == 0) {
1146 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1147 *tmp = '\0';
1149 if (home == NULL ||
1150 strcmp(&line[cursor], get_obj(home))) {
1151 rel_shared(home);
1152 home = create_shared_str(
1153 &line[cursor + strlen(ENV_HOME)]);
1155 continue;
1158 if (strncmp(&line[cursor], ENV_SHELL,
1159 strlen(ENV_SHELL)) == 0) {
1160 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1161 *tmp = '\0';
1163 if (shell == NULL ||
1164 strcmp(&line[cursor], get_obj(shell))) {
1165 rel_shared(shell);
1166 shell = create_shared_str(&line[cursor]);
1168 continue;
1171 e = xmalloc(sizeof (struct event));
1172 e->etype = CRONEVENT;
1173 if (!(((e->of.ct.minute = next_field(0, 59)) != NULL) &&
1174 ((e->of.ct.hour = next_field(0, 23)) != NULL) &&
1175 ((e->of.ct.daymon = next_field(1, 31)) != NULL) &&
1176 ((e->of.ct.month = next_field(1, 12)) != NULL) &&
1177 ((e->of.ct.dayweek = next_field(0, 6)) != NULL))) {
1178 free(e);
1179 cte_add(lineno, line);
1180 continue;
1182 while (line[cursor] == ' ' || line[cursor] == '\t')
1183 cursor++;
1184 if (line[cursor] == '\n' || line[cursor] == '\0')
1185 continue;
1186 /* get the command to execute */
1187 start = cursor;
1188 again:
1189 while ((line[cursor] != '%') &&
1190 (line[cursor] != '\n') &&
1191 (line[cursor] != '\0') &&
1192 (line[cursor] != '\\'))
1193 cursor++;
1194 if (line[cursor] == '\\') {
1195 cursor += 2;
1196 goto again;
1198 e->cmd = xmalloc(cursor-start + 1);
1199 (void) strncpy(e->cmd, line + start, cursor-start);
1200 e->cmd[cursor-start] = '\0';
1201 /* see if there is any standard input */
1202 if (line[cursor] == '%') {
1203 e->of.ct.input = xmalloc(strlen(line)-cursor + 1);
1204 (void) strcpy(e->of.ct.input, line + cursor + 1);
1205 for (i = 0; i < strlen(e->of.ct.input); i++) {
1206 if (e->of.ct.input[i] == '%')
1207 e->of.ct.input[i] = '\n';
1209 } else {
1210 e->of.ct.input = NULL;
1212 /* set the timezone of this entry */
1213 e->of.ct.tz = dup_shared(tz);
1214 /* set the shell of this entry */
1215 e->of.ct.shell = dup_shared(shell);
1216 /* set the home of this entry */
1217 e->of.ct.home = dup_shared(home);
1218 /* have the event point to it's owner */
1219 e->u = u;
1220 /* insert this event at the front of this user's event list */
1221 e->link = u->ctevents;
1222 u->ctevents = e;
1223 /* set the time for the first occurance of this event */
1224 e->time = next_time(e, reftime);
1225 /* finally, add this event to the main event list */
1226 switch (el_add(e, e->time, u->ctid)) {
1227 case -1:
1228 ignore_msg("readcron", "cron", e);
1229 break;
1230 case -2: /* event time lower than init time */
1231 reset_needed = 1;
1232 break;
1234 cte_valid();
1235 #ifdef DEBUG
1236 cftime(timebuf, "%+", &e->time);
1237 (void) fprintf(stderr, "inserting cron event %s at %ld (%s)\n",
1238 e->cmd, e->time, timebuf);
1239 #endif
1241 cte_sendmail(u->name); /* mail errors if any to user */
1242 (void) fclose(cf);
1243 rel_shared(tz);
1244 rel_shared(shell);
1245 rel_shared(home);
1249 * Below are the functions for handling of errors in crontabs. Concept is to
1250 * collect faulty lines and send one email at the end of the crontab
1251 * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
1252 * of crontab is aborted. Otherwise reading of crontab is continued to the end
1253 * of the file but no further error logging appears.
1255 static void
1256 cte_init()
1258 if (cte_text == NULL)
1259 cte_text = xmalloc(MAILBUFLEN);
1260 (void) strlcpy(cte_text, cte_intro, MAILBUFLEN);
1261 cte_lp = cte_text + sizeof (cte_intro) - 1;
1262 cte_free = MAILBINITFREE;
1263 cte_nvalid = 0;
1266 static void
1267 cte_add(int lineno, char *ctline)
1269 int len;
1270 char *p;
1272 if (cte_free >= LINELIMIT) {
1273 (void) sprintf(cte_lp, "%4d: ", lineno);
1274 (void) strlcat(cte_lp, ctline, LINELIMIT - 1);
1275 len = strlen(cte_lp);
1276 if (cte_lp[len - 1] != '\n') {
1277 cte_lp[len++] = '\n';
1278 cte_lp[len] = '\0';
1280 for (p = cte_lp; *p; p++) {
1281 if (isprint(*p) || *p == '\n' || *p == '\t')
1282 continue;
1283 *p = '.';
1285 cte_lp += len;
1286 cte_free -= len;
1287 if (cte_free < LINELIMIT) {
1288 size_t buflen = MAILBUFLEN - (cte_lp - cte_text);
1289 (void) strlcpy(cte_lp, cte_trail1, buflen);
1290 if (cte_nvalid == 0)
1291 (void) strlcat(cte_lp, cte_trail2, buflen);
1296 static void
1297 cte_valid()
1299 cte_nvalid++;
1302 static int
1303 cte_istoomany()
1306 * Return TRUE only if all lines are faulty. So evaluation of
1307 * a crontab is not aborted if at least one valid line was found.
1309 return (cte_nvalid == 0 && cte_free < LINELIMIT);
1312 static void
1313 cte_sendmail(char *username)
1315 if (cte_free < MAILBINITFREE)
1316 mail(username, cte_text, ERR_CRONTABENT);
1320 * Send mail with error message to a user
1322 static void
1323 mail(char *usrname, char *mesg, int format)
1325 /* mail mails a user a message. */
1326 FILE *pipe;
1327 char *temp;
1328 struct passwd *ruser_ids;
1329 pid_t fork_val;
1330 int saveerrno = errno;
1331 struct utsname name;
1333 #ifdef TESTING
1334 return;
1335 #endif
1336 (void) uname(&name);
1337 if ((fork_val = fork()) == (pid_t)-1) {
1338 msg("cron cannot fork\n");
1339 return;
1341 if (fork_val == 0) {
1342 child_sigreset();
1343 contract_clear_template();
1344 if ((ruser_ids = getpwnam(usrname)) == NULL)
1345 exit(0);
1346 (void) setuid(ruser_ids->pw_uid);
1347 temp = xmalloc(strlen(MAIL) + strlen(usrname) + 2);
1348 (void) sprintf(temp, "%s %s", MAIL, usrname);
1349 pipe = popen(temp, "w");
1350 if (pipe != NULL) {
1351 (void) fprintf(pipe, "To: %s\n", usrname);
1352 switch (format) {
1353 case ERR_CRONTABENT:
1354 (void) fprintf(pipe, CRONTABERR);
1355 (void) fprintf(pipe, "Your \"crontab\" on %s\n",
1356 name.nodename);
1357 (void) fprintf(pipe, mesg);
1358 (void) fprintf(pipe,
1359 "\nEntries or crontab have been ignored\n");
1360 break;
1361 case ERR_UNIXERR:
1362 (void) fprintf(pipe, "Subject: %s\n\n", mesg);
1363 (void) fprintf(pipe,
1364 "The error on %s was \"%s\"\n",
1365 name.nodename, errmsg(saveerrno));
1366 break;
1368 case ERR_CANTEXECCRON:
1369 (void) fprintf(pipe,
1370 "Subject: Couldn't run your \"cron\" job\n\n");
1371 (void) fprintf(pipe,
1372 "Your \"cron\" job on %s ", name.nodename);
1373 (void) fprintf(pipe, "couldn't be run\n");
1374 (void) fprintf(pipe, "%s\n", mesg);
1375 (void) fprintf(pipe,
1376 "The error was \"%s\"\n", errmsg(saveerrno));
1377 break;
1379 case ERR_CANTEXECAT:
1380 (void) fprintf(pipe,
1381 "Subject: Couldn't run your \"at\" job\n\n");
1382 (void) fprintf(pipe, "Your \"at\" job on %s ",
1383 name.nodename);
1384 (void) fprintf(pipe, "couldn't be run\n");
1385 (void) fprintf(pipe, "%s\n", mesg);
1386 (void) fprintf(pipe,
1387 "The error was \"%s\"\n", errmsg(saveerrno));
1388 break;
1390 default:
1391 break;
1393 (void) pclose(pipe);
1395 free(temp);
1396 exit(0);
1399 contract_abandon_latest(fork_val);
1401 if (cron_pid == getpid()) {
1402 miscpid_insert(fork_val);
1406 static char *
1407 next_field(int lower, int upper)
1410 * next_field returns a pointer to a string which holds the next
1411 * field of a line of a crontab file.
1412 * if (numbers in this field are out of range (lower..upper),
1413 * or there is a syntax error) then
1414 * NULL is returned, and a mail message is sent to the
1415 * user telling them which line the error was in.
1418 char *s;
1419 int num, num2, start;
1421 while ((line[cursor] == ' ') || (line[cursor] == '\t'))
1422 cursor++;
1423 start = cursor;
1424 if (line[cursor] == '\0') {
1425 return (NULL);
1427 if (line[cursor] == '*') {
1428 cursor++;
1429 if ((line[cursor] != ' ') && (line[cursor] != '\t'))
1430 return (NULL);
1431 s = xmalloc(2);
1432 (void) strcpy(s, "*");
1433 return (s);
1435 for (;;) {
1436 if (!isdigit(line[cursor]))
1437 return (NULL);
1438 num = 0;
1439 do {
1440 num = num*10 + (line[cursor]-'0');
1441 } while (isdigit(line[++cursor]));
1442 if ((num < lower) || (num > upper))
1443 return (NULL);
1444 if (line[cursor] == '-') {
1445 if (!isdigit(line[++cursor]))
1446 return (NULL);
1447 num2 = 0;
1448 do {
1449 num2 = num2*10 + (line[cursor]-'0');
1450 } while (isdigit(line[++cursor]));
1451 if ((num2 < lower) || (num2 > upper))
1452 return (NULL);
1454 if ((line[cursor] == ' ') || (line[cursor] == '\t'))
1455 break;
1456 if (line[cursor] == '\0')
1457 return (NULL);
1458 if (line[cursor++] != ',')
1459 return (NULL);
1461 s = xmalloc(cursor-start + 1);
1462 (void) strncpy(s, line + start, cursor-start);
1463 s[cursor-start] = '\0';
1464 return (s);
1467 #define tm_cmp(t1, t2) (\
1468 (t1)->tm_year == (t2)->tm_year && \
1469 (t1)->tm_mon == (t2)->tm_mon && \
1470 (t1)->tm_mday == (t2)->tm_mday && \
1471 (t1)->tm_hour == (t2)->tm_hour && \
1472 (t1)->tm_min == (t2)->tm_min)
1474 #define tm_setup(tp, yr, mon, dy, hr, min, dst) \
1475 (tp)->tm_year = yr; \
1476 (tp)->tm_mon = mon; \
1477 (tp)->tm_mday = dy; \
1478 (tp)->tm_hour = hr; \
1479 (tp)->tm_min = min; \
1480 (tp)->tm_isdst = dst; \
1481 (tp)->tm_sec = 0; \
1482 (tp)->tm_wday = 0; \
1483 (tp)->tm_yday = 0;
1486 * modification for bugid 1104537. the second argument to next_time is
1487 * now the value of time(2) to be used. if this is 0, then use the
1488 * current time. otherwise, the second argument is the time from which to
1489 * calculate things. this is useful to correct situations where you've
1490 * gone backwards in time (I.e. the system's internal clock is correcting
1491 * itself backwards).
1496 static time_t
1497 tz_next_time(struct event *e, time_t tflag)
1500 * returns the integer time for the next occurance of event e.
1501 * the following fields have ranges as indicated:
1502 * PRGM | min hour day of month mon day of week
1503 * ------|-------------------------------------------------------
1504 * cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday)
1505 * time | 0-59 0-23 1-31 0-11 0-6 (0=sunday)
1506 * NOTE: this routine is hard to understand.
1509 struct tm *tm, ref_tm, tmp, tmp1, tmp2;
1510 int tm_mon, tm_mday, tm_wday, wday, m, min, h, hr, carry, day, days;
1511 int d1, day1, carry1, d2, day2, carry2, daysahead, mon, yr, db, wd;
1512 int today;
1513 time_t t, ref_t, t1, t2, zone_start;
1514 int fallback;
1515 extern int days_btwn(int, int, int, int, int, int);
1517 if (tflag == 0) {
1518 t = time(NULL); /* original way of doing things */
1519 } else {
1520 t = tflag;
1523 tm = &ref_tm; /* use a local variable and call localtime_r() */
1524 ref_t = t; /* keep a copy of the reference time */
1526 recalc:
1527 fallback = 0;
1529 (void) localtime_r(&t, tm);
1531 if (daylight) {
1532 tmp = *tm;
1533 tmp.tm_isdst = (tm->tm_isdst > 0 ? 0 : 1);
1534 t1 = xmktime(&tmp);
1536 * see if we will have timezone switch over, and clock will
1537 * fall back. zone_start will hold the time when it happens
1538 * (ie time of PST -> PDT switch over).
1540 if (tm->tm_isdst != tmp.tm_isdst &&
1541 (t1 - t) == (timezone - altzone) &&
1542 tm_cmp(tm, &tmp)) {
1543 zone_start = get_switching_time(tmp.tm_isdst, t);
1544 fallback = 1;
1548 tm_mon = next_ge(tm->tm_mon + 1, e->of.ct.month) - 1; /* 0-11 */
1549 tm_mday = next_ge(tm->tm_mday, e->of.ct.daymon); /* 1-31 */
1550 tm_wday = next_ge(tm->tm_wday, e->of.ct.dayweek); /* 0-6 */
1551 today = TRUE;
1552 if ((strcmp(e->of.ct.daymon, "*") == 0 && tm->tm_wday != tm_wday) ||
1553 (strcmp(e->of.ct.dayweek, "*") == 0 && tm->tm_mday != tm_mday) ||
1554 (tm->tm_mday != tm_mday && tm->tm_wday != tm_wday) ||
1555 (tm->tm_mon != tm_mon)) {
1556 today = FALSE;
1558 m = tm->tm_min + (t == ref_t ? 1 : 0);
1559 if ((tm->tm_hour + 1) <= next_ge(tm->tm_hour, e->of.ct.hour)) {
1560 m = 0;
1562 min = next_ge(m%60, e->of.ct.minute);
1563 carry = (min < m) ? 1 : 0;
1564 h = tm->tm_hour + carry;
1565 hr = next_ge(h%24, e->of.ct.hour);
1566 carry = (hr < h) ? 1 : 0;
1568 if (carry == 0 && today) {
1569 /* this event must occur today */
1570 tm_setup(&tmp, tm->tm_year, tm->tm_mon, tm->tm_mday,
1571 hr, min, tm->tm_isdst);
1572 tmp1 = tmp;
1573 if ((t1 = xmktime(&tmp1)) == (time_t)-1) {
1574 return (0);
1576 if (daylight && tmp.tm_isdst != tmp1.tm_isdst) {
1577 /* In case we are falling back */
1578 if (fallback) {
1579 /* we may need to run the job once more. */
1580 t = zone_start;
1581 goto recalc;
1585 * In case we are not in falling back period,
1586 * calculate the time assuming the DST. If the
1587 * date/time is not altered by mktime, it is the
1588 * time to execute the job.
1590 tmp2 = tmp;
1591 tmp2.tm_isdst = tmp1.tm_isdst;
1592 if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
1593 return (0);
1595 if (tmp1.tm_isdst == tmp2.tm_isdst &&
1596 tm_cmp(&tmp, &tmp2)) {
1598 * We got a valid time.
1600 return (t1);
1601 } else {
1603 * If the date does not match even if
1604 * we assume the alternate timezone, then
1605 * it must be the invalid time. eg
1606 * 2am while switching 1:59am to 3am.
1607 * t1 should point the time before the
1608 * switching over as we've calculate the
1609 * time with assuming alternate zone.
1611 if (tmp1.tm_isdst != tmp2.tm_isdst) {
1612 t = get_switching_time(tmp1.tm_isdst,
1613 t1);
1614 } else {
1615 /* does this really happen? */
1616 t = get_switching_time(tmp1.tm_isdst,
1617 t1 - abs(timezone - altzone));
1619 if (t == (time_t)-1) {
1620 return (0);
1623 goto recalc;
1625 if (tm_cmp(&tmp, &tmp1)) {
1626 /* got valid time */
1627 return (t1);
1628 } else {
1630 * This should never happen, but just in
1631 * case, we fall back to the old code.
1633 if (tm->tm_min > min) {
1634 t += (time_t)(hr-tm->tm_hour-1) * HOUR +
1635 (time_t)(60-tm->tm_min + min) * MINUTE;
1636 } else {
1637 t += (time_t)(hr-tm->tm_hour) * HOUR +
1638 (time_t)(min-tm->tm_min) * MINUTE;
1640 t1 = t;
1641 t -= (time_t)tm->tm_sec;
1642 (void) localtime_r(&t, &tmp);
1643 if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
1644 t -= (timezone - altzone);
1645 return ((t <= ref_t) ? t1 : t);
1650 * Job won't run today, however if we have a switch over within
1651 * one hour and we will have one hour time drifting back in this
1652 * period, we may need to run the job one more time if the job was
1653 * set to run on this hour of clock.
1655 if (fallback) {
1656 t = zone_start;
1657 goto recalc;
1660 min = next_ge(0, e->of.ct.minute);
1661 hr = next_ge(0, e->of.ct.hour);
1664 * calculate the date of the next occurance of this event, which
1665 * will be on a different day than the current
1668 /* check monthly day specification */
1669 d1 = tm->tm_mday + 1;
1670 day1 = next_ge((d1-1)%days_in_mon(tm->tm_mon, tm->tm_year) + 1,
1671 e->of.ct.daymon);
1672 carry1 = (day1 < d1) ? 1 : 0;
1674 /* check weekly day specification */
1675 d2 = tm->tm_wday + 1;
1676 wday = next_ge(d2%7, e->of.ct.dayweek);
1677 if (wday < d2)
1678 daysahead = 7 - d2 + wday;
1679 else
1680 daysahead = wday - d2;
1681 day2 = (d1 + daysahead-1)%days_in_mon(tm->tm_mon, tm->tm_year) + 1;
1682 carry2 = (day2 < d1) ? 1 : 0;
1685 * based on their respective specifications, day1, and day2 give
1686 * the day of the month for the next occurance of this event.
1688 if ((strcmp(e->of.ct.daymon, "*") == 0) &&
1689 (strcmp(e->of.ct.dayweek, "*") != 0)) {
1690 day1 = day2;
1691 carry1 = carry2;
1693 if ((strcmp(e->of.ct.daymon, "*") != 0) &&
1694 (strcmp(e->of.ct.dayweek, "*") == 0)) {
1695 day2 = day1;
1696 carry2 = carry1;
1699 yr = tm->tm_year;
1700 if ((carry1 && carry2) || (tm->tm_mon != tm_mon)) {
1701 /* event does not occur in this month */
1702 m = tm->tm_mon + 1;
1703 mon = next_ge(m%12 + 1, e->of.ct.month) - 1; /* 0..11 */
1704 carry = (mon < m) ? 1 : 0;
1705 yr += carry;
1706 /* recompute day1 and day2 */
1707 day1 = next_ge(1, e->of.ct.daymon);
1708 db = days_btwn(tm->tm_mon, tm->tm_mday, tm->tm_year, mon,
1709 1, yr) + 1;
1710 wd = (tm->tm_wday + db)%7;
1711 /* wd is the day of the week of the first of month mon */
1712 wday = next_ge(wd, e->of.ct.dayweek);
1713 if (wday < wd)
1714 day2 = 1 + 7 - wd + wday;
1715 else
1716 day2 = 1 + wday - wd;
1717 if ((strcmp(e->of.ct.daymon, "*") != 0) &&
1718 (strcmp(e->of.ct.dayweek, "*") == 0))
1719 day2 = day1;
1720 if ((strcmp(e->of.ct.daymon, "*") == 0) &&
1721 (strcmp(e->of.ct.dayweek, "*") != 0))
1722 day1 = day2;
1723 day = (day1 < day2) ? day1 : day2;
1724 } else { /* event occurs in this month */
1725 mon = tm->tm_mon;
1726 if (!carry1 && !carry2)
1727 day = (day1 < day2) ? day1 : day2;
1728 else if (!carry1)
1729 day = day1;
1730 else
1731 day = day2;
1735 * now that we have the min, hr, day, mon, yr of the next event,
1736 * figure out what time that turns out to be.
1738 tm_setup(&tmp, yr, mon, day, hr, min, -1);
1739 tmp2 = tmp;
1740 if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
1741 return (0);
1743 if (tm_cmp(&tmp, &tmp2)) {
1745 * mktime returns clock for the current time zone. If the
1746 * target date was in fallback period, it needs to be adjusted
1747 * to the time comes first.
1748 * Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
1749 * mktime returns the time in PST, but 1:30am in PDT comes
1750 * first. So reverse the tm_isdst, and see if we have such
1751 * time/date.
1753 if (daylight) {
1754 int dst = tmp2.tm_isdst;
1756 tmp2 = tmp;
1757 tmp2.tm_isdst = (dst > 0 ? 0 : 1);
1758 if ((t2 = xmktime(&tmp2)) == (time_t)-1) {
1759 return (0);
1761 if (tm_cmp(&tmp, &tmp2)) {
1763 * same time/date found in the opposite zone.
1764 * check the clock to see which comes early.
1766 if (t2 > ref_t && t2 < t1) {
1767 t1 = t2;
1771 return (t1);
1772 } else {
1774 * mktime has set different time/date for the given date.
1775 * This means that the next job is scheduled to be run on the
1776 * invalid time. There are three possible invalid date/time.
1777 * 1. Non existing day of the month. such as April 31th.
1778 * 2. Feb 29th in the non-leap year.
1779 * 3. Time gap during the DST switch over.
1781 d1 = days_in_mon(mon, yr);
1782 if ((mon != 1 && day > d1) || (mon == 1 && day > 29)) {
1784 * see if we have got a specific date which
1785 * is invalid.
1787 if (strcmp(e->of.ct.dayweek, "*") == 0 &&
1788 mon == (next_ge((mon + 1)%12 + 1,
1789 e->of.ct.month) - 1) &&
1790 day <= next_ge(1, e->of.ct.daymon)) {
1791 /* job never run */
1792 return (0);
1795 * Since the day has gone invalid, we need to go to
1796 * next month, and recalcuate the first occurrence.
1797 * eg the cron tab such as:
1798 * 0 0 1,15,31 1,2,3,4,5 * /usr/bin....
1799 * 2/31 is invalid, so the next job is 3/1.
1801 tmp2 = tmp;
1802 tmp2.tm_min = 0;
1803 tmp2.tm_hour = 0;
1804 tmp2.tm_mday = 1; /* 1st day of the month */
1805 if (mon == 11) {
1806 tmp2.tm_mon = 0;
1807 tmp2.tm_year = yr + 1;
1808 } else {
1809 tmp2.tm_mon = mon + 1;
1811 if ((t = xmktime(&tmp2)) == (time_t)-1) {
1812 return (0);
1814 } else if (mon == 1 && day > d1) {
1816 * ie 29th in the non-leap year. Forwarding the
1817 * clock to Feb 29th 00:00 (March 1st), and recalculate
1818 * the next time.
1820 tmp2 = tmp;
1821 tmp2.tm_min = 0;
1822 tmp2.tm_hour = 0;
1823 if ((t = xmktime(&tmp2)) == (time_t)-1) {
1824 return (0);
1826 } else if (daylight) {
1828 * Non existing time, eg 2am PST during summer time
1829 * switch.
1830 * We need to get the correct isdst which we are
1831 * swithing to, by adding time difference to make sure
1832 * that t2 is in the zone being switched.
1834 t2 = t1;
1835 t2 += abs(timezone - altzone);
1836 (void) localtime_r(&t2, &tmp2);
1837 zone_start = get_switching_time(tmp2.tm_isdst,
1838 t1 - abs(timezone - altzone));
1839 if (zone_start == (time_t)-1) {
1840 return (0);
1842 t = zone_start;
1843 } else {
1845 * This should never happen, but fall back to the
1846 * old code.
1848 days = days_btwn(tm->tm_mon,
1849 tm->tm_mday, tm->tm_year, mon, day, yr);
1850 t += (time_t)(23-tm->tm_hour)*HOUR
1851 + (time_t)(60-tm->tm_min)*MINUTE
1852 + (time_t)hr*HOUR + (time_t)min*MINUTE
1853 + (time_t)days*DAY;
1854 t1 = t;
1855 t -= (time_t)tm->tm_sec;
1856 (void) localtime_r(&t, &tmp);
1857 if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
1858 t -= (timezone - altzone);
1859 return (t <= ref_t ? t1 : t);
1861 goto recalc;
1863 /*NOTREACHED*/
1866 static time_t
1867 next_time(struct event *e, time_t tflag)
1869 if (e->of.ct.tz != NULL) {
1870 time_t ret;
1872 (void) putenv((char *)get_obj(e->of.ct.tz));
1873 tzset();
1874 ret = tz_next_time(e, tflag);
1875 (void) putenv(tzone);
1876 tzset();
1877 return (ret);
1878 } else {
1879 return (tz_next_time(e, tflag));
1884 * This returns TOD in time_t that zone switch will happen, and this
1885 * will be called when clock fallback is about to happen.
1886 * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
1887 * will fall back to 1:00 PDT. So this function will be called only
1888 * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
1889 * First goes through the common time differences to see if zone
1890 * switch happens at those minutes later. If not, check every minutes
1891 * until 6 hours ahead see if it happens(We might have 45minutes
1892 * fallback).
1894 static time_t
1895 get_switching_time(int to_dst, time_t t_ref)
1897 time_t t, t1;
1898 struct tm tmp, tmp1;
1899 int hints[] = { 60, 120, 30, 90, 0}; /* minutes */
1900 int i;
1902 (void) localtime_r(&t_ref, &tmp);
1903 tmp1 = tmp;
1904 tmp1.tm_sec = 0;
1905 tmp1.tm_min = 0;
1906 if ((t = xmktime(&tmp1)) == (time_t)-1)
1907 return ((time_t)-1);
1909 /* fast path */
1910 for (i = 0; hints[i] != 0; i++) {
1911 t1 = t + hints[i] * 60;
1912 (void) localtime_r(&t1, &tmp1);
1913 if (tmp1.tm_isdst == to_dst) {
1914 t1--;
1915 (void) localtime_r(&t1, &tmp1);
1916 if (tmp1.tm_isdst != to_dst) {
1917 return (t1 + 1);
1922 /* ugly, but don't know other than this. */
1923 tmp1 = tmp;
1924 tmp1.tm_sec = 0;
1925 if ((t = xmktime(&tmp1)) == (time_t)-1)
1926 return ((time_t)-1);
1927 while (t < (t_ref + 6*60*60)) { /* 6 hours should be enough */
1928 t += 60; /* at least one minute, I assume */
1929 (void) localtime_r(&t, &tmp);
1930 if (tmp.tm_isdst == to_dst)
1931 return (t);
1933 return ((time_t)-1);
1936 static time_t
1937 xmktime(struct tm *tmp)
1939 time_t ret;
1941 if ((ret = mktime(tmp)) == (time_t)-1) {
1942 if (errno == EOVERFLOW) {
1943 return ((time_t)-1);
1945 crabort("internal error: mktime failed",
1946 REMOVE_FIFO|CONSOLE_MSG);
1948 return (ret);
1951 #define DUMMY 100
1953 static int
1954 next_ge(int current, char *list)
1957 * list is a character field as in a crontab file;
1958 * for example: "40, 20, 50-10"
1959 * next_ge returns the next number in the list that is
1960 * greater than or equal to current. if no numbers of list
1961 * are >= current, the smallest element of list is returned.
1962 * NOTE: current must be in the appropriate range.
1965 char *ptr;
1966 int n, n2, min, min_gt;
1968 if (strcmp(list, "*") == 0)
1969 return (current);
1970 ptr = list;
1971 min = DUMMY;
1972 min_gt = DUMMY;
1973 for (;;) {
1974 if ((n = (int)num(&ptr)) == current)
1975 return (current);
1976 if (n < min)
1977 min = n;
1978 if ((n > current) && (n < min_gt))
1979 min_gt = n;
1980 if (*ptr == '-') {
1981 ptr++;
1982 if ((n2 = (int)num(&ptr)) > n) {
1983 if ((current > n) && (current <= n2))
1984 return (current);
1985 } else { /* range that wraps around */
1986 if (current > n)
1987 return (current);
1988 if (current <= n2)
1989 return (current);
1992 if (*ptr == '\0')
1993 break;
1994 ptr += 1;
1996 if (min_gt != DUMMY)
1997 return (min_gt);
1998 else
1999 return (min);
2002 static void
2003 free_if_unused(struct usr *u)
2005 struct usr *cur, *prev;
2007 * To make sure a usr structure is idle we must check that
2008 * there are no at jobs queued for the user; the user does
2009 * not have a crontab, and also that there are no running at
2010 * or cron jobs (since the runinfo structure also has a
2011 * pointer to the usr structure).
2013 if (!u->ctexists && u->atevents == NULL &&
2014 u->cruncnt == 0 && u->aruncnt == 0) {
2015 #ifdef DEBUG
2016 (void) fprintf(stderr, "%s removed from usr list\n", u->name);
2017 #endif
2018 for (cur = uhead, prev = NULL;
2019 cur != u;
2020 prev = cur, cur = cur->nextusr) {
2021 if (cur == NULL) {
2022 return;
2026 if (prev == NULL)
2027 uhead = u->nextusr;
2028 else
2029 prev->nextusr = u->nextusr;
2030 free(u->name);
2031 free(u->home);
2032 free(u);
2036 static void
2037 del_atjob(char *name, char *usrname)
2040 struct event *e, *eprev;
2041 struct usr *u;
2043 if ((u = find_usr(usrname)) == NULL)
2044 return;
2045 e = u->atevents;
2046 eprev = NULL;
2047 while (e != NULL) {
2048 if (strcmp(name, e->cmd) == 0) {
2049 if (next_event == e)
2050 next_event = NULL;
2051 if (eprev == NULL)
2052 u->atevents = e->link;
2053 else
2054 eprev->link = e->link;
2055 el_remove(e->of.at.eventid, 1);
2056 free(e->cmd);
2057 free(e);
2058 break;
2059 } else {
2060 eprev = e;
2061 e = e->link;
2065 free_if_unused(u);
2068 static void
2069 del_ctab(char *name)
2072 struct usr *u;
2074 if ((u = find_usr(name)) == NULL)
2075 return;
2076 rm_ctevents(u);
2077 el_remove(u->ctid, 0);
2078 u->ctid = 0;
2079 u->ctexists = 0;
2081 free_if_unused(u);
2084 static void
2085 rm_ctevents(struct usr *u)
2087 struct event *e2, *e3;
2090 * see if the next event (to be run by cron) is a cronevent
2091 * owned by this user.
2094 if ((next_event != NULL) &&
2095 (next_event->etype == CRONEVENT) &&
2096 (next_event->u == u)) {
2097 next_event = NULL;
2099 e2 = u->ctevents;
2100 while (e2 != NULL) {
2101 free(e2->cmd);
2102 rel_shared(e2->of.ct.tz);
2103 rel_shared(e2->of.ct.shell);
2104 rel_shared(e2->of.ct.home);
2105 free(e2->of.ct.minute);
2106 free(e2->of.ct.hour);
2107 free(e2->of.ct.daymon);
2108 free(e2->of.ct.month);
2109 free(e2->of.ct.dayweek);
2110 free(e2->of.ct.input);
2111 e3 = e2->link;
2112 free(e2);
2113 e2 = e3;
2115 u->ctevents = NULL;
2119 static struct usr *
2120 find_usr(char *uname)
2122 struct usr *u;
2124 u = uhead;
2125 while (u != NULL) {
2126 if (strcmp(u->name, uname) == 0)
2127 return (u);
2128 u = u->nextusr;
2130 return (NULL);
2134 * Execute cron command or at/batch job.
2135 * If ever a premature return is added to this function pay attention to
2136 * free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
2138 static int
2139 ex(struct event *e)
2141 int r;
2142 int fd;
2143 pid_t rfork;
2144 FILE *atcmdfp;
2145 char mailvar[4];
2146 char *at_cmdfile = NULL;
2147 struct stat buf;
2148 struct queue *qp;
2149 struct runinfo *rp;
2150 struct project proj, *pproj = NULL;
2151 union {
2152 struct {
2153 char buf[PROJECT_BUFSZ];
2154 char buf2[PROJECT_BUFSZ];
2155 } p;
2156 char error[CANT_STR_LEN + PATH_MAX];
2157 } bufs;
2158 char *tmpfile;
2159 FILE *fptr;
2160 time_t dhltime;
2161 projid_t projid;
2162 int projflag = 0;
2163 char *home;
2164 char *sh;
2166 qp = &qt[e->etype]; /* set pointer to queue defs */
2167 if (qp->nrun >= qp->njob) {
2168 msg("%c queue max run limit reached", e->etype + 'a');
2169 resched(qp->nwait);
2170 return (0);
2173 rp = rinfo_get(0); /* allocating a new runinfo struct */
2176 * the tempnam() function uses malloc(3C) to allocate space for the
2177 * constructed file name, and returns a pointer to this area, which
2178 * is assigned to rp->outfile. Here rp->outfile is not overwritten.
2181 rp->outfile = tempnam(TMPDIR, PFX);
2182 rp->jobtype = e->etype;
2183 if (e->etype == CRONEVENT) {
2184 rp->jobname = xmalloc(strlen(e->cmd) + 1);
2185 (void) strcpy(rp->jobname, e->cmd);
2186 /* "cron" jobs only produce mail if there's output */
2187 rp->mailwhendone = 0;
2188 } else {
2189 at_cmdfile = xmalloc(strlen(ATDIR) + strlen(e->cmd) + 2);
2190 (void) sprintf(at_cmdfile, "%s/%s", ATDIR, e->cmd);
2191 if ((atcmdfp = fopen(at_cmdfile, "r")) == NULL) {
2192 if (errno == ENAMETOOLONG) {
2193 if (chdir(ATDIR) == 0)
2194 cron_unlink(e->cmd);
2195 } else {
2196 cron_unlink(at_cmdfile);
2198 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECAT);
2199 free(at_cmdfile);
2200 rinfo_free(rp);
2201 return (0);
2203 rp->jobname = xmalloc(strlen(at_cmdfile) + 1);
2204 (void) strcpy(rp->jobname, at_cmdfile);
2207 * Skip over the first two lines.
2209 (void) fscanf(atcmdfp, "%*[^\n]\n");
2210 (void) fscanf(atcmdfp, "%*[^\n]\n");
2211 if (fscanf(atcmdfp, ": notify by mail: %3s%*[^\n]\n",
2212 mailvar) == 1) {
2214 * Check to see if we should always send mail
2215 * to the owner.
2217 rp->mailwhendone = (strcmp(mailvar, "yes") == 0);
2218 } else {
2219 rp->mailwhendone = 0;
2222 if (fscanf(atcmdfp, "\n: project: %d\n", &projid) == 1) {
2223 projflag = 1;
2225 (void) fclose(atcmdfp);
2229 * we make sure that the system time
2230 * hasn't drifted backwards. if it has, el_add() is now
2231 * called, to make sure that the event queue is back in order,
2232 * and we set the delayed flag. cron will pick up the request
2233 * later on at the proper time.
2235 dhltime = time(NULL);
2236 if ((dhltime - e->time) < 0) {
2237 msg("clock time drifted backwards!\n");
2238 if (next_event->etype == CRONEVENT) {
2239 msg("correcting cron event\n");
2240 next_event->time = next_time(next_event, dhltime);
2241 switch (el_add(next_event, next_event->time,
2242 (next_event->u)->ctid)) {
2243 case -1:
2244 ignore_msg("ex", "cron", next_event);
2245 break;
2246 case -2: /* event time lower than init time */
2247 reset_needed = 1;
2248 break;
2250 } else { /* etype == ATEVENT */
2251 msg("correcting batch event\n");
2252 if (el_add(next_event, next_event->time,
2253 next_event->of.at.eventid) < 0) {
2254 ignore_msg("ex", "at", next_event);
2257 delayed++;
2258 t_old = time(NULL);
2259 free(at_cmdfile);
2260 rinfo_free(rp);
2261 return (0);
2264 if ((rfork = fork()) == (pid_t)-1) {
2265 reap_child();
2266 if ((rfork = fork()) == (pid_t)-1) {
2267 msg("cannot fork");
2268 free(at_cmdfile);
2269 rinfo_free(rp);
2270 resched(60);
2271 (void) sleep(30);
2272 return (0);
2275 if (rfork) { /* parent process */
2276 contract_abandon_latest(rfork);
2278 ++qp->nrun;
2279 rp->pid = rfork;
2280 rp->que = e->etype;
2281 if (e->etype != CRONEVENT)
2282 (e->u)->aruncnt++;
2283 else
2284 (e->u)->cruncnt++;
2285 rp->rusr = (e->u);
2286 logit(BCHAR, rp, 0);
2287 free(at_cmdfile);
2289 return (0);
2292 child_sigreset();
2293 contract_clear_template();
2295 if (e->etype != CRONEVENT) {
2296 /* open jobfile as stdin to shell */
2297 if (stat(at_cmdfile, &buf)) {
2298 if (errno == ENAMETOOLONG) {
2299 if (chdir(ATDIR) == 0)
2300 cron_unlink(e->cmd);
2301 } else
2302 cron_unlink(at_cmdfile);
2303 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2304 exit(1);
2306 if (!(buf.st_mode&ISUID)) {
2308 * if setuid bit off, original owner has
2309 * given this file to someone else
2311 cron_unlink(at_cmdfile);
2312 exit(1);
2314 if ((fd = open(at_cmdfile, O_RDONLY)) == -1) {
2315 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2316 cron_unlink(at_cmdfile);
2317 exit(1);
2319 if (fd != 0) {
2320 (void) dup2(fd, 0);
2321 (void) close(fd);
2324 * retrieve the project id of the at job and convert it
2325 * to a project name. fail if it's not a valid project
2326 * or if the user isn't a member of the project.
2328 if (projflag == 1) {
2329 if ((pproj = getprojbyid(projid, &proj,
2330 (void *)&bufs.p.buf,
2331 sizeof (bufs.p.buf))) == NULL ||
2332 !inproj(e->u->name, pproj->pj_name,
2333 bufs.p.buf2, sizeof (bufs.p.buf2))) {
2334 cron_unlink(at_cmdfile);
2335 mail((e->u)->name, BADPROJID, ERR_CANTEXECAT);
2336 exit(1);
2342 * Put process in a new session, and create a new task.
2344 if (setsid() < 0) {
2345 msg("setsid failed with errno = %d. job failed (%s)"
2346 " for user %s", errno, e->cmd, e->u->name);
2347 if (e->etype != CRONEVENT)
2348 cron_unlink(at_cmdfile);
2349 exit(1);
2353 * set correct user identification and check their account
2355 r = set_user_cred(e->u, pproj);
2356 if (r == VUC_EXPIRED) {
2357 msg("user (%s) account is expired", e->u->name);
2358 clean_out_user(e->u);
2359 exit(1);
2361 if (r == VUC_NEW_AUTH) {
2362 msg("user (%s) password has expired", e->u->name);
2363 clean_out_user(e->u);
2364 exit(1);
2366 if (r != VUC_OK) {
2367 msg("bad user (%s)", e->u->name);
2368 clean_out_user(e->u);
2369 exit(1);
2372 * check user and initialize the supplementary group access list.
2373 * bugid 1230784: deleted from parent to avoid cron hang. Now
2374 * only child handles the call.
2377 if (verify_user_cred(e->u) != VUC_OK ||
2378 setgid(e->u->gid) == -1 ||
2379 initgroups(e->u->name, e->u->gid) == -1) {
2380 msg("bad user (%s) or setgid failed (%s)",
2381 e->u->name, e->u->name);
2382 clean_out_user(e->u);
2383 exit(1);
2386 if ((e->u)->uid == 0) { /* set default path */
2387 /* path settable in defaults file */
2388 envinit[2] = supath;
2389 } else {
2390 envinit[2] = path;
2393 if (e->etype != CRONEVENT)
2394 cron_unlink(at_cmdfile);
2396 if (setuid(e->u->uid) == -1) {
2397 msg("setuid failed (%s)", e->u->name);
2398 clean_out_user(e->u);
2399 exit(1);
2402 if (e->etype == CRONEVENT) {
2403 /* check for standard input to command */
2404 if (e->of.ct.input != NULL) {
2405 if ((tmpfile = strdup(TMPINFILE)) == NULL) {
2406 mail((e->u)->name, MALLOCERR,
2407 ERR_CANTEXECCRON);
2408 exit(1);
2410 if ((fd = mkstemp(tmpfile)) == -1 ||
2411 (fptr = fdopen(fd, "w")) == NULL) {
2412 mail((e->u)->name, NOSTDIN,
2413 ERR_CANTEXECCRON);
2414 cron_unlink(tmpfile);
2415 free(tmpfile);
2416 exit(1);
2418 if ((fwrite(e->of.ct.input, sizeof (char),
2419 strlen(e->of.ct.input), fptr)) !=
2420 strlen(e->of.ct.input)) {
2421 mail((e->u)->name, NOSTDIN, ERR_CANTEXECCRON);
2422 cron_unlink(tmpfile);
2423 free(tmpfile);
2424 (void) close(fd);
2425 (void) fclose(fptr);
2426 exit(1);
2428 if (fseek(fptr, (off_t)0, SEEK_SET) != -1) {
2429 if (fd != 0) {
2430 (void) dup2(fd, 0);
2431 (void) close(fd);
2434 cron_unlink(tmpfile);
2435 free(tmpfile);
2436 (void) fclose(fptr);
2437 } else if ((fd = open("/dev/null", O_RDONLY)) > 0) {
2438 (void) dup2(fd, 0);
2439 (void) close(fd);
2443 /* redirect stdout and stderr for the shell */
2444 if ((fd = open(rp->outfile, O_WRONLY|O_CREAT|O_EXCL, OUTMODE)) == 1)
2445 fd = open("/dev/null", O_WRONLY);
2447 if (fd >= 0 && fd != 1)
2448 (void) dup2(fd, 1);
2450 if (fd >= 0 && fd != 2) {
2451 (void) dup2(fd, 2);
2452 if (fd != 1)
2453 (void) close(fd);
2456 if (e->etype == CRONEVENT && e->of.ct.home != NULL) {
2457 home = (char *)get_obj(e->of.ct.home);
2458 } else {
2459 home = (e->u)->home;
2461 (void) strlcat(homedir, home, sizeof (homedir));
2462 (void) strlcat(logname, (e->u)->name, sizeof (logname));
2463 environ = envinit;
2464 if (chdir(home) == -1) {
2465 snprintf(bufs.error, sizeof (bufs.error), CANTCDHOME, home);
2466 mail((e->u)->name, bufs.error,
2467 e->etype == CRONEVENT ? ERR_CANTEXECCRON :
2468 ERR_CANTEXECAT);
2469 exit(1);
2471 #ifdef TESTING
2472 exit(1);
2473 #endif
2475 * make sure that all file descriptors EXCEPT 0, 1 and 2
2476 * will be closed.
2478 closefrom(3);
2480 if ((e->u)->uid != 0)
2481 (void) nice(qp->nice);
2482 if (e->etype == CRONEVENT) {
2483 if (e->of.ct.tz) {
2484 (void) putenv((char *)get_obj(e->of.ct.tz));
2486 if (e->of.ct.shell) {
2487 char *name;
2489 sh = (char *)get_obj(e->of.ct.shell);
2490 name = strrchr(sh, '/');
2491 if (name == NULL)
2492 name = sh;
2493 else
2494 name++;
2496 (void) putenv(sh);
2497 sh += strlen(ENV_SHELL);
2498 (void) execl(sh, name, "-c", e->cmd, 0);
2499 } else {
2500 (void) execl(SHELL, "sh", "-c", e->cmd, 0);
2501 sh = SHELL;
2503 } else { /* type == ATEVENT */
2504 (void) execl(SHELL, "sh", 0);
2505 sh = SHELL;
2507 snprintf(bufs.error, sizeof (bufs.error), CANTEXECSH, sh);
2508 mail((e->u)->name, bufs.error,
2509 e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT);
2510 exit(1);
2511 /*NOTREACHED*/
2515 * Main idle loop.
2516 * When timed out to run the job, return 0.
2517 * If for some reasons we need to reschedule jobs, return 1.
2519 static int
2520 idle(long t)
2522 time_t now;
2524 refresh = 0;
2526 while (t > 0L) {
2527 if (msg_wait(t) != 0) {
2528 /* we need to run next job immediately */
2529 return (0);
2532 reap_child();
2534 if (refresh) {
2535 /* We got THAW or REFRESH message */
2536 return (1);
2539 now = time(NULL);
2540 if (last_time > now) {
2541 /* clock has been reset to backward */
2542 return (1);
2545 if (next_event == NULL && !el_empty()) {
2546 next_event = (struct event *)el_first();
2549 if (next_event == NULL)
2550 t = INFINITY;
2551 else
2552 t = (long)next_event->time - now;
2554 return (0);
2558 * This used to be in the idle(), but moved to the separate function.
2559 * This called from various place when cron needs to reap the
2560 * child. It includes the situation that cron hit maxrun, and needs
2561 * to reschedule the job.
2563 static void
2564 reap_child()
2566 pid_t pid;
2567 int prc;
2568 struct runinfo *rp;
2570 for (;;) {
2571 pid = waitpid((pid_t)-1, &prc, WNOHANG);
2572 if (pid <= 0)
2573 break;
2574 #ifdef DEBUG
2575 fprintf(stderr,
2576 "wait returned %x for process %d\n", prc, pid);
2577 #endif
2578 if ((rp = rinfo_get(pid)) == NULL) {
2579 if (miscpid_delete(pid) == 0) {
2580 /* not found in anywhere */
2581 msg(PIDERR, pid);
2583 } else if (rp->que == ZOMB) {
2584 (void) unlink(rp->outfile);
2585 rinfo_free(rp);
2586 } else {
2587 cleanup(rp, prc);
2592 static void
2593 cleanup(struct runinfo *pr, int rc)
2595 int nextfork = 1;
2596 struct usr *p;
2597 struct stat buf;
2599 logit(ECHAR, pr, rc);
2600 --qt[pr->que].nrun;
2601 p = pr->rusr;
2602 if (pr->que != CRONEVENT)
2603 --p->aruncnt;
2604 else
2605 --p->cruncnt;
2607 if (lstat(pr->outfile, &buf) == 0) {
2608 if (!S_ISLNK(buf.st_mode) &&
2609 (buf.st_size > 0 || pr->mailwhendone)) {
2610 /* mail user stdout and stderr */
2611 for (;;) {
2612 if ((pr->pid = fork()) < 0) {
2614 * if fork fails try forever in doubling
2615 * retry times, up to 16 seconds
2617 (void) sleep(nextfork);
2618 if (nextfork < 16)
2619 nextfork += nextfork;
2620 continue;
2621 } else if (pr->pid == 0) {
2622 child_sigreset();
2623 contract_clear_template();
2625 mail_result(p, pr, buf.st_size);
2626 /* NOTREACHED */
2627 } else {
2628 contract_abandon_latest(pr->pid);
2629 pr->que = ZOMB;
2630 break;
2633 } else {
2634 (void) unlink(pr->outfile);
2635 rinfo_free(pr);
2637 } else {
2638 rinfo_free(pr);
2641 free_if_unused(p);
2645 * Mail stdout and stderr of a job to user. Get uid for real user and become
2646 * that person. We do this so that mail won't come from root since this
2647 * could be a security hole. If failure, quit - don't send mail as root.
2649 static void
2650 mail_result(struct usr *p, struct runinfo *pr, size_t filesize)
2652 struct passwd *ruser_ids;
2653 FILE *mailpipe;
2654 FILE *st;
2655 struct utsname name;
2656 int nbytes;
2657 char iobuf[BUFSIZ];
2658 char *cmd;
2659 char *lowname = (pr->jobtype == CRONEVENT ? "cron" : "at");
2661 (void) uname(&name);
2662 if ((ruser_ids = getpwnam(p->name)) == NULL)
2663 exit(0);
2664 (void) setuid(ruser_ids->pw_uid);
2666 cmd = xmalloc(strlen(MAIL) + strlen(p->name)+2);
2667 (void) sprintf(cmd, "%s %s", MAIL, p->name);
2668 mailpipe = popen(cmd, "w");
2669 free(cmd);
2670 if (mailpipe == NULL)
2671 exit(127);
2672 (void) fprintf(mailpipe, "To: %s\n", p->name);
2673 (void) fprintf(mailpipe, "Subject: %s <%s@%s> %s\n",
2674 (pr->jobtype == CRONEVENT ? "Cron" : "At"),
2675 p->name, name.nodename, pr->jobname);
2678 * RFC3834 (Section 5) defines the Auto-Submitted header to prevent
2679 * vacation replies, et al, from being sent in response to
2680 * machine-generated mail.
2682 (void) fprintf(mailpipe, "Auto-Submitted: auto-generated\n");
2685 * Additional headers for mail filtering and diagnostics:
2687 (void) fprintf(mailpipe, "X-Mailer: cron (%s %s)\n", name.sysname,
2688 name.release);
2689 (void) fprintf(mailpipe, "X-Cron-User: %s\n", p->name);
2690 (void) fprintf(mailpipe, "X-Cron-Host: %s\n", name.nodename);
2691 (void) fprintf(mailpipe, "X-Cron-Job-Name: %s\n", pr->jobname);
2692 (void) fprintf(mailpipe, "X-Cron-Job-Type: %s\n", lowname);
2695 * Message Body:
2697 * (Temporary file is fopen'ed with "r", secure open.)
2699 (void) fprintf(mailpipe, "\n");
2700 if (filesize > 0 &&
2701 (st = fopen(pr->outfile, "r")) != NULL) {
2702 while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0)
2703 (void) fwrite(iobuf, sizeof (char), nbytes, mailpipe);
2704 (void) fclose(st);
2705 } else {
2706 (void) fprintf(mailpipe, "Job completed with no output.\n");
2708 (void) pclose(mailpipe);
2709 exit(0);
2712 static int
2713 msg_wait(long tim)
2715 struct message msg;
2716 int cnt;
2717 time_t reftime;
2718 fd_set fds;
2719 struct timespec tout, *toutp;
2720 static int pending_msg;
2721 static time_t pending_reftime;
2723 if (pending_msg) {
2724 process_msg(&msgbuf, pending_reftime);
2725 pending_msg = 0;
2726 return (0);
2729 FD_ZERO(&fds);
2730 FD_SET(msgfd, &fds);
2732 toutp = NULL;
2733 if (tim != INFINITY) {
2734 #ifdef CRON_MAXSLEEP
2736 * CRON_MAXSLEEP can be defined to have cron periodically wake
2737 * up, so that cron can detect a change of TOD and adjust the
2738 * sleep time more frequently.
2740 tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim;
2741 #endif
2742 tout.tv_nsec = 0;
2743 tout.tv_sec = tim;
2744 toutp = &tout;
2747 cnt = pselect(msgfd + 1, &fds, NULL, NULL, toutp, &defmask);
2748 if (cnt == -1 && errno != EINTR)
2749 perror("! pselect");
2751 /* pselect timeout or interrupted */
2752 if (cnt <= 0)
2753 return (0);
2755 errno = 0;
2756 if ((cnt = read(msgfd, &msg, sizeof (msg))) != sizeof (msg)) {
2757 if (cnt != -1 || errno != EAGAIN)
2758 perror("! read");
2759 return (0);
2761 reftime = time(NULL);
2762 if (next_event != NULL && reftime >= next_event->time) {
2764 * we need to run the job before reloading crontab.
2766 (void) memcpy(&msgbuf, &msg, sizeof (msg));
2767 pending_msg = 1;
2768 pending_reftime = reftime;
2769 return (1);
2771 process_msg(&msg, reftime);
2772 return (0);
2776 * process the message supplied via pipe. This will be called either
2777 * immediately after cron read the message from pipe, or idle time
2778 * if the message was pending due to the job execution.
2780 static void
2781 process_msg(struct message *pmsg, time_t reftime)
2783 if (pmsg->etype == 0)
2784 return;
2786 switch (pmsg->etype) {
2787 case AT:
2788 if (pmsg->action == DELETE)
2789 del_atjob(pmsg->fname, pmsg->logname);
2790 else
2791 mod_atjob(pmsg->fname, (time_t)0);
2792 break;
2793 case CRON:
2794 if (pmsg->action == DELETE)
2795 del_ctab(pmsg->fname);
2796 else
2797 mod_ctab(pmsg->fname, reftime);
2798 break;
2799 case REFRESH:
2800 refresh = 1;
2801 pmsg->etype = 0;
2802 return;
2803 default:
2804 msg("message received - bad format");
2805 break;
2807 if (next_event != NULL) {
2808 if (next_event->etype == CRONEVENT) {
2809 switch (el_add(next_event, next_event->time,
2810 (next_event->u)->ctid)) {
2811 case -1:
2812 ignore_msg("process_msg", "cron", next_event);
2813 break;
2814 case -2: /* event time lower than init time */
2815 reset_needed = 1;
2816 break;
2818 } else { /* etype == ATEVENT */
2819 if (el_add(next_event, next_event->time,
2820 next_event->of.at.eventid) < 0) {
2821 ignore_msg("process_msg", "at", next_event);
2824 next_event = NULL;
2826 (void) fflush(stdout);
2827 pmsg->etype = 0;
2831 * Allocate a new or find an existing runinfo structure
2833 static struct runinfo *
2834 rinfo_get(pid_t pid)
2836 struct runinfo *rp;
2838 if (pid == 0) { /* allocate a new entry */
2839 rp = xcalloc(1, sizeof (struct runinfo));
2840 rp->next = rthead; /* link the entry into the list */
2841 rthead = rp;
2842 return (rp);
2844 /* search the list for an existing entry */
2845 for (rp = rthead; rp != NULL; rp = rp->next) {
2846 if (rp->pid == pid)
2847 break;
2849 return (rp);
2853 * Free a runinfo structure and its associated memory
2855 static void
2856 rinfo_free(struct runinfo *entry)
2858 struct runinfo **rpp;
2859 struct runinfo *rp;
2861 #ifdef DEBUG
2862 (void) fprintf(stderr, "freeing job %s\n", entry->jobname);
2863 #endif
2864 for (rpp = &rthead; (rp = *rpp) != NULL; rpp = &rp->next) {
2865 if (rp == entry) {
2866 *rpp = rp->next; /* unlink the entry */
2867 free(rp->outfile);
2868 free(rp->jobname);
2869 free(rp);
2870 break;
2875 /* ARGSUSED */
2876 static void
2877 thaw_handler(int sig)
2879 refresh = 1;
2883 /* ARGSUSED */
2884 static void
2885 cronend(int sig)
2887 crabort("SIGTERM", REMOVE_FIFO);
2890 /*ARGSUSED*/
2891 static void
2892 child_handler(int sig)
2897 static void
2898 child_sigreset(void)
2900 (void) signal(SIGCLD, SIG_DFL);
2901 (void) sigprocmask(SIG_SETMASK, &defmask, NULL);
2905 * crabort() - handle exits out of cron
2907 static void
2908 crabort(char *mssg, int action)
2910 int c;
2912 if (action & REMOVE_FIFO) {
2913 /* FIFO vanishes when cron finishes */
2914 if (unlink(FIFO) < 0)
2915 perror("cron could not unlink FIFO");
2918 if (action & CONSOLE_MSG) {
2919 /* write error msg to console */
2920 if ((c = open(CONSOLE, O_WRONLY)) >= 0) {
2921 (void) write(c, "cron aborted: ", 14);
2922 (void) write(c, mssg, strlen(mssg));
2923 (void) write(c, "\n", 1);
2924 (void) close(c);
2928 /* always log the message */
2929 msg(mssg);
2930 msg("******* CRON ABORTED ********");
2931 exit(1);
2935 * msg() - time-stamped error reporting function
2937 /*PRINTFLIKE1*/
2938 static void
2939 msg(char *fmt, ...)
2941 va_list args;
2942 time_t t;
2944 t = time(NULL);
2946 (void) fflush(stdout);
2948 (void) fprintf(stderr, "! ");
2950 va_start(args, fmt);
2951 (void) vfprintf(stderr, fmt, args);
2952 va_end(args);
2954 (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
2955 (void) fprintf(stderr, " %s\n", timebuf);
2957 (void) fflush(stderr);
2960 static void
2961 ignore_msg(char *func_name, char *job_type, struct event *event)
2963 msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)",
2964 func_name, job_type,
2965 event->u->name ? event->u->name : "unknown",
2966 event->cmd ? event->cmd : "unknown",
2967 event->time);
2970 static void
2971 logit(int cc, struct runinfo *rp, int rc)
2973 time_t t;
2974 int ret;
2976 if (!log)
2977 return;
2979 t = time(NULL);
2980 if (cc == BCHAR)
2981 (void) printf("%c CMD: %s\n", cc, next_event->cmd);
2982 (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
2983 (void) printf("%c %s %u %c %s",
2984 cc, (rp->rusr)->name, rp->pid, QUE(rp->que), timebuf);
2985 if ((ret = TSTAT(rc)) != 0)
2986 (void) printf(" ts=%d", ret);
2987 if ((ret = RCODE(rc)) != 0)
2988 (void) printf(" rc=%d", ret);
2989 (void) putchar('\n');
2990 (void) fflush(stdout);
2993 static void
2994 resched(int delay)
2996 time_t nt;
2998 /* run job at a later time */
2999 nt = next_event->time + delay;
3000 if (next_event->etype == CRONEVENT) {
3001 next_event->time = next_time(next_event, (time_t)0);
3002 if (nt < next_event->time)
3003 next_event->time = nt;
3004 switch (el_add(next_event, next_event->time,
3005 (next_event->u)->ctid)) {
3006 case -1:
3007 ignore_msg("resched", "cron", next_event);
3008 break;
3009 case -2: /* event time lower than init time */
3010 reset_needed = 1;
3011 break;
3013 delayed = 1;
3014 msg("rescheduling a cron job");
3015 return;
3017 add_atevent(next_event->u, next_event->cmd, nt, next_event->etype);
3018 msg("rescheduling at job");
3021 static void
3022 quedefs(int action)
3024 int i;
3025 int j;
3026 char qbuf[QBUFSIZ];
3027 FILE *fd;
3029 /* set up default queue definitions */
3030 for (i = 0; i < NQUEUE; i++) {
3031 qt[i].njob = qd.njob;
3032 qt[i].nice = qd.nice;
3033 qt[i].nwait = qd.nwait;
3035 if (action == DEFAULT)
3036 return;
3037 if ((fd = fopen(QUEDEFS, "r")) == NULL) {
3038 msg("cannot open quedefs file");
3039 msg("using default queue definitions");
3040 return;
3042 while (fgets(qbuf, QBUFSIZ, fd) != NULL) {
3043 if ((j = qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.')
3044 continue;
3045 parsqdef(&qbuf[2]);
3046 qt[j].njob = qq.njob;
3047 qt[j].nice = qq.nice;
3048 qt[j].nwait = qq.nwait;
3050 (void) fclose(fd);
3053 static void
3054 parsqdef(char *name)
3056 int i;
3058 qq = qd;
3059 while (*name) {
3060 i = 0;
3061 while (isdigit(*name)) {
3062 i *= 10;
3063 i += *name++ - '0';
3065 switch (*name++) {
3066 case JOBF:
3067 qq.njob = i;
3068 break;
3069 case NICEF:
3070 qq.nice = i;
3071 break;
3072 case WAITF:
3073 qq.nwait = i;
3074 break;
3080 * defaults - read defaults from /etc/default/cron
3082 static void
3083 defaults()
3085 int flags;
3086 char *deflog;
3087 char *hz, *tz;
3090 * get HZ value for environment
3092 if ((hz = getenv("HZ")) == NULL)
3093 (void) sprintf(hzname, "HZ=%d", HZ);
3094 else
3095 (void) snprintf(hzname, sizeof (hzname), "HZ=%s", hz);
3097 * get TZ value for environment
3099 (void) snprintf(tzone, sizeof (tzone), "TZ=%s",
3100 ((tz = getenv("TZ")) != NULL) ? tz : DEFTZ);
3102 if (defopen(DEFFILE) == 0) {
3103 /* ignore case */
3104 flags = defcntl(DC_GETFLAGS, 0);
3105 TURNOFF(flags, DC_CASE);
3106 (void) defcntl(DC_SETFLAGS, flags);
3108 if (((deflog = defread("CRONLOG=")) == NULL) ||
3109 (*deflog == 'N') || (*deflog == 'n'))
3110 log = 0;
3111 else
3112 log = 1;
3113 /* fix for 1087611 - allow paths to be set in defaults file */
3114 if ((Def_path = defread("PATH=")) != NULL) {
3115 (void) strlcat(path, Def_path, LINE_MAX);
3116 } else {
3117 (void) strlcpy(path, NONROOTPATH, LINE_MAX);
3119 if ((Def_supath = defread("SUPATH=")) != NULL) {
3120 (void) strlcat(supath, Def_supath, LINE_MAX);
3121 } else {
3122 (void) strlcpy(supath, ROOTPATH, LINE_MAX);
3124 (void) defopen(NULL);
3129 * Determine if a user entry for a job is still ok. The method used here
3130 * is a lot (about 75x) faster than using setgrent() / getgrent()
3131 * endgrent(). It should be safe because we use the sysconf to determine
3132 * the max, and it tolerates the max being 0.
3135 static int
3136 verify_user_cred(struct usr *u)
3138 struct passwd *pw;
3139 size_t numUsrGrps = 0;
3140 size_t numOrigGrps = 0;
3141 size_t i;
3142 int retval;
3145 * Maximum number of groups a user may be in concurrently. This
3146 * is a value which we obtain at runtime through a sysconf()
3147 * call.
3150 static size_t nGroupsMax = (size_t)-1;
3153 * Arrays for cron user's group list, constructed at startup to
3154 * be nGroupsMax elements long, used for verifying user
3155 * credentials prior to execution.
3158 static gid_t *UsrGrps;
3159 static gid_t *OrigGrps;
3161 if ((pw = getpwnam(u->name)) == NULL)
3162 return (VUC_BADUSER);
3163 if (u->home != NULL) {
3164 if (strcmp(u->home, pw->pw_dir) != 0) {
3165 free(u->home);
3166 u->home = xmalloc(strlen(pw->pw_dir) + 1);
3167 (void) strcpy(u->home, pw->pw_dir);
3169 } else {
3170 u->home = xmalloc(strlen(pw->pw_dir) + 1);
3171 (void) strcpy(u->home, pw->pw_dir);
3173 if (u->uid != pw->pw_uid)
3174 u->uid = pw->pw_uid;
3175 if (u->gid != pw->pw_gid)
3176 u->gid = pw->pw_gid;
3179 * Create the group id lists needed for job credential
3180 * verification.
3183 if (nGroupsMax == (size_t)-1) {
3184 if ((nGroupsMax = sysconf(_SC_NGROUPS_MAX)) > 0) {
3185 UsrGrps = xcalloc(nGroupsMax, sizeof (gid_t));
3186 OrigGrps = xcalloc(nGroupsMax, sizeof (gid_t));
3189 #ifdef DEBUG
3190 (void) fprintf(stderr, "nGroupsMax = %ld\n", nGroupsMax);
3191 #endif
3194 #ifdef DEBUG
3195 (void) fprintf(stderr, "verify_user_cred (%s-%d)\n", pw->pw_name,
3196 pw->pw_uid);
3197 (void) fprintf(stderr, "verify_user_cred: pw->pw_gid = %d, "
3198 "u->gid = %d\n", pw->pw_gid, u->gid);
3199 #endif
3201 retval = (u->gid == pw->pw_gid) ? VUC_OK : VUC_NOTINGROUP;
3203 if (nGroupsMax > 0) {
3204 numOrigGrps = getgroups(nGroupsMax, OrigGrps);
3206 (void) initgroups(pw->pw_name, pw->pw_gid);
3207 numUsrGrps = getgroups(nGroupsMax, UsrGrps);
3209 for (i = 0; i < numUsrGrps; i++) {
3210 if (UsrGrps[i] == u->gid) {
3211 retval = VUC_OK;
3212 break;
3216 if (OrigGrps) {
3217 (void) setgroups(numOrigGrps, OrigGrps);
3221 #ifdef DEBUG
3222 (void) fprintf(stderr, "verify_user_cred: VUC = %d\n", retval);
3223 #endif
3225 return (retval);
3228 static int
3229 set_user_cred(const struct usr *u, struct project *pproj)
3231 static char *progname = "cron";
3232 int r = 0, rval = 0;
3234 if ((r = pam_start(progname, u->name, &pam_conv, &pamh))
3235 != PAM_SUCCESS) {
3236 #ifdef DEBUG
3237 msg("pam_start returns %d\n", r);
3238 #endif
3239 rval = VUC_BADUSER;
3240 goto set_eser_cred_exit;
3243 r = pam_acct_mgmt(pamh, 0);
3244 #ifdef DEBUG
3245 msg("pam_acc_mgmt returns %d\n", r);
3246 #endif
3247 if (r == PAM_ACCT_EXPIRED) {
3248 rval = VUC_EXPIRED;
3249 goto set_eser_cred_exit;
3251 if (r == PAM_NEW_AUTHTOK_REQD) {
3252 rval = VUC_NEW_AUTH;
3253 goto set_eser_cred_exit;
3255 if (r != PAM_SUCCESS) {
3256 rval = VUC_BADUSER;
3257 goto set_eser_cred_exit;
3260 if (pproj != NULL) {
3261 size_t sz = sizeof (PROJECT) + strlen(pproj->pj_name);
3262 char *buf = alloca(sz);
3264 (void) snprintf(buf, sz, PROJECT "%s", pproj->pj_name);
3265 (void) pam_set_item(pamh, PAM_RESOURCE, buf);
3268 r = pam_setcred(pamh, PAM_ESTABLISH_CRED);
3269 if (r != PAM_SUCCESS)
3270 rval = VUC_BADUSER;
3272 set_eser_cred_exit:
3273 (void) pam_end(pamh, r);
3274 return (rval);
3277 static void
3278 clean_out_user(struct usr *u)
3280 if (next_event->u == u) {
3281 next_event = NULL;
3284 clean_out_ctab(u);
3285 clean_out_atjobs(u);
3286 free_if_unused(u);
3289 static void
3290 clean_out_atjobs(struct usr *u)
3292 struct event *ev, *pv;
3294 for (pv = NULL, ev = u->atevents;
3295 ev != NULL;
3296 pv = ev, ev = ev->link, free(pv)) {
3297 el_remove(ev->of.at.eventid, 1);
3298 if (cwd == AT)
3299 cron_unlink(ev->cmd);
3300 else {
3301 char buf[PATH_MAX];
3302 if (strlen(ATDIR) + strlen(ev->cmd) + 2
3303 < PATH_MAX) {
3304 (void) sprintf(buf, "%s/%s", ATDIR, ev->cmd);
3305 cron_unlink(buf);
3308 free(ev->cmd);
3311 u->atevents = NULL;
3314 static void
3315 clean_out_ctab(struct usr *u)
3317 rm_ctevents(u);
3318 el_remove(u->ctid, 0);
3319 u->ctid = 0;
3320 u->ctexists = 0;
3323 static void
3324 cron_unlink(char *name)
3326 unlink(name);
3329 /*ARGSUSED*/
3330 static int
3331 cron_conv(int num_msg, struct pam_message **msgs,
3332 struct pam_response **response, void *appdata_ptr)
3334 struct pam_message **m = msgs;
3335 int i;
3337 for (i = 0; i < num_msg; i++) {
3338 switch (m[i]->msg_style) {
3339 case PAM_ERROR_MSG:
3340 case PAM_TEXT_INFO:
3341 if (m[i]->msg != NULL) {
3342 (void) msg("%s\n", m[i]->msg);
3344 break;
3346 default:
3347 break;
3350 return (0);
3354 * Cron creates process for other than job. Mail process is the
3355 * one which rinfo does not cover. Therefore, miscpid will keep
3356 * track of the pids executed from cron. Otherwise, we will see
3357 * "unexpected pid returned.." messages appear in the log file.
3359 static void
3360 miscpid_insert(pid_t pid)
3362 struct miscpid *mp;
3364 mp = xmalloc(sizeof (*mp));
3365 mp->pid = pid;
3366 mp->next = miscpid_head;
3367 miscpid_head = mp;
3370 static int
3371 miscpid_delete(pid_t pid)
3373 struct miscpid *mp, *omp;
3374 int found = 0;
3376 omp = NULL;
3377 for (mp = miscpid_head; mp != NULL; mp = mp->next) {
3378 if (mp->pid == pid) {
3379 found = 1;
3380 break;
3382 omp = mp;
3384 if (found) {
3385 if (omp != NULL)
3386 omp->next = mp->next;
3387 else
3388 miscpid_head = NULL;
3389 free(mp);
3391 return (found);
3395 * Establish contract terms such that all children are in abandoned
3396 * process contracts.
3398 static void
3399 contract_set_template(void)
3401 int fd;
3403 if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3404 crabort("cannot open process contract template",
3405 REMOVE_FIFO | CONSOLE_MSG);
3407 if (ct_pr_tmpl_set_param(fd, 0) ||
3408 ct_tmpl_set_informative(fd, 0) ||
3409 ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR))
3410 crabort("cannot establish contract template terms",
3411 REMOVE_FIFO | CONSOLE_MSG);
3413 if (ct_tmpl_activate(fd))
3414 crabort("cannot activate contract template",
3415 REMOVE_FIFO | CONSOLE_MSG);
3417 (void) close(fd);
3421 * Clear active process contract template.
3423 static void
3424 contract_clear_template(void)
3426 int fd;
3428 if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3429 crabort("cannot open process contract template",
3430 REMOVE_FIFO | CONSOLE_MSG);
3432 if (ct_tmpl_clear(fd))
3433 crabort("cannot clear contract template",
3434 REMOVE_FIFO | CONSOLE_MSG);
3436 (void) close(fd);
3440 * Abandon latest process contract unconditionally. If we have leaked [some
3441 * critical amount], exit such that the kernel reaps our contracts.
3443 static void
3444 contract_abandon_latest(pid_t pid)
3446 int r;
3447 ctid_t id;
3448 static uint_t cts_lost;
3450 if (cts_lost > MAX_LOST_CONTRACTS)
3451 crabort("repeated failure to abandon contracts",
3452 REMOVE_FIFO | CONSOLE_MSG);
3454 if (r = contract_latest(&id)) {
3455 msg("could not obtain latest contract for "
3456 "PID %ld: %s", pid, strerror(r));
3457 cts_lost++;
3458 return;
3461 if (r = contract_abandon_id(id)) {
3462 msg("could not abandon latest contract %ld: %s", id,
3463 strerror(r));
3464 cts_lost++;
3465 return;
3469 static struct shared *
3470 create_shared(void *obj, void * (*obj_alloc)(void *obj),
3471 void (*obj_free)(void *))
3473 struct shared *out;
3475 if ((out = xmalloc(sizeof (struct shared))) == NULL) {
3476 return (NULL);
3478 if ((out->obj = obj_alloc(obj)) == NULL) {
3479 free(out);
3480 return (NULL);
3482 out->count = 1;
3483 out->free = obj_free;
3485 return (out);
3488 static struct shared *
3489 create_shared_str(char *str)
3491 return (create_shared(str, (void *(*)(void *))strdup, free));
3494 static struct shared *
3495 dup_shared(struct shared *obj)
3497 if (obj != NULL) {
3498 obj->count++;
3500 return (obj);
3503 static void
3504 rel_shared(struct shared *obj)
3506 if (obj && (--obj->count) == 0) {
3507 obj->free(obj->obj);
3508 free(obj);
3512 static void *
3513 get_obj(struct shared *obj)
3515 return (obj->obj);