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]
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>
40 #include <sys/param.h>
41 #include <sys/resource.h>
45 #include <sys/types.h>
46 #include <sys/utsname.h>
49 #include <security/pam_appl.h>
58 #include <libcontract.h>
59 #include <libcontract_priv.h>
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 */
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 */
94 #define ZOMB 100 /* proc slot used for mailing output */
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 \
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)
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
;
174 int count
; /* usage count */
175 void (*free
)(void *obj
); /* routine that will free obj */
176 void *obj
; /* object */
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 */
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 */
197 struct { /* for at events */
198 short exists
; /* for revising at events */
199 int eventid
; /* for el_remove-ing at events */
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 */
216 }; /* ptr to next user */
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 */
227 static struct queue qq
;
229 static struct runinfo
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
;
241 static struct miscpid
{
243 struct miscpid
*next
;
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
[] = {
284 extern char **environ
;
288 static char hzname
[10];
290 static void cronend(int);
291 static void thaw_handler(int);
292 static void child_handler(int);
293 static void child_sigreset(void);
295 static void mod_ctab(char *, time_t);
296 static void mod_atjob(char *, time_t);
297 static void add_atevent(struct usr
*, char *, time_t, int);
298 static void rm_ctevents(struct usr
*);
299 static void cleanup(struct runinfo
*rn
, int r
);
300 static void crabort(char *, int);
301 static void msg(char *fmt
, ...);
302 static void ignore_msg(char *, char *, struct event
*);
303 static void logit(int, struct runinfo
*, int);
304 static void parsqdef(char *);
305 static void defaults();
306 static void initialize(int);
307 static void quedefs(int);
308 static int idle(long);
309 static struct usr
*find_usr(char *);
310 static int ex(struct event
*e
);
311 static void read_dirs(int);
312 static void mail(char *, char *, int);
313 static char *next_field(int, int);
314 static void readcron(struct usr
*, time_t);
315 static int next_ge(int, char *);
316 static void free_if_unused(struct usr
*);
317 static void del_atjob(char *, char *);
318 static void del_ctab(char *);
319 static void resched(int);
320 static int msg_wait(long);
321 static struct runinfo
*rinfo_get(pid_t
);
322 static void rinfo_free(struct runinfo
*rp
);
323 static void mail_result(struct usr
*p
, struct runinfo
*pr
, size_t filesize
);
324 static time_t next_time(struct event
*, time_t);
325 static time_t get_switching_time(int, time_t);
326 static time_t xmktime(struct tm
*);
327 static void process_msg(struct message
*, time_t);
328 static void reap_child(void);
329 static void miscpid_insert(pid_t
);
330 static int miscpid_delete(pid_t
);
331 static void contract_set_template(void);
332 static void contract_clear_template(void);
333 static void contract_abandon_latest(pid_t
);
335 static void cte_init(void);
336 static void cte_add(int, char *);
337 static void cte_valid(void);
338 static int cte_istoomany(void);
339 static void cte_sendmail(char *);
341 static int set_user_cred(const struct usr
*, struct project
*);
343 static struct shared
*create_shared_str(char *str
);
344 static struct shared
*dup_shared(struct shared
*obj
);
345 static void rel_shared(struct shared
*obj
);
346 static void *get_obj(struct shared
*obj
);
348 * last_time is set immediately prior to exection of an event (via ex())
349 * to indicate the last time an event was executed. This was (surely)
350 * it's original intended use.
352 static time_t last_time
, init_time
, t_old
;
353 static int reset_needed
; /* set to 1 when cron(1M) needs to re-initialize */
356 static sigset_t defmask
, sigmask
;
361 extern int audit_cron_session(char *, char *, uid_t
, gid_t
, char *);
362 extern void audit_cron_new_job(char *, int, void *);
363 extern void audit_cron_bad_user(char *);
364 extern void audit_cron_user_acct_expired(char *);
365 extern int audit_cron_create_anc_file(char *, char *, char *, uid_t
);
366 extern int audit_cron_delete_anc_file(char *, char *);
367 extern int audit_cron_is_anc_name(char *);
368 extern int audit_cron_mode();
370 static int cron_conv(int, struct pam_message
**,
371 struct pam_response
**, void *);
373 static struct pam_conv pam_conv
= {cron_conv
, NULL
};
374 static pam_handle_t
*pamh
; /* Authentication handle */
377 * Function to help check a user's credentials.
380 static int verify_user_cred(struct usr
*u
);
383 * Values returned by verify_user_cred and set_user_cred:
387 #define VUC_BADUSER 1
388 #define VUC_NOTINGROUP 2
389 #define VUC_EXPIRED 3
390 #define VUC_NEW_AUTH 4
393 * Modes of process_anc_files function
395 #define CRON_ANC_DELETE 1
396 #define CRON_ANC_CREATE 0
399 * Functions to remove a user or job completely from the running database.
401 static void clean_out_atjobs(struct usr
*u
);
402 static void clean_out_ctab(struct usr
*u
);
403 static void clean_out_user(struct usr
*u
);
404 static void cron_unlink(char *name
);
405 static void process_anc_files(int);
410 extern void el_init(int, time_t, time_t, int);
411 extern int el_add(void *, time_t, int);
412 extern void el_remove(int, int);
413 extern int el_empty(void);
414 extern void *el_first(void);
415 extern void el_delete(void);
417 static int valid_entry(char *, int);
418 static struct usr
*create_ulist(char *, int);
419 static void init_cronevent(char *, int);
420 static void init_atevent(char *, time_t, int, int);
421 static void update_atevent(struct usr
*, char *, time_t, int);
424 main(int argc
, char *argv
[])
427 time_t ne_time
; /* amt of time until next event execution */
428 time_t newtime
, lastmtime
= 0L;
430 struct event
*e
, *e2
, *eprev
;
433 struct sigaction act
;
436 * reset_needed is set to 1 whenever el_add() finds out that a cron
437 * job is scheduled to be run before the time when cron(1M) daemon
439 * Other cases where a reset is needed is when ex() finds that the
440 * event to be executed is being run at the wrong time, or when idle()
441 * determines that time was reset.
442 * We immediately return to the top of the while (TRUE) loop in
443 * main() where the event list is cleared and rebuilt, and reset_needed
449 * Only the privileged user can run this command.
452 crabort(NOTALLOWED
, 0);
455 (void) setlocale(LC_ALL
, "");
456 /* fork unless 'nofork' is specified */
457 if ((argc
<= 1) || (strcmp(argv
[1], "nofork"))) {
458 if (rfork
= fork()) {
459 if (rfork
== (pid_t
)-1) {
466 (void) setpgrp(); /* detach cron from console */
470 (void) signal(SIGHUP
, SIG_IGN
);
471 (void) signal(SIGINT
, SIG_IGN
);
472 (void) signal(SIGQUIT
, SIG_IGN
);
473 (void) signal(SIGTERM
, cronend
);
477 quedefs(DEFAULT
); /* load default queue definitions */
479 msg("*** cron started *** pid = %d", cron_pid
);
481 /* setup THAW handler */
482 act
.sa_handler
= thaw_handler
;
484 (void) sigemptyset(&act
.sa_mask
);
485 (void) sigaction(SIGTHAW
, &act
, NULL
);
487 /* setup CHLD handler */
488 act
.sa_handler
= child_handler
;
490 (void) sigemptyset(&act
.sa_mask
);
491 (void) sigaddset(&act
.sa_mask
, SIGCLD
);
492 (void) sigaction(SIGCLD
, &act
, NULL
);
494 (void) sigemptyset(&defmask
);
495 (void) sigemptyset(&sigmask
);
496 (void) sigaddset(&sigmask
, SIGCLD
);
497 (void) sigaddset(&sigmask
, SIGTHAW
);
498 (void) sigprocmask(SIG_BLOCK
, &sigmask
, NULL
);
502 for (;;) { /* MAIN LOOP */
504 if ((t_old
> t
) || (t
-last_time
> CUSHION
) || reset_needed
) {
507 * the time was set backwards or forward or
508 * refresh is requested.
511 msg("re-scheduling jobs");
513 msg("time was reset, re-initializing");
533 * reset_needed might have been set in the functions
534 * call path from initialize()
542 if (next_event
== NULL
&& !el_empty()) {
543 next_event
= (struct event
*)el_first();
545 if (next_event
== NULL
) {
548 ne_time
= next_event
->time
- t
;
550 cftime(timebuf
, "%+", &next_event
->time
);
551 (void) fprintf(stderr
, "next_time=%ld %s\n",
552 next_event
->time
, timebuf
);
557 * reset_needed may be set in the functions call path
560 if (idle(ne_time
) || reset_needed
) {
566 if (stat(QUEDEFS
, &buf
)) {
567 msg("cannot stat QUEDEFS file");
568 } else if (lastmtime
!= buf
.st_mtime
) {
570 lastmtime
= buf
.st_mtime
;
573 last_time
= next_event
->time
; /* save execution time */
576 * reset_needed may be set in the functions call path
579 if (ex(next_event
) || reset_needed
) {
584 switch (next_event
->etype
) {
586 /* add cronevent back into the main event list */
593 * check if time(0)< last_time. if so, then the
594 * system clock has gone backwards. to prevent this
595 * job from being started twice, we reschedule this
596 * job for the >>next time after last_time<<, and
597 * then set next_event->time to this. note that
598 * crontab's resolution is 1 minute.
601 if (last_time
> time(NULL
)) {
604 * bump up to next 30 second
608 newtime
= 30 - (last_time
% 30);
609 newtime
+= last_time
;
612 * get the next scheduled event,
613 * not the one that we just
617 next_time(next_event
, newtime
);
621 next_time(next_event
, (time_t)0);
624 cftime(timebuf
, "%+", &next_event
->time
);
625 (void) fprintf(stderr
,
626 "pushing back cron event %s at %ld (%s)\n",
627 next_event
->cmd
, next_event
->time
, timebuf
);
630 switch (el_add(next_event
, next_event
->time
,
631 (next_event
->u
)->ctid
)) {
633 ignore_msg("main", "cron", next_event
);
635 case -2: /* event time lower than init time */
641 /* remove at or batch job from system */
647 e
= (next_event
->u
)->atevents
;
649 if (e
== next_event
) {
651 (e
->u
)->atevents
= e
->link
;
653 eprev
->link
= e
->link
;
671 initialize(int firstpass
)
674 (void) fprintf(stderr
, "in initialize\n");
677 /* for mail(1), make sure messages come from root */
678 if (putenv("LOGNAME=root") != 0) {
679 crabort("cannot expand env variable",
680 REMOVE_FIFO
|CONSOLE_MSG
);
682 if (access(FIFO
, R_OK
) == -1) {
683 if (errno
== ENOENT
) {
684 if (mknod(FIFO
, S_IFIFO
|0600, 0) != 0)
685 crabort("cannot create fifo queue",
686 REMOVE_FIFO
|CONSOLE_MSG
);
689 /* didn't fork... init(1M) is waiting */
693 crabort("cannot access fifo queue",
694 REMOVE_FIFO
|CONSOLE_MSG
);
698 /* didn't fork... init(1M) is waiting */
701 * the wait is painful, but we don't want
702 * init respawning this quickly
705 crabort("cannot start cron; FIFO exists", CONSOLE_MSG
);
709 if ((msgfd
= open(FIFO
, O_RDWR
)) < 0) {
711 crabort("cannot open fifo queue", REMOVE_FIFO
|CONSOLE_MSG
);
714 init_time
= time(NULL
);
715 el_init(8, init_time
, (time_t)(60*60*24), 10);
717 init_time
= time(NULL
);
718 el_init(8, init_time
, (time_t)(60*60*24), 10);
721 * read directories, create users list, and add events to the
722 * main event list. Only zero user list on firstpass.
726 read_dirs(firstpass
);
732 /* stdout is log file */
733 if (freopen(ACCTFILE
, "a", stdout
) == NULL
)
734 (void) fprintf(stderr
, "cannot open %s\n", ACCTFILE
);
736 /* log should be root-only */
737 (void) fchmod(1, S_IRUSR
|S_IWUSR
);
739 /* stderr also goes to ACCTFILE */
740 (void) close(fileno(stderr
));
743 (void) freopen("/dev/null", "r", stdin
);
745 contract_set_template();
758 if (chdir(CRONDIR
) == -1)
759 crabort(BADCD
, REMOVE_FIFO
|CONSOLE_MSG
);
761 if ((dir
= opendir(".")) == NULL
)
762 crabort(NOREADDIR
, REMOVE_FIFO
|CONSOLE_MSG
);
763 while ((dp
= readdir(dir
)) != NULL
) {
764 if (!valid_entry(dp
->d_name
, CRONEVENT
))
766 init_cronevent(dp
->d_name
, first
);
768 (void) closedir(dir
);
770 if (chdir(ATDIR
) == -1) {
771 msg("cannot chdir to at directory");
774 if ((dir
= opendir(".")) == NULL
) {
775 msg("cannot read at at directory");
779 while ((dp
= readdir(dir
)) != NULL
) {
780 if (!valid_entry(dp
->d_name
, ATEVENT
))
783 if (((tim
= num(&ptr
)) == 0) || (*ptr
!= '.'))
788 jobtype
= *ptr
- 'a';
789 if (jobtype
>= NQUEUE
) {
790 cron_unlink(dp
->d_name
);
793 init_atevent(dp
->d_name
, tim
, jobtype
, first
);
795 (void) closedir(dir
);
799 valid_entry(char *name
, int type
)
803 if (strcmp(name
, ".") == 0 ||
804 strcmp(name
, "..") == 0)
807 /* skip over ancillary file names */
808 if (audit_cron_is_anc_name(name
))
811 if (stat(name
, &buf
)) {
812 mail(name
, BADSTAT
, ERR_UNIXERR
);
816 if (!S_ISREG(buf
.st_mode
)) {
817 mail(name
, BADTYPE
, ERR_NOTREG
);
821 if (type
== ATEVENT
) {
822 if (!(buf
.st_mode
& ISUID
)) {
831 create_ulist(char *name
, int type
)
835 u
= xcalloc(1, sizeof (struct usr
));
836 u
->name
= xstrdup(name
);
837 if (type
== CRONEVENT
) {
852 init_cronevent(char *name
, int first
)
857 u
= create_ulist(name
, CRONEVENT
);
860 if ((u
= find_usr(name
)) == NULL
) {
861 u
= create_ulist(name
, CRONEVENT
);
866 el_remove(u
->ctid
, 0);
873 init_atevent(char *name
, time_t tim
, int jobtype
, int first
)
878 u
= create_ulist(name
, ATEVENT
);
879 add_atevent(u
, name
, tim
, jobtype
);
881 if ((u
= find_usr(name
)) == NULL
) {
882 u
= create_ulist(name
, ATEVENT
);
883 add_atevent(u
, name
, tim
, jobtype
);
885 update_atevent(u
, name
, tim
, jobtype
);
891 mod_ctab(char *name
, time_t reftime
)
896 char namebuf
[LINE_MAX
];
899 /* skip over ancillary file names */
900 if (audit_cron_is_anc_name(name
))
903 if ((pw
= getpwnam(name
)) == NULL
) {
904 msg("No such user as %s - cron entries not created", name
);
908 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s",
909 CRONDIR
, name
) >= sizeof (namebuf
)) {
910 msg("Too long path name %s - cron entries not created",
919 * a warning message is given by the crontab command so there is
920 * no need to give one here...... use this code if you only want
921 * users with a login shell of /usr/bin/sh to use cron
923 #ifdef BOURNESHELLONLY
924 if ((strcmp(pw
->pw_shell
, "") != 0) &&
925 (strcmp(pw
->pw_shell
, SHELL
) != 0)) {
926 mail(name
, BADSHELL
, ERR_CANTEXECCRON
);
931 if (stat(pname
, &buf
)) {
932 mail(name
, BADSTAT
, ERR_UNIXERR
);
936 if (!S_ISREG(buf
.st_mode
)) {
937 mail(name
, BADTYPE
, ERR_CRONTABENT
);
940 if ((u
= find_usr(name
)) == NULL
) {
942 (void) fprintf(stderr
, "new user (%s) with a crontab\n", name
);
944 u
= create_ulist(name
, CRONEVENT
);
945 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
946 (void) strcpy(u
->home
, pw
->pw_dir
);
949 readcron(u
, reftime
);
953 if (u
->home
!= NULL
) {
954 if (strcmp(u
->home
, pw
->pw_dir
) != 0) {
956 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
957 (void) strcpy(u
->home
, pw
->pw_dir
);
960 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
961 (void) strcpy(u
->home
, pw
->pw_dir
);
966 (void) fprintf(stderr
, "%s now has a crontab\n",
969 /* user didnt have a crontab last time */
972 readcron(u
, reftime
);
976 (void) fprintf(stderr
, "%s has revised his crontab\n", u
->name
);
979 el_remove(u
->ctid
, 0);
980 readcron(u
, reftime
);
986 mod_atjob(char *name
, time_t reftime
)
993 char namebuf
[PATH_MAX
];
998 if (((tim
= num(&ptr
)) == 0) || (*ptr
!= '.'))
1003 jobtype
= *ptr
- 'a';
1005 /* check for audit ancillary file */
1006 if (audit_cron_is_anc_name(name
))
1010 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s", ATDIR
, name
)
1011 >= sizeof (namebuf
)) {
1018 if (stat(pname
, &buf
) || jobtype
>= NQUEUE
) {
1022 if (!(buf
.st_mode
& ISUID
) || !S_ISREG(buf
.st_mode
)) {
1026 if ((pw
= getpwuid(buf
.st_uid
)) == NULL
) {
1031 * a warning message is given by the at command so there is no
1032 * need to give one here......use this code if you only want
1033 * users with a login shell of /usr/bin/sh to use cron
1035 #ifdef BOURNESHELLONLY
1036 if ((strcmp(pw
->pw_shell
, "") != 0) &&
1037 (strcmp(pw
->pw_shell
, SHELL
) != 0)) {
1038 mail(pw
->pw_name
, BADSHELL
, ERR_CANTEXECAT
);
1043 if ((u
= find_usr(pw
->pw_name
)) == NULL
) {
1045 (void) fprintf(stderr
, "new user (%s) with an at job = %s\n",
1048 u
= create_ulist(pw
->pw_name
, ATEVENT
);
1049 u
->home
= xstrdup(pw
->pw_dir
);
1050 u
->uid
= pw
->pw_uid
;
1051 u
->gid
= pw
->pw_gid
;
1052 add_atevent(u
, name
, tim
, jobtype
);
1054 u
->uid
= pw
->pw_uid
;
1055 u
->gid
= pw
->pw_gid
;
1057 u
->home
= xstrdup(pw
->pw_dir
);
1058 update_atevent(u
, name
, tim
, jobtype
);
1063 add_atevent(struct usr
*u
, char *job
, time_t tim
, int jobtype
)
1067 e
= xmalloc(sizeof (struct event
));
1069 e
->cmd
= xmalloc(strlen(job
) + 1);
1070 (void) strcpy(e
->cmd
, job
);
1072 e
->link
= u
->atevents
;
1074 e
->of
.at
.exists
= TRUE
;
1075 e
->of
.at
.eventid
= ecid
++;
1076 if (tim
< init_time
) /* old job */
1077 e
->time
= init_time
;
1081 (void) fprintf(stderr
, "add_atevent: user=%s, job=%s, time=%ld\n",
1082 u
->name
, e
->cmd
, e
->time
);
1084 if (el_add(e
, e
->time
, e
->of
.at
.eventid
) < 0) {
1085 ignore_msg("add_atevent", "at", e
);
1090 update_atevent(struct usr
*u
, char *name
, time_t tim
, int jobtype
)
1096 if (strcmp(e
->cmd
, name
) == 0) {
1097 e
->of
.at
.exists
= TRUE
;
1105 (void) fprintf(stderr
, "%s has a new at job = %s\n",
1108 add_atevent(u
, name
, tim
, jobtype
);
1112 static char line
[CTLINESIZE
]; /* holds a line from a crontab file */
1113 static int cursor
; /* cursor for the above line */
1116 readcron(struct usr
*u
, time_t reftime
)
1119 * readcron reads in a crontab file for a user (u). The list of
1120 * events for user u is built, and u->events is made to point to
1121 * this list. Each event is also entered into the main event
1124 FILE *cf
; /* cf will be a user's crontab file */
1128 char namebuf
[PATH_MAX
];
1130 struct shared
*tz
= NULL
;
1131 struct shared
*home
= NULL
;
1132 struct shared
*shell
= NULL
;
1135 /* read the crontab file */
1136 cte_init(); /* Init error handling */
1138 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s",
1139 CRONDIR
, u
->name
) >= sizeof (namebuf
)) {
1146 if ((cf
= fopen(pname
, "r")) == NULL
) {
1147 mail(u
->name
, NOREAD
, ERR_UNIXERR
);
1150 while (fgets(line
, CTLINESIZE
, cf
) != NULL
) {
1152 /* process a line of a crontab file */
1154 if (cte_istoomany())
1157 while (line
[cursor
] == ' ' || line
[cursor
] == '\t')
1159 if (line
[cursor
] == '#' || line
[cursor
] == '\n')
1162 if (strncmp(&line
[cursor
], ENV_TZ
,
1163 strlen(ENV_TZ
)) == 0) {
1164 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1168 if (tz
== NULL
|| strcmp(&line
[cursor
], get_obj(tz
))) {
1170 tz
= create_shared_str(&line
[cursor
]);
1175 if (strncmp(&line
[cursor
], ENV_HOME
,
1176 strlen(ENV_HOME
)) == 0) {
1177 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1181 strcmp(&line
[cursor
], get_obj(home
))) {
1183 home
= create_shared_str(
1184 &line
[cursor
+ strlen(ENV_HOME
)]);
1189 if (strncmp(&line
[cursor
], ENV_SHELL
,
1190 strlen(ENV_SHELL
)) == 0) {
1191 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1194 if (shell
== NULL
||
1195 strcmp(&line
[cursor
], get_obj(shell
))) {
1197 shell
= create_shared_str(&line
[cursor
]);
1202 e
= xmalloc(sizeof (struct event
));
1203 e
->etype
= CRONEVENT
;
1204 if (!(((e
->of
.ct
.minute
= next_field(0, 59)) != NULL
) &&
1205 ((e
->of
.ct
.hour
= next_field(0, 23)) != NULL
) &&
1206 ((e
->of
.ct
.daymon
= next_field(1, 31)) != NULL
) &&
1207 ((e
->of
.ct
.month
= next_field(1, 12)) != NULL
) &&
1208 ((e
->of
.ct
.dayweek
= next_field(0, 6)) != NULL
))) {
1210 cte_add(lineno
, line
);
1213 while (line
[cursor
] == ' ' || line
[cursor
] == '\t')
1215 if (line
[cursor
] == '\n' || line
[cursor
] == '\0')
1217 /* get the command to execute */
1220 while ((line
[cursor
] != '%') &&
1221 (line
[cursor
] != '\n') &&
1222 (line
[cursor
] != '\0') &&
1223 (line
[cursor
] != '\\'))
1225 if (line
[cursor
] == '\\') {
1229 e
->cmd
= xmalloc(cursor
-start
+ 1);
1230 (void) strncpy(e
->cmd
, line
+ start
, cursor
-start
);
1231 e
->cmd
[cursor
-start
] = '\0';
1232 /* see if there is any standard input */
1233 if (line
[cursor
] == '%') {
1234 e
->of
.ct
.input
= xmalloc(strlen(line
)-cursor
+ 1);
1235 (void) strcpy(e
->of
.ct
.input
, line
+ cursor
+ 1);
1236 for (i
= 0; i
< strlen(e
->of
.ct
.input
); i
++) {
1237 if (e
->of
.ct
.input
[i
] == '%')
1238 e
->of
.ct
.input
[i
] = '\n';
1241 e
->of
.ct
.input
= NULL
;
1243 /* set the timezone of this entry */
1244 e
->of
.ct
.tz
= dup_shared(tz
);
1245 /* set the shell of this entry */
1246 e
->of
.ct
.shell
= dup_shared(shell
);
1247 /* set the home of this entry */
1248 e
->of
.ct
.home
= dup_shared(home
);
1249 /* have the event point to it's owner */
1251 /* insert this event at the front of this user's event list */
1252 e
->link
= u
->ctevents
;
1254 /* set the time for the first occurance of this event */
1255 e
->time
= next_time(e
, reftime
);
1256 /* finally, add this event to the main event list */
1257 switch (el_add(e
, e
->time
, u
->ctid
)) {
1259 ignore_msg("readcron", "cron", e
);
1261 case -2: /* event time lower than init time */
1267 cftime(timebuf
, "%+", &e
->time
);
1268 (void) fprintf(stderr
, "inserting cron event %s at %ld (%s)\n",
1269 e
->cmd
, e
->time
, timebuf
);
1272 cte_sendmail(u
->name
); /* mail errors if any to user */
1280 * Below are the functions for handling of errors in crontabs. Concept is to
1281 * collect faulty lines and send one email at the end of the crontab
1282 * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
1283 * of crontab is aborted. Otherwise reading of crontab is continued to the end
1284 * of the file but no further error logging appears.
1289 if (cte_text
== NULL
)
1290 cte_text
= xmalloc(MAILBUFLEN
);
1291 (void) strlcpy(cte_text
, cte_intro
, MAILBUFLEN
);
1292 cte_lp
= cte_text
+ sizeof (cte_intro
) - 1;
1293 cte_free
= MAILBINITFREE
;
1298 cte_add(int lineno
, char *ctline
)
1303 if (cte_free
>= LINELIMIT
) {
1304 (void) sprintf(cte_lp
, "%4d: ", lineno
);
1305 (void) strlcat(cte_lp
, ctline
, LINELIMIT
- 1);
1306 len
= strlen(cte_lp
);
1307 if (cte_lp
[len
- 1] != '\n') {
1308 cte_lp
[len
++] = '\n';
1311 for (p
= cte_lp
; *p
; p
++) {
1312 if (isprint(*p
) || *p
== '\n' || *p
== '\t')
1318 if (cte_free
< LINELIMIT
) {
1319 size_t buflen
= MAILBUFLEN
- (cte_lp
- cte_text
);
1320 (void) strlcpy(cte_lp
, cte_trail1
, buflen
);
1321 if (cte_nvalid
== 0)
1322 (void) strlcat(cte_lp
, cte_trail2
, buflen
);
1337 * Return TRUE only if all lines are faulty. So evaluation of
1338 * a crontab is not aborted if at least one valid line was found.
1340 return (cte_nvalid
== 0 && cte_free
< LINELIMIT
);
1344 cte_sendmail(char *username
)
1346 if (cte_free
< MAILBINITFREE
)
1347 mail(username
, cte_text
, ERR_CRONTABENT
);
1351 * Send mail with error message to a user
1354 mail(char *usrname
, char *mesg
, int format
)
1356 /* mail mails a user a message. */
1359 struct passwd
*ruser_ids
;
1361 int saveerrno
= errno
;
1362 struct utsname name
;
1367 (void) uname(&name
);
1368 if ((fork_val
= fork()) == (pid_t
)-1) {
1369 msg("cron cannot fork\n");
1372 if (fork_val
== 0) {
1374 contract_clear_template();
1375 if ((ruser_ids
= getpwnam(usrname
)) == NULL
)
1377 (void) setuid(ruser_ids
->pw_uid
);
1378 temp
= xmalloc(strlen(MAIL
) + strlen(usrname
) + 2);
1379 (void) sprintf(temp
, "%s %s", MAIL
, usrname
);
1380 pipe
= popen(temp
, "w");
1382 (void) fprintf(pipe
, "To: %s\n", usrname
);
1384 case ERR_CRONTABENT
:
1385 (void) fprintf(pipe
, CRONTABERR
);
1386 (void) fprintf(pipe
, "Your \"crontab\" on %s\n",
1388 (void) fprintf(pipe
, mesg
);
1389 (void) fprintf(pipe
,
1390 "\nEntries or crontab have been ignored\n");
1393 (void) fprintf(pipe
, "Subject: %s\n\n", mesg
);
1394 (void) fprintf(pipe
,
1395 "The error on %s was \"%s\"\n",
1396 name
.nodename
, errmsg(saveerrno
));
1399 case ERR_CANTEXECCRON
:
1400 (void) fprintf(pipe
,
1401 "Subject: Couldn't run your \"cron\" job\n\n");
1402 (void) fprintf(pipe
,
1403 "Your \"cron\" job on %s ", name
.nodename
);
1404 (void) fprintf(pipe
, "couldn't be run\n");
1405 (void) fprintf(pipe
, "%s\n", mesg
);
1406 (void) fprintf(pipe
,
1407 "The error was \"%s\"\n", errmsg(saveerrno
));
1410 case ERR_CANTEXECAT
:
1411 (void) fprintf(pipe
,
1412 "Subject: Couldn't run your \"at\" job\n\n");
1413 (void) fprintf(pipe
, "Your \"at\" job on %s ",
1415 (void) fprintf(pipe
, "couldn't be run\n");
1416 (void) fprintf(pipe
, "%s\n", mesg
);
1417 (void) fprintf(pipe
,
1418 "The error was \"%s\"\n", errmsg(saveerrno
));
1424 (void) pclose(pipe
);
1430 contract_abandon_latest(fork_val
);
1432 if (cron_pid
== getpid()) {
1433 miscpid_insert(fork_val
);
1438 next_field(int lower
, int upper
)
1441 * next_field returns a pointer to a string which holds the next
1442 * field of a line of a crontab file.
1443 * if (numbers in this field are out of range (lower..upper),
1444 * or there is a syntax error) then
1445 * NULL is returned, and a mail message is sent to the
1446 * user telling them which line the error was in.
1450 int num
, num2
, start
;
1452 while ((line
[cursor
] == ' ') || (line
[cursor
] == '\t'))
1455 if (line
[cursor
] == '\0') {
1458 if (line
[cursor
] == '*') {
1460 if ((line
[cursor
] != ' ') && (line
[cursor
] != '\t'))
1463 (void) strcpy(s
, "*");
1467 if (!isdigit(line
[cursor
]))
1471 num
= num
*10 + (line
[cursor
]-'0');
1472 } while (isdigit(line
[++cursor
]));
1473 if ((num
< lower
) || (num
> upper
))
1475 if (line
[cursor
] == '-') {
1476 if (!isdigit(line
[++cursor
]))
1480 num2
= num2
*10 + (line
[cursor
]-'0');
1481 } while (isdigit(line
[++cursor
]));
1482 if ((num2
< lower
) || (num2
> upper
))
1485 if ((line
[cursor
] == ' ') || (line
[cursor
] == '\t'))
1487 if (line
[cursor
] == '\0')
1489 if (line
[cursor
++] != ',')
1492 s
= xmalloc(cursor
-start
+ 1);
1493 (void) strncpy(s
, line
+ start
, cursor
-start
);
1494 s
[cursor
-start
] = '\0';
1498 #define tm_cmp(t1, t2) (\
1499 (t1)->tm_year == (t2)->tm_year && \
1500 (t1)->tm_mon == (t2)->tm_mon && \
1501 (t1)->tm_mday == (t2)->tm_mday && \
1502 (t1)->tm_hour == (t2)->tm_hour && \
1503 (t1)->tm_min == (t2)->tm_min)
1505 #define tm_setup(tp, yr, mon, dy, hr, min, dst) \
1506 (tp)->tm_year = yr; \
1507 (tp)->tm_mon = mon; \
1508 (tp)->tm_mday = dy; \
1509 (tp)->tm_hour = hr; \
1510 (tp)->tm_min = min; \
1511 (tp)->tm_isdst = dst; \
1513 (tp)->tm_wday = 0; \
1517 * modification for bugid 1104537. the second argument to next_time is
1518 * now the value of time(2) to be used. if this is 0, then use the
1519 * current time. otherwise, the second argument is the time from which to
1520 * calculate things. this is useful to correct situations where you've
1521 * gone backwards in time (I.e. the system's internal clock is correcting
1522 * itself backwards).
1528 tz_next_time(struct event
*e
, time_t tflag
)
1531 * returns the integer time for the next occurance of event e.
1532 * the following fields have ranges as indicated:
1533 * PRGM | min hour day of month mon day of week
1534 * ------|-------------------------------------------------------
1535 * cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday)
1536 * time | 0-59 0-23 1-31 0-11 0-6 (0=sunday)
1537 * NOTE: this routine is hard to understand.
1540 struct tm
*tm
, ref_tm
, tmp
, tmp1
, tmp2
;
1541 int tm_mon
, tm_mday
, tm_wday
, wday
, m
, min
, h
, hr
, carry
, day
, days
;
1542 int d1
, day1
, carry1
, d2
, day2
, carry2
, daysahead
, mon
, yr
, db
, wd
;
1544 time_t t
, ref_t
, t1
, t2
, zone_start
;
1546 extern int days_btwn(int, int, int, int, int, int);
1549 t
= time(NULL
); /* original way of doing things */
1554 tm
= &ref_tm
; /* use a local variable and call localtime_r() */
1555 ref_t
= t
; /* keep a copy of the reference time */
1560 (void) localtime_r(&t
, tm
);
1564 tmp
.tm_isdst
= (tm
->tm_isdst
> 0 ? 0 : 1);
1567 * see if we will have timezone switch over, and clock will
1568 * fall back. zone_start will hold the time when it happens
1569 * (ie time of PST -> PDT switch over).
1571 if (tm
->tm_isdst
!= tmp
.tm_isdst
&&
1572 (t1
- t
) == (timezone
- altzone
) &&
1574 zone_start
= get_switching_time(tmp
.tm_isdst
, t
);
1579 tm_mon
= next_ge(tm
->tm_mon
+ 1, e
->of
.ct
.month
) - 1; /* 0-11 */
1580 tm_mday
= next_ge(tm
->tm_mday
, e
->of
.ct
.daymon
); /* 1-31 */
1581 tm_wday
= next_ge(tm
->tm_wday
, e
->of
.ct
.dayweek
); /* 0-6 */
1583 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0 && tm
->tm_wday
!= tm_wday
) ||
1584 (strcmp(e
->of
.ct
.dayweek
, "*") == 0 && tm
->tm_mday
!= tm_mday
) ||
1585 (tm
->tm_mday
!= tm_mday
&& tm
->tm_wday
!= tm_wday
) ||
1586 (tm
->tm_mon
!= tm_mon
)) {
1589 m
= tm
->tm_min
+ (t
== ref_t
? 1 : 0);
1590 if ((tm
->tm_hour
+ 1) <= next_ge(tm
->tm_hour
, e
->of
.ct
.hour
)) {
1593 min
= next_ge(m
%60, e
->of
.ct
.minute
);
1594 carry
= (min
< m
) ? 1 : 0;
1595 h
= tm
->tm_hour
+ carry
;
1596 hr
= next_ge(h
%24, e
->of
.ct
.hour
);
1597 carry
= (hr
< h
) ? 1 : 0;
1599 if (carry
== 0 && today
) {
1600 /* this event must occur today */
1601 tm_setup(&tmp
, tm
->tm_year
, tm
->tm_mon
, tm
->tm_mday
,
1602 hr
, min
, tm
->tm_isdst
);
1604 if ((t1
= xmktime(&tmp1
)) == (time_t)-1) {
1607 if (daylight
&& tmp
.tm_isdst
!= tmp1
.tm_isdst
) {
1608 /* In case we are falling back */
1610 /* we may need to run the job once more. */
1616 * In case we are not in falling back period,
1617 * calculate the time assuming the DST. If the
1618 * date/time is not altered by mktime, it is the
1619 * time to execute the job.
1622 tmp2
.tm_isdst
= tmp1
.tm_isdst
;
1623 if ((t1
= xmktime(&tmp2
)) == (time_t)-1) {
1626 if (tmp1
.tm_isdst
== tmp2
.tm_isdst
&&
1627 tm_cmp(&tmp
, &tmp2
)) {
1629 * We got a valid time.
1634 * If the date does not match even if
1635 * we assume the alternate timezone, then
1636 * it must be the invalid time. eg
1637 * 2am while switching 1:59am to 3am.
1638 * t1 should point the time before the
1639 * switching over as we've calculate the
1640 * time with assuming alternate zone.
1642 if (tmp1
.tm_isdst
!= tmp2
.tm_isdst
) {
1643 t
= get_switching_time(tmp1
.tm_isdst
,
1646 /* does this really happen? */
1647 t
= get_switching_time(tmp1
.tm_isdst
,
1648 t1
- abs(timezone
- altzone
));
1650 if (t
== (time_t)-1) {
1656 if (tm_cmp(&tmp
, &tmp1
)) {
1657 /* got valid time */
1661 * This should never happen, but just in
1662 * case, we fall back to the old code.
1664 if (tm
->tm_min
> min
) {
1665 t
+= (time_t)(hr
-tm
->tm_hour
-1) * HOUR
+
1666 (time_t)(60-tm
->tm_min
+ min
) * MINUTE
;
1668 t
+= (time_t)(hr
-tm
->tm_hour
) * HOUR
+
1669 (time_t)(min
-tm
->tm_min
) * MINUTE
;
1672 t
-= (time_t)tm
->tm_sec
;
1673 (void) localtime_r(&t
, &tmp
);
1674 if ((tm
->tm_isdst
== 0) && (tmp
.tm_isdst
> 0))
1675 t
-= (timezone
- altzone
);
1676 return ((t
<= ref_t
) ? t1
: t
);
1681 * Job won't run today, however if we have a switch over within
1682 * one hour and we will have one hour time drifting back in this
1683 * period, we may need to run the job one more time if the job was
1684 * set to run on this hour of clock.
1691 min
= next_ge(0, e
->of
.ct
.minute
);
1692 hr
= next_ge(0, e
->of
.ct
.hour
);
1695 * calculate the date of the next occurance of this event, which
1696 * will be on a different day than the current
1699 /* check monthly day specification */
1700 d1
= tm
->tm_mday
+ 1;
1701 day1
= next_ge((d1
-1)%days_in_mon(tm
->tm_mon
, tm
->tm_year
) + 1,
1703 carry1
= (day1
< d1
) ? 1 : 0;
1705 /* check weekly day specification */
1706 d2
= tm
->tm_wday
+ 1;
1707 wday
= next_ge(d2
%7, e
->of
.ct
.dayweek
);
1709 daysahead
= 7 - d2
+ wday
;
1711 daysahead
= wday
- d2
;
1712 day2
= (d1
+ daysahead
-1)%days_in_mon(tm
->tm_mon
, tm
->tm_year
) + 1;
1713 carry2
= (day2
< d1
) ? 1 : 0;
1716 * based on their respective specifications, day1, and day2 give
1717 * the day of the month for the next occurance of this event.
1719 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0) &&
1720 (strcmp(e
->of
.ct
.dayweek
, "*") != 0)) {
1724 if ((strcmp(e
->of
.ct
.daymon
, "*") != 0) &&
1725 (strcmp(e
->of
.ct
.dayweek
, "*") == 0)) {
1731 if ((carry1
&& carry2
) || (tm
->tm_mon
!= tm_mon
)) {
1732 /* event does not occur in this month */
1734 mon
= next_ge(m
%12 + 1, e
->of
.ct
.month
) - 1; /* 0..11 */
1735 carry
= (mon
< m
) ? 1 : 0;
1737 /* recompute day1 and day2 */
1738 day1
= next_ge(1, e
->of
.ct
.daymon
);
1739 db
= days_btwn(tm
->tm_mon
, tm
->tm_mday
, tm
->tm_year
, mon
,
1741 wd
= (tm
->tm_wday
+ db
)%7;
1742 /* wd is the day of the week of the first of month mon */
1743 wday
= next_ge(wd
, e
->of
.ct
.dayweek
);
1745 day2
= 1 + 7 - wd
+ wday
;
1747 day2
= 1 + wday
- wd
;
1748 if ((strcmp(e
->of
.ct
.daymon
, "*") != 0) &&
1749 (strcmp(e
->of
.ct
.dayweek
, "*") == 0))
1751 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0) &&
1752 (strcmp(e
->of
.ct
.dayweek
, "*") != 0))
1754 day
= (day1
< day2
) ? day1
: day2
;
1755 } else { /* event occurs in this month */
1757 if (!carry1
&& !carry2
)
1758 day
= (day1
< day2
) ? day1
: day2
;
1766 * now that we have the min, hr, day, mon, yr of the next event,
1767 * figure out what time that turns out to be.
1769 tm_setup(&tmp
, yr
, mon
, day
, hr
, min
, -1);
1771 if ((t1
= xmktime(&tmp2
)) == (time_t)-1) {
1774 if (tm_cmp(&tmp
, &tmp2
)) {
1776 * mktime returns clock for the current time zone. If the
1777 * target date was in fallback period, it needs to be adjusted
1778 * to the time comes first.
1779 * Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
1780 * mktime returns the time in PST, but 1:30am in PDT comes
1781 * first. So reverse the tm_isdst, and see if we have such
1785 int dst
= tmp2
.tm_isdst
;
1788 tmp2
.tm_isdst
= (dst
> 0 ? 0 : 1);
1789 if ((t2
= xmktime(&tmp2
)) == (time_t)-1) {
1792 if (tm_cmp(&tmp
, &tmp2
)) {
1794 * same time/date found in the opposite zone.
1795 * check the clock to see which comes early.
1797 if (t2
> ref_t
&& t2
< t1
) {
1805 * mktime has set different time/date for the given date.
1806 * This means that the next job is scheduled to be run on the
1807 * invalid time. There are three possible invalid date/time.
1808 * 1. Non existing day of the month. such as April 31th.
1809 * 2. Feb 29th in the non-leap year.
1810 * 3. Time gap during the DST switch over.
1812 d1
= days_in_mon(mon
, yr
);
1813 if ((mon
!= 1 && day
> d1
) || (mon
== 1 && day
> 29)) {
1815 * see if we have got a specific date which
1818 if (strcmp(e
->of
.ct
.dayweek
, "*") == 0 &&
1819 mon
== (next_ge((mon
+ 1)%12 + 1,
1820 e
->of
.ct
.month
) - 1) &&
1821 day
<= next_ge(1, e
->of
.ct
.daymon
)) {
1826 * Since the day has gone invalid, we need to go to
1827 * next month, and recalcuate the first occurrence.
1828 * eg the cron tab such as:
1829 * 0 0 1,15,31 1,2,3,4,5 * /usr/bin....
1830 * 2/31 is invalid, so the next job is 3/1.
1835 tmp2
.tm_mday
= 1; /* 1st day of the month */
1838 tmp2
.tm_year
= yr
+ 1;
1840 tmp2
.tm_mon
= mon
+ 1;
1842 if ((t
= xmktime(&tmp2
)) == (time_t)-1) {
1845 } else if (mon
== 1 && day
> d1
) {
1847 * ie 29th in the non-leap year. Forwarding the
1848 * clock to Feb 29th 00:00 (March 1st), and recalculate
1854 if ((t
= xmktime(&tmp2
)) == (time_t)-1) {
1857 } else if (daylight
) {
1859 * Non existing time, eg 2am PST during summer time
1861 * We need to get the correct isdst which we are
1862 * swithing to, by adding time difference to make sure
1863 * that t2 is in the zone being switched.
1866 t2
+= abs(timezone
- altzone
);
1867 (void) localtime_r(&t2
, &tmp2
);
1868 zone_start
= get_switching_time(tmp2
.tm_isdst
,
1869 t1
- abs(timezone
- altzone
));
1870 if (zone_start
== (time_t)-1) {
1876 * This should never happen, but fall back to the
1879 days
= days_btwn(tm
->tm_mon
,
1880 tm
->tm_mday
, tm
->tm_year
, mon
, day
, yr
);
1881 t
+= (time_t)(23-tm
->tm_hour
)*HOUR
1882 + (time_t)(60-tm
->tm_min
)*MINUTE
1883 + (time_t)hr
*HOUR
+ (time_t)min
*MINUTE
1886 t
-= (time_t)tm
->tm_sec
;
1887 (void) localtime_r(&t
, &tmp
);
1888 if ((tm
->tm_isdst
== 0) && (tmp
.tm_isdst
> 0))
1889 t
-= (timezone
- altzone
);
1890 return (t
<= ref_t
? t1
: t
);
1898 next_time(struct event
*e
, time_t tflag
)
1900 if (e
->of
.ct
.tz
!= NULL
) {
1903 (void) putenv((char *)get_obj(e
->of
.ct
.tz
));
1905 ret
= tz_next_time(e
, tflag
);
1906 (void) putenv(tzone
);
1910 return (tz_next_time(e
, tflag
));
1915 * This returns TOD in time_t that zone switch will happen, and this
1916 * will be called when clock fallback is about to happen.
1917 * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
1918 * will fall back to 1:00 PDT. So this function will be called only
1919 * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
1920 * First goes through the common time differences to see if zone
1921 * switch happens at those minutes later. If not, check every minutes
1922 * until 6 hours ahead see if it happens(We might have 45minutes
1926 get_switching_time(int to_dst
, time_t t_ref
)
1929 struct tm tmp
, tmp1
;
1930 int hints
[] = { 60, 120, 30, 90, 0}; /* minutes */
1933 (void) localtime_r(&t_ref
, &tmp
);
1937 if ((t
= xmktime(&tmp1
)) == (time_t)-1)
1938 return ((time_t)-1);
1941 for (i
= 0; hints
[i
] != 0; i
++) {
1942 t1
= t
+ hints
[i
] * 60;
1943 (void) localtime_r(&t1
, &tmp1
);
1944 if (tmp1
.tm_isdst
== to_dst
) {
1946 (void) localtime_r(&t1
, &tmp1
);
1947 if (tmp1
.tm_isdst
!= to_dst
) {
1953 /* ugly, but don't know other than this. */
1956 if ((t
= xmktime(&tmp1
)) == (time_t)-1)
1957 return ((time_t)-1);
1958 while (t
< (t_ref
+ 6*60*60)) { /* 6 hours should be enough */
1959 t
+= 60; /* at least one minute, I assume */
1960 (void) localtime_r(&t
, &tmp
);
1961 if (tmp
.tm_isdst
== to_dst
)
1964 return ((time_t)-1);
1968 xmktime(struct tm
*tmp
)
1972 if ((ret
= mktime(tmp
)) == (time_t)-1) {
1973 if (errno
== EOVERFLOW
) {
1974 return ((time_t)-1);
1976 crabort("internal error: mktime failed",
1977 REMOVE_FIFO
|CONSOLE_MSG
);
1985 next_ge(int current
, char *list
)
1988 * list is a character field as in a crontab file;
1989 * for example: "40, 20, 50-10"
1990 * next_ge returns the next number in the list that is
1991 * greater than or equal to current. if no numbers of list
1992 * are >= current, the smallest element of list is returned.
1993 * NOTE: current must be in the appropriate range.
1997 int n
, n2
, min
, min_gt
;
1999 if (strcmp(list
, "*") == 0)
2005 if ((n
= (int)num(&ptr
)) == current
)
2009 if ((n
> current
) && (n
< min_gt
))
2013 if ((n2
= (int)num(&ptr
)) > n
) {
2014 if ((current
> n
) && (current
<= n2
))
2016 } else { /* range that wraps around */
2027 if (min_gt
!= DUMMY
)
2034 free_if_unused(struct usr
*u
)
2036 struct usr
*cur
, *prev
;
2038 * To make sure a usr structure is idle we must check that
2039 * there are no at jobs queued for the user; the user does
2040 * not have a crontab, and also that there are no running at
2041 * or cron jobs (since the runinfo structure also has a
2042 * pointer to the usr structure).
2044 if (!u
->ctexists
&& u
->atevents
== NULL
&&
2045 u
->cruncnt
== 0 && u
->aruncnt
== 0) {
2047 (void) fprintf(stderr
, "%s removed from usr list\n", u
->name
);
2049 for (cur
= uhead
, prev
= NULL
;
2051 prev
= cur
, cur
= cur
->nextusr
) {
2060 prev
->nextusr
= u
->nextusr
;
2068 del_atjob(char *name
, char *usrname
)
2071 struct event
*e
, *eprev
;
2074 if ((u
= find_usr(usrname
)) == NULL
)
2079 if (strcmp(name
, e
->cmd
) == 0) {
2080 if (next_event
== e
)
2083 u
->atevents
= e
->link
;
2085 eprev
->link
= e
->link
;
2086 el_remove(e
->of
.at
.eventid
, 1);
2100 del_ctab(char *name
)
2105 if ((u
= find_usr(name
)) == NULL
)
2108 el_remove(u
->ctid
, 0);
2116 rm_ctevents(struct usr
*u
)
2118 struct event
*e2
, *e3
;
2121 * see if the next event (to be run by cron) is a cronevent
2122 * owned by this user.
2125 if ((next_event
!= NULL
) &&
2126 (next_event
->etype
== CRONEVENT
) &&
2127 (next_event
->u
== u
)) {
2131 while (e2
!= NULL
) {
2133 rel_shared(e2
->of
.ct
.tz
);
2134 rel_shared(e2
->of
.ct
.shell
);
2135 rel_shared(e2
->of
.ct
.home
);
2136 free(e2
->of
.ct
.minute
);
2137 free(e2
->of
.ct
.hour
);
2138 free(e2
->of
.ct
.daymon
);
2139 free(e2
->of
.ct
.month
);
2140 free(e2
->of
.ct
.dayweek
);
2141 free(e2
->of
.ct
.input
);
2151 find_usr(char *uname
)
2157 if (strcmp(u
->name
, uname
) == 0)
2165 * Execute cron command or at/batch job.
2166 * If ever a premature return is added to this function pay attention to
2167 * free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
2177 char *at_cmdfile
= NULL
;
2181 struct project proj
, *pproj
= NULL
;
2184 char buf
[PROJECT_BUFSZ
];
2185 char buf2
[PROJECT_BUFSZ
];
2187 char error
[CANT_STR_LEN
+ PATH_MAX
];
2197 qp
= &qt
[e
->etype
]; /* set pointer to queue defs */
2198 if (qp
->nrun
>= qp
->njob
) {
2199 msg("%c queue max run limit reached", e
->etype
+ 'a');
2204 rp
= rinfo_get(0); /* allocating a new runinfo struct */
2207 * the tempnam() function uses malloc(3C) to allocate space for the
2208 * constructed file name, and returns a pointer to this area, which
2209 * is assigned to rp->outfile. Here rp->outfile is not overwritten.
2212 rp
->outfile
= tempnam(TMPDIR
, PFX
);
2213 rp
->jobtype
= e
->etype
;
2214 if (e
->etype
== CRONEVENT
) {
2215 rp
->jobname
= xmalloc(strlen(e
->cmd
) + 1);
2216 (void) strcpy(rp
->jobname
, e
->cmd
);
2217 /* "cron" jobs only produce mail if there's output */
2218 rp
->mailwhendone
= 0;
2220 at_cmdfile
= xmalloc(strlen(ATDIR
) + strlen(e
->cmd
) + 2);
2221 (void) sprintf(at_cmdfile
, "%s/%s", ATDIR
, e
->cmd
);
2222 if ((atcmdfp
= fopen(at_cmdfile
, "r")) == NULL
) {
2223 if (errno
== ENAMETOOLONG
) {
2224 if (chdir(ATDIR
) == 0)
2225 cron_unlink(e
->cmd
);
2227 cron_unlink(at_cmdfile
);
2229 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECAT
);
2234 rp
->jobname
= xmalloc(strlen(at_cmdfile
) + 1);
2235 (void) strcpy(rp
->jobname
, at_cmdfile
);
2238 * Skip over the first two lines.
2240 (void) fscanf(atcmdfp
, "%*[^\n]\n");
2241 (void) fscanf(atcmdfp
, "%*[^\n]\n");
2242 if (fscanf(atcmdfp
, ": notify by mail: %3s%*[^\n]\n",
2245 * Check to see if we should always send mail
2248 rp
->mailwhendone
= (strcmp(mailvar
, "yes") == 0);
2250 rp
->mailwhendone
= 0;
2253 if (fscanf(atcmdfp
, "\n: project: %d\n", &projid
) == 1) {
2256 (void) fclose(atcmdfp
);
2260 * we make sure that the system time
2261 * hasn't drifted backwards. if it has, el_add() is now
2262 * called, to make sure that the event queue is back in order,
2263 * and we set the delayed flag. cron will pick up the request
2264 * later on at the proper time.
2266 dhltime
= time(NULL
);
2267 if ((dhltime
- e
->time
) < 0) {
2268 msg("clock time drifted backwards!\n");
2269 if (next_event
->etype
== CRONEVENT
) {
2270 msg("correcting cron event\n");
2271 next_event
->time
= next_time(next_event
, dhltime
);
2272 switch (el_add(next_event
, next_event
->time
,
2273 (next_event
->u
)->ctid
)) {
2275 ignore_msg("ex", "cron", next_event
);
2277 case -2: /* event time lower than init time */
2281 } else { /* etype == ATEVENT */
2282 msg("correcting batch event\n");
2283 if (el_add(next_event
, next_event
->time
,
2284 next_event
->of
.at
.eventid
) < 0) {
2285 ignore_msg("ex", "at", next_event
);
2295 if ((rfork
= fork()) == (pid_t
)-1) {
2297 if ((rfork
= fork()) == (pid_t
)-1) {
2306 if (rfork
) { /* parent process */
2307 contract_abandon_latest(rfork
);
2312 if (e
->etype
!= CRONEVENT
)
2317 logit(BCHAR
, rp
, 0);
2324 contract_clear_template();
2326 if (e
->etype
!= CRONEVENT
) {
2327 /* open jobfile as stdin to shell */
2328 if (stat(at_cmdfile
, &buf
)) {
2329 if (errno
== ENAMETOOLONG
) {
2330 if (chdir(ATDIR
) == 0)
2331 cron_unlink(e
->cmd
);
2333 cron_unlink(at_cmdfile
);
2334 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECCRON
);
2337 if (!(buf
.st_mode
&ISUID
)) {
2339 * if setuid bit off, original owner has
2340 * given this file to someone else
2342 cron_unlink(at_cmdfile
);
2345 if ((fd
= open(at_cmdfile
, O_RDONLY
)) == -1) {
2346 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECCRON
);
2347 cron_unlink(at_cmdfile
);
2355 * retrieve the project id of the at job and convert it
2356 * to a project name. fail if it's not a valid project
2357 * or if the user isn't a member of the project.
2359 if (projflag
== 1) {
2360 if ((pproj
= getprojbyid(projid
, &proj
,
2361 (void *)&bufs
.p
.buf
,
2362 sizeof (bufs
.p
.buf
))) == NULL
||
2363 !inproj(e
->u
->name
, pproj
->pj_name
,
2364 bufs
.p
.buf2
, sizeof (bufs
.p
.buf2
))) {
2365 cron_unlink(at_cmdfile
);
2366 mail((e
->u
)->name
, BADPROJID
, ERR_CANTEXECAT
);
2373 * Put process in a new session, and create a new task.
2376 msg("setsid failed with errno = %d. job failed (%s)"
2377 " for user %s", errno
, e
->cmd
, e
->u
->name
);
2378 if (e
->etype
!= CRONEVENT
)
2379 cron_unlink(at_cmdfile
);
2384 * set correct user identification and check their account
2386 r
= set_user_cred(e
->u
, pproj
);
2387 if (r
== VUC_EXPIRED
) {
2388 msg("user (%s) account is expired", e
->u
->name
);
2389 audit_cron_user_acct_expired(e
->u
->name
);
2390 clean_out_user(e
->u
);
2393 if (r
== VUC_NEW_AUTH
) {
2394 msg("user (%s) password has expired", e
->u
->name
);
2395 audit_cron_user_acct_expired(e
->u
->name
);
2396 clean_out_user(e
->u
);
2400 msg("bad user (%s)", e
->u
->name
);
2401 audit_cron_bad_user(e
->u
->name
);
2402 clean_out_user(e
->u
);
2406 * check user and initialize the supplementary group access list.
2407 * bugid 1230784: deleted from parent to avoid cron hang. Now
2408 * only child handles the call.
2411 if (verify_user_cred(e
->u
) != VUC_OK
||
2412 setgid(e
->u
->gid
) == -1 ||
2413 initgroups(e
->u
->name
, e
->u
->gid
) == -1) {
2414 msg("bad user (%s) or setgid failed (%s)",
2415 e
->u
->name
, e
->u
->name
);
2416 audit_cron_bad_user(e
->u
->name
);
2417 clean_out_user(e
->u
);
2421 if ((e
->u
)->uid
== 0) { /* set default path */
2422 /* path settable in defaults file */
2423 envinit
[2] = supath
;
2428 if (e
->etype
!= CRONEVENT
) {
2429 r
= audit_cron_session(e
->u
->name
, NULL
,
2430 e
->u
->uid
, e
->u
->gid
, at_cmdfile
);
2431 cron_unlink(at_cmdfile
);
2433 r
= audit_cron_session(e
->u
->name
, CRONDIR
,
2434 e
->u
->uid
, e
->u
->gid
, NULL
);
2437 msg("cron audit problem. job failed (%s) for user %s",
2438 e
->cmd
, e
->u
->name
);
2442 audit_cron_new_job(e
->cmd
, e
->etype
, (void *)e
);
2444 if (setuid(e
->u
->uid
) == -1) {
2445 msg("setuid failed (%s)", e
->u
->name
);
2446 clean_out_user(e
->u
);
2450 if (e
->etype
== CRONEVENT
) {
2451 /* check for standard input to command */
2452 if (e
->of
.ct
.input
!= NULL
) {
2453 if ((tmpfile
= strdup(TMPINFILE
)) == NULL
) {
2454 mail((e
->u
)->name
, MALLOCERR
,
2458 if ((fd
= mkstemp(tmpfile
)) == -1 ||
2459 (fptr
= fdopen(fd
, "w")) == NULL
) {
2460 mail((e
->u
)->name
, NOSTDIN
,
2462 cron_unlink(tmpfile
);
2466 if ((fwrite(e
->of
.ct
.input
, sizeof (char),
2467 strlen(e
->of
.ct
.input
), fptr
)) !=
2468 strlen(e
->of
.ct
.input
)) {
2469 mail((e
->u
)->name
, NOSTDIN
, ERR_CANTEXECCRON
);
2470 cron_unlink(tmpfile
);
2473 (void) fclose(fptr
);
2476 if (fseek(fptr
, (off_t
)0, SEEK_SET
) != -1) {
2482 cron_unlink(tmpfile
);
2484 (void) fclose(fptr
);
2485 } else if ((fd
= open("/dev/null", O_RDONLY
)) > 0) {
2491 /* redirect stdout and stderr for the shell */
2492 if ((fd
= open(rp
->outfile
, O_WRONLY
|O_CREAT
|O_EXCL
, OUTMODE
)) == 1)
2493 fd
= open("/dev/null", O_WRONLY
);
2495 if (fd
>= 0 && fd
!= 1)
2498 if (fd
>= 0 && fd
!= 2) {
2504 if (e
->etype
== CRONEVENT
&& e
->of
.ct
.home
!= NULL
) {
2505 home
= (char *)get_obj(e
->of
.ct
.home
);
2507 home
= (e
->u
)->home
;
2509 (void) strlcat(homedir
, home
, sizeof (homedir
));
2510 (void) strlcat(logname
, (e
->u
)->name
, sizeof (logname
));
2512 if (chdir(home
) == -1) {
2513 snprintf(bufs
.error
, sizeof (bufs
.error
), CANTCDHOME
, home
);
2514 mail((e
->u
)->name
, bufs
.error
,
2515 e
->etype
== CRONEVENT
? ERR_CANTEXECCRON
:
2523 * make sure that all file descriptors EXCEPT 0, 1 and 2
2528 if ((e
->u
)->uid
!= 0)
2529 (void) nice(qp
->nice
);
2530 if (e
->etype
== CRONEVENT
) {
2532 (void) putenv((char *)get_obj(e
->of
.ct
.tz
));
2534 if (e
->of
.ct
.shell
) {
2537 sh
= (char *)get_obj(e
->of
.ct
.shell
);
2538 name
= strrchr(sh
, '/');
2545 sh
+= strlen(ENV_SHELL
);
2546 (void) execl(sh
, name
, "-c", e
->cmd
, 0);
2548 (void) execl(SHELL
, "sh", "-c", e
->cmd
, 0);
2551 } else { /* type == ATEVENT */
2552 (void) execl(SHELL
, "sh", 0);
2555 snprintf(bufs
.error
, sizeof (bufs
.error
), CANTEXECSH
, sh
);
2556 mail((e
->u
)->name
, bufs
.error
,
2557 e
->etype
== CRONEVENT
? ERR_CANTEXECCRON
: ERR_CANTEXECAT
);
2564 * When timed out to run the job, return 0.
2565 * If for some reasons we need to reschedule jobs, return 1.
2575 if (msg_wait(t
) != 0) {
2576 /* we need to run next job immediately */
2583 /* We got THAW or REFRESH message */
2588 if (last_time
> now
) {
2589 /* clock has been reset to backward */
2593 if (next_event
== NULL
&& !el_empty()) {
2594 next_event
= (struct event
*)el_first();
2597 if (next_event
== NULL
)
2600 t
= (long)next_event
->time
- now
;
2606 * This used to be in the idle(), but moved to the separate function.
2607 * This called from various place when cron needs to reap the
2608 * child. It includes the situation that cron hit maxrun, and needs
2609 * to reschedule the job.
2619 pid
= waitpid((pid_t
)-1, &prc
, WNOHANG
);
2624 "wait returned %x for process %d\n", prc
, pid
);
2626 if ((rp
= rinfo_get(pid
)) == NULL
) {
2627 if (miscpid_delete(pid
) == 0) {
2628 /* not found in anywhere */
2631 } else if (rp
->que
== ZOMB
) {
2632 (void) unlink(rp
->outfile
);
2641 cleanup(struct runinfo
*pr
, int rc
)
2647 logit(ECHAR
, pr
, rc
);
2650 if (pr
->que
!= CRONEVENT
)
2655 if (lstat(pr
->outfile
, &buf
) == 0) {
2656 if (!S_ISLNK(buf
.st_mode
) &&
2657 (buf
.st_size
> 0 || pr
->mailwhendone
)) {
2658 /* mail user stdout and stderr */
2660 if ((pr
->pid
= fork()) < 0) {
2662 * if fork fails try forever in doubling
2663 * retry times, up to 16 seconds
2665 (void) sleep(nextfork
);
2667 nextfork
+= nextfork
;
2669 } else if (pr
->pid
== 0) {
2671 contract_clear_template();
2673 mail_result(p
, pr
, buf
.st_size
);
2676 contract_abandon_latest(pr
->pid
);
2682 (void) unlink(pr
->outfile
);
2693 * Mail stdout and stderr of a job to user. Get uid for real user and become
2694 * that person. We do this so that mail won't come from root since this
2695 * could be a security hole. If failure, quit - don't send mail as root.
2698 mail_result(struct usr
*p
, struct runinfo
*pr
, size_t filesize
)
2700 struct passwd
*ruser_ids
;
2703 struct utsname name
;
2707 char *lowname
= (pr
->jobtype
== CRONEVENT
? "cron" : "at");
2709 (void) uname(&name
);
2710 if ((ruser_ids
= getpwnam(p
->name
)) == NULL
)
2712 (void) setuid(ruser_ids
->pw_uid
);
2714 cmd
= xmalloc(strlen(MAIL
) + strlen(p
->name
)+2);
2715 (void) sprintf(cmd
, "%s %s", MAIL
, p
->name
);
2716 mailpipe
= popen(cmd
, "w");
2718 if (mailpipe
== NULL
)
2720 (void) fprintf(mailpipe
, "To: %s\n", p
->name
);
2721 (void) fprintf(mailpipe
, "Subject: %s <%s@%s> %s\n",
2722 (pr
->jobtype
== CRONEVENT
? "Cron" : "At"),
2723 p
->name
, name
.nodename
, pr
->jobname
);
2726 * RFC3834 (Section 5) defines the Auto-Submitted header to prevent
2727 * vacation replies, et al, from being sent in response to
2728 * machine-generated mail.
2730 (void) fprintf(mailpipe
, "Auto-Submitted: auto-generated\n");
2733 * Additional headers for mail filtering and diagnostics:
2735 (void) fprintf(mailpipe
, "X-Mailer: cron (%s %s)\n", name
.sysname
,
2737 (void) fprintf(mailpipe
, "X-Cron-User: %s\n", p
->name
);
2738 (void) fprintf(mailpipe
, "X-Cron-Host: %s\n", name
.nodename
);
2739 (void) fprintf(mailpipe
, "X-Cron-Job-Name: %s\n", pr
->jobname
);
2740 (void) fprintf(mailpipe
, "X-Cron-Job-Type: %s\n", lowname
);
2745 * (Temporary file is fopen'ed with "r", secure open.)
2747 (void) fprintf(mailpipe
, "\n");
2749 (st
= fopen(pr
->outfile
, "r")) != NULL
) {
2750 while ((nbytes
= fread(iobuf
, sizeof (char), BUFSIZ
, st
)) != 0)
2751 (void) fwrite(iobuf
, sizeof (char), nbytes
, mailpipe
);
2754 (void) fprintf(mailpipe
, "Job completed with no output.\n");
2756 (void) pclose(mailpipe
);
2767 struct timespec tout
, *toutp
;
2768 static int pending_msg
;
2769 static time_t pending_reftime
;
2772 process_msg(&msgbuf
, pending_reftime
);
2778 FD_SET(msgfd
, &fds
);
2781 if (tim
!= INFINITY
) {
2782 #ifdef CRON_MAXSLEEP
2784 * CRON_MAXSLEEP can be defined to have cron periodically wake
2785 * up, so that cron can detect a change of TOD and adjust the
2786 * sleep time more frequently.
2788 tim
= (tim
> CRON_MAXSLEEP
) ? CRON_MAXSLEEP
: tim
;
2795 cnt
= pselect(msgfd
+ 1, &fds
, NULL
, NULL
, toutp
, &defmask
);
2796 if (cnt
== -1 && errno
!= EINTR
)
2797 perror("! pselect");
2799 /* pselect timeout or interrupted */
2804 if ((cnt
= read(msgfd
, &msg
, sizeof (msg
))) != sizeof (msg
)) {
2805 if (cnt
!= -1 || errno
!= EAGAIN
)
2809 reftime
= time(NULL
);
2810 if (next_event
!= NULL
&& reftime
>= next_event
->time
) {
2812 * we need to run the job before reloading crontab.
2814 (void) memcpy(&msgbuf
, &msg
, sizeof (msg
));
2816 pending_reftime
= reftime
;
2819 process_msg(&msg
, reftime
);
2824 * process the message supplied via pipe. This will be called either
2825 * immediately after cron read the message from pipe, or idle time
2826 * if the message was pending due to the job execution.
2829 process_msg(struct message
*pmsg
, time_t reftime
)
2831 if (pmsg
->etype
== 0)
2834 switch (pmsg
->etype
) {
2836 if (pmsg
->action
== DELETE
)
2837 del_atjob(pmsg
->fname
, pmsg
->logname
);
2839 mod_atjob(pmsg
->fname
, (time_t)0);
2842 if (pmsg
->action
== DELETE
)
2843 del_ctab(pmsg
->fname
);
2845 mod_ctab(pmsg
->fname
, reftime
);
2852 msg("message received - bad format");
2855 if (next_event
!= NULL
) {
2856 if (next_event
->etype
== CRONEVENT
) {
2857 switch (el_add(next_event
, next_event
->time
,
2858 (next_event
->u
)->ctid
)) {
2860 ignore_msg("process_msg", "cron", next_event
);
2862 case -2: /* event time lower than init time */
2866 } else { /* etype == ATEVENT */
2867 if (el_add(next_event
, next_event
->time
,
2868 next_event
->of
.at
.eventid
) < 0) {
2869 ignore_msg("process_msg", "at", next_event
);
2874 (void) fflush(stdout
);
2879 * Allocate a new or find an existing runinfo structure
2881 static struct runinfo
*
2882 rinfo_get(pid_t pid
)
2886 if (pid
== 0) { /* allocate a new entry */
2887 rp
= xcalloc(1, sizeof (struct runinfo
));
2888 rp
->next
= rthead
; /* link the entry into the list */
2892 /* search the list for an existing entry */
2893 for (rp
= rthead
; rp
!= NULL
; rp
= rp
->next
) {
2901 * Free a runinfo structure and its associated memory
2904 rinfo_free(struct runinfo
*entry
)
2906 struct runinfo
**rpp
;
2910 (void) fprintf(stderr
, "freeing job %s\n", entry
->jobname
);
2912 for (rpp
= &rthead
; (rp
= *rpp
) != NULL
; rpp
= &rp
->next
) {
2914 *rpp
= rp
->next
; /* unlink the entry */
2925 thaw_handler(int sig
)
2935 crabort("SIGTERM", REMOVE_FIFO
);
2940 child_handler(int sig
)
2946 child_sigreset(void)
2948 (void) signal(SIGCLD
, SIG_DFL
);
2949 (void) sigprocmask(SIG_SETMASK
, &defmask
, NULL
);
2953 * crabort() - handle exits out of cron
2956 crabort(char *mssg
, int action
)
2960 if (action
& REMOVE_FIFO
) {
2961 /* FIFO vanishes when cron finishes */
2962 if (unlink(FIFO
) < 0)
2963 perror("cron could not unlink FIFO");
2966 if (action
& CONSOLE_MSG
) {
2967 /* write error msg to console */
2968 if ((c
= open(CONSOLE
, O_WRONLY
)) >= 0) {
2969 (void) write(c
, "cron aborted: ", 14);
2970 (void) write(c
, mssg
, strlen(mssg
));
2971 (void) write(c
, "\n", 1);
2976 /* always log the message */
2978 msg("******* CRON ABORTED ********");
2983 * msg() - time-stamped error reporting function
2994 (void) fflush(stdout
);
2996 (void) fprintf(stderr
, "! ");
2998 va_start(args
, fmt
);
2999 (void) vfprintf(stderr
, fmt
, args
);
3002 (void) strftime(timebuf
, sizeof (timebuf
), FORMAT
, localtime(&t
));
3003 (void) fprintf(stderr
, " %s\n", timebuf
);
3005 (void) fflush(stderr
);
3009 ignore_msg(char *func_name
, char *job_type
, struct event
*event
)
3011 msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)",
3012 func_name
, job_type
,
3013 event
->u
->name
? event
->u
->name
: "unknown",
3014 event
->cmd
? event
->cmd
: "unknown",
3019 logit(int cc
, struct runinfo
*rp
, int rc
)
3029 (void) printf("%c CMD: %s\n", cc
, next_event
->cmd
);
3030 (void) strftime(timebuf
, sizeof (timebuf
), FORMAT
, localtime(&t
));
3031 (void) printf("%c %s %u %c %s",
3032 cc
, (rp
->rusr
)->name
, rp
->pid
, QUE(rp
->que
), timebuf
);
3033 if ((ret
= TSTAT(rc
)) != 0)
3034 (void) printf(" ts=%d", ret
);
3035 if ((ret
= RCODE(rc
)) != 0)
3036 (void) printf(" rc=%d", ret
);
3037 (void) putchar('\n');
3038 (void) fflush(stdout
);
3046 /* run job at a later time */
3047 nt
= next_event
->time
+ delay
;
3048 if (next_event
->etype
== CRONEVENT
) {
3049 next_event
->time
= next_time(next_event
, (time_t)0);
3050 if (nt
< next_event
->time
)
3051 next_event
->time
= nt
;
3052 switch (el_add(next_event
, next_event
->time
,
3053 (next_event
->u
)->ctid
)) {
3055 ignore_msg("resched", "cron", next_event
);
3057 case -2: /* event time lower than init time */
3062 msg("rescheduling a cron job");
3065 add_atevent(next_event
->u
, next_event
->cmd
, nt
, next_event
->etype
);
3066 msg("rescheduling at job");
3077 /* set up default queue definitions */
3078 for (i
= 0; i
< NQUEUE
; i
++) {
3079 qt
[i
].njob
= qd
.njob
;
3080 qt
[i
].nice
= qd
.nice
;
3081 qt
[i
].nwait
= qd
.nwait
;
3083 if (action
== DEFAULT
)
3085 if ((fd
= fopen(QUEDEFS
, "r")) == NULL
) {
3086 msg("cannot open quedefs file");
3087 msg("using default queue definitions");
3090 while (fgets(qbuf
, QBUFSIZ
, fd
) != NULL
) {
3091 if ((j
= qbuf
[0]-'a') < 0 || j
>= NQUEUE
|| qbuf
[1] != '.')
3094 qt
[j
].njob
= qq
.njob
;
3095 qt
[j
].nice
= qq
.nice
;
3096 qt
[j
].nwait
= qq
.nwait
;
3102 parsqdef(char *name
)
3109 while (isdigit(*name
)) {
3128 * defaults - read defaults from /etc/default/cron
3138 * get HZ value for environment
3140 if ((hz
= getenv("HZ")) == NULL
)
3141 (void) sprintf(hzname
, "HZ=%d", HZ
);
3143 (void) snprintf(hzname
, sizeof (hzname
), "HZ=%s", hz
);
3145 * get TZ value for environment
3147 (void) snprintf(tzone
, sizeof (tzone
), "TZ=%s",
3148 ((tz
= getenv("TZ")) != NULL
) ? tz
: DEFTZ
);
3150 if (defopen(DEFFILE
) == 0) {
3152 flags
= defcntl(DC_GETFLAGS
, 0);
3153 TURNOFF(flags
, DC_CASE
);
3154 (void) defcntl(DC_SETFLAGS
, flags
);
3156 if (((deflog
= defread("CRONLOG=")) == NULL
) ||
3157 (*deflog
== 'N') || (*deflog
== 'n'))
3161 /* fix for 1087611 - allow paths to be set in defaults file */
3162 if ((Def_path
= defread("PATH=")) != NULL
) {
3163 (void) strlcat(path
, Def_path
, LINE_MAX
);
3165 (void) strlcpy(path
, NONROOTPATH
, LINE_MAX
);
3167 if ((Def_supath
= defread("SUPATH=")) != NULL
) {
3168 (void) strlcat(supath
, Def_supath
, LINE_MAX
);
3170 (void) strlcpy(supath
, ROOTPATH
, LINE_MAX
);
3172 (void) defopen(NULL
);
3177 * Determine if a user entry for a job is still ok. The method used here
3178 * is a lot (about 75x) faster than using setgrent() / getgrent()
3179 * endgrent(). It should be safe because we use the sysconf to determine
3180 * the max, and it tolerates the max being 0.
3184 verify_user_cred(struct usr
*u
)
3187 size_t numUsrGrps
= 0;
3188 size_t numOrigGrps
= 0;
3193 * Maximum number of groups a user may be in concurrently. This
3194 * is a value which we obtain at runtime through a sysconf()
3198 static size_t nGroupsMax
= (size_t)-1;
3201 * Arrays for cron user's group list, constructed at startup to
3202 * be nGroupsMax elements long, used for verifying user
3203 * credentials prior to execution.
3206 static gid_t
*UsrGrps
;
3207 static gid_t
*OrigGrps
;
3209 if ((pw
= getpwnam(u
->name
)) == NULL
)
3210 return (VUC_BADUSER
);
3211 if (u
->home
!= NULL
) {
3212 if (strcmp(u
->home
, pw
->pw_dir
) != 0) {
3214 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
3215 (void) strcpy(u
->home
, pw
->pw_dir
);
3218 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
3219 (void) strcpy(u
->home
, pw
->pw_dir
);
3221 if (u
->uid
!= pw
->pw_uid
)
3222 u
->uid
= pw
->pw_uid
;
3223 if (u
->gid
!= pw
->pw_gid
)
3224 u
->gid
= pw
->pw_gid
;
3227 * Create the group id lists needed for job credential
3231 if (nGroupsMax
== (size_t)-1) {
3232 if ((nGroupsMax
= sysconf(_SC_NGROUPS_MAX
)) > 0) {
3233 UsrGrps
= xcalloc(nGroupsMax
, sizeof (gid_t
));
3234 OrigGrps
= xcalloc(nGroupsMax
, sizeof (gid_t
));
3238 (void) fprintf(stderr
, "nGroupsMax = %ld\n", nGroupsMax
);
3243 (void) fprintf(stderr
, "verify_user_cred (%s-%d)\n", pw
->pw_name
,
3245 (void) fprintf(stderr
, "verify_user_cred: pw->pw_gid = %d, "
3246 "u->gid = %d\n", pw
->pw_gid
, u
->gid
);
3249 retval
= (u
->gid
== pw
->pw_gid
) ? VUC_OK
: VUC_NOTINGROUP
;
3251 if (nGroupsMax
> 0) {
3252 numOrigGrps
= getgroups(nGroupsMax
, OrigGrps
);
3254 (void) initgroups(pw
->pw_name
, pw
->pw_gid
);
3255 numUsrGrps
= getgroups(nGroupsMax
, UsrGrps
);
3257 for (i
= 0; i
< numUsrGrps
; i
++) {
3258 if (UsrGrps
[i
] == u
->gid
) {
3265 (void) setgroups(numOrigGrps
, OrigGrps
);
3270 (void) fprintf(stderr
, "verify_user_cred: VUC = %d\n", retval
);
3277 set_user_cred(const struct usr
*u
, struct project
*pproj
)
3279 static char *progname
= "cron";
3280 int r
= 0, rval
= 0;
3282 if ((r
= pam_start(progname
, u
->name
, &pam_conv
, &pamh
))
3285 msg("pam_start returns %d\n", r
);
3288 goto set_eser_cred_exit
;
3291 r
= pam_acct_mgmt(pamh
, 0);
3293 msg("pam_acc_mgmt returns %d\n", r
);
3295 if (r
== PAM_ACCT_EXPIRED
) {
3297 goto set_eser_cred_exit
;
3299 if (r
== PAM_NEW_AUTHTOK_REQD
) {
3300 rval
= VUC_NEW_AUTH
;
3301 goto set_eser_cred_exit
;
3303 if (r
!= PAM_SUCCESS
) {
3305 goto set_eser_cred_exit
;
3308 if (pproj
!= NULL
) {
3309 size_t sz
= sizeof (PROJECT
) + strlen(pproj
->pj_name
);
3310 char *buf
= alloca(sz
);
3312 (void) snprintf(buf
, sz
, PROJECT
"%s", pproj
->pj_name
);
3313 (void) pam_set_item(pamh
, PAM_RESOURCE
, buf
);
3316 r
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
);
3317 if (r
!= PAM_SUCCESS
)
3321 (void) pam_end(pamh
, r
);
3326 clean_out_user(struct usr
*u
)
3328 if (next_event
->u
== u
) {
3333 clean_out_atjobs(u
);
3338 clean_out_atjobs(struct usr
*u
)
3340 struct event
*ev
, *pv
;
3342 for (pv
= NULL
, ev
= u
->atevents
;
3344 pv
= ev
, ev
= ev
->link
, free(pv
)) {
3345 el_remove(ev
->of
.at
.eventid
, 1);
3347 cron_unlink(ev
->cmd
);
3350 if (strlen(ATDIR
) + strlen(ev
->cmd
) + 2
3352 (void) sprintf(buf
, "%s/%s", ATDIR
, ev
->cmd
);
3363 clean_out_ctab(struct usr
*u
)
3366 el_remove(u
->ctid
, 0);
3372 cron_unlink(char *name
)
3377 if (r
== 0 || (r
== -1 && errno
== ENOENT
)) {
3378 (void) audit_cron_delete_anc_file(name
, NULL
);
3383 create_anc_ctab(struct event
*e
)
3385 if (audit_cron_create_anc_file(e
->u
->name
,
3386 (cwd
== CRON
) ? NULL
:CRONDIR
,
3387 e
->u
->name
, e
->u
->uid
) == -1) {
3388 process_anc_files(CRON_ANC_DELETE
);
3389 crabort("cannot create ancillary files for crontabs",
3390 REMOVE_FIFO
|CONSOLE_MSG
);
3395 delete_anc_ctab(struct event
*e
)
3397 (void) audit_cron_delete_anc_file(e
->u
->name
,
3398 (cwd
== CRON
) ? NULL
:CRONDIR
);
3402 create_anc_atjob(struct event
*e
)
3404 if (!e
->of
.at
.exists
)
3407 if (audit_cron_create_anc_file(e
->cmd
,
3408 (cwd
== AT
) ? NULL
:ATDIR
,
3409 e
->u
->name
, e
->u
->uid
) == -1) {
3410 process_anc_files(CRON_ANC_DELETE
);
3411 crabort("cannot create ancillary files for atjobs",
3412 REMOVE_FIFO
|CONSOLE_MSG
);
3417 delete_anc_atjob(struct event
*e
)
3419 if (!e
->of
.at
.exists
)
3422 (void) audit_cron_delete_anc_file(e
->cmd
,
3423 (cwd
== AT
) ? NULL
:ATDIR
);
3428 process_anc_files(int del
)
3430 struct usr
*u
= uhead
;
3433 if (!audit_cron_mode())
3437 if (u
->ctexists
&& u
->ctevents
!= NULL
) {
3444 if ((e
= e
->link
) == NULL
)
3449 if (u
->atevents
!= NULL
) {
3453 delete_anc_atjob(e
);
3455 create_anc_atjob(e
);
3456 if ((e
= e
->link
) == NULL
)
3461 if ((u
= u
->nextusr
) == NULL
)
3468 cron_conv(int num_msg
, struct pam_message
**msgs
,
3469 struct pam_response
**response
, void *appdata_ptr
)
3471 struct pam_message
**m
= msgs
;
3474 for (i
= 0; i
< num_msg
; i
++) {
3475 switch (m
[i
]->msg_style
) {
3478 if (m
[i
]->msg
!= NULL
) {
3479 (void) msg("%s\n", m
[i
]->msg
);
3491 * Cron creates process for other than job. Mail process is the
3492 * one which rinfo does not cover. Therefore, miscpid will keep
3493 * track of the pids executed from cron. Otherwise, we will see
3494 * "unexpected pid returned.." messages appear in the log file.
3497 miscpid_insert(pid_t pid
)
3501 mp
= xmalloc(sizeof (*mp
));
3503 mp
->next
= miscpid_head
;
3508 miscpid_delete(pid_t pid
)
3510 struct miscpid
*mp
, *omp
;
3514 for (mp
= miscpid_head
; mp
!= NULL
; mp
= mp
->next
) {
3515 if (mp
->pid
== pid
) {
3523 omp
->next
= mp
->next
;
3525 miscpid_head
= NULL
;
3532 * Establish contract terms such that all children are in abandoned
3533 * process contracts.
3536 contract_set_template(void)
3540 if ((fd
= open64(CTFS_ROOT
"/process/template", O_RDWR
)) < 0)
3541 crabort("cannot open process contract template",
3542 REMOVE_FIFO
| CONSOLE_MSG
);
3544 if (ct_pr_tmpl_set_param(fd
, 0) ||
3545 ct_tmpl_set_informative(fd
, 0) ||
3546 ct_pr_tmpl_set_fatal(fd
, CT_PR_EV_HWERR
))
3547 crabort("cannot establish contract template terms",
3548 REMOVE_FIFO
| CONSOLE_MSG
);
3550 if (ct_tmpl_activate(fd
))
3551 crabort("cannot activate contract template",
3552 REMOVE_FIFO
| CONSOLE_MSG
);
3558 * Clear active process contract template.
3561 contract_clear_template(void)
3565 if ((fd
= open64(CTFS_ROOT
"/process/template", O_RDWR
)) < 0)
3566 crabort("cannot open process contract template",
3567 REMOVE_FIFO
| CONSOLE_MSG
);
3569 if (ct_tmpl_clear(fd
))
3570 crabort("cannot clear contract template",
3571 REMOVE_FIFO
| CONSOLE_MSG
);
3577 * Abandon latest process contract unconditionally. If we have leaked [some
3578 * critical amount], exit such that the kernel reaps our contracts.
3581 contract_abandon_latest(pid_t pid
)
3585 static uint_t cts_lost
;
3587 if (cts_lost
> MAX_LOST_CONTRACTS
)
3588 crabort("repeated failure to abandon contracts",
3589 REMOVE_FIFO
| CONSOLE_MSG
);
3591 if (r
= contract_latest(&id
)) {
3592 msg("could not obtain latest contract for "
3593 "PID %ld: %s", pid
, strerror(r
));
3598 if (r
= contract_abandon_id(id
)) {
3599 msg("could not abandon latest contract %ld: %s", id
,
3606 static struct shared
*
3607 create_shared(void *obj
, void * (*obj_alloc
)(void *obj
),
3608 void (*obj_free
)(void *))
3612 if ((out
= xmalloc(sizeof (struct shared
))) == NULL
) {
3615 if ((out
->obj
= obj_alloc(obj
)) == NULL
) {
3620 out
->free
= obj_free
;
3625 static struct shared
*
3626 create_shared_str(char *str
)
3628 return (create_shared(str
, (void *(*)(void *))strdup
, free
));
3631 static struct shared
*
3632 dup_shared(struct shared
*obj
)
3641 rel_shared(struct shared
*obj
)
3643 if (obj
&& (--obj
->count
) == 0) {
3644 obj
->free(obj
->obj
);
3650 get_obj(struct shared
*obj
)