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>
28 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
29 /* All Rights Reserved */
31 /* Copyright (c) 1987, 1988 Microsoft Corporation */
32 /* All Rights Reserved */
36 #define __EXTENSIONS__
39 #include <sys/contract/process.h>
41 #include <sys/param.h>
42 #include <sys/resource.h>
46 #include <sys/types.h>
47 #include <sys/utsname.h>
50 #include <security/pam_appl.h>
59 #include <libcontract.h>
60 #include <libcontract_priv.h>
74 #include <libzoneinfo.h>
82 #define MAIL "/usr/bin/mail" /* mail program to use */
83 #define CONSOLE "/dev/console" /* where messages go when cron dies */
85 #define TMPINFILE "/tmp/crinXXXXXX" /* file to put stdin in for cmd */
88 #define TMPOUTFILE "/tmp/croutXXXXXX" /* file to place stdout, stderr */
90 #define INMODE 00400 /* mode for stdin file */
91 #define OUTMODE 00600 /* mode for stdout file */
92 #define ISUID S_ISUID /* mode for verifing at jobs */
94 #define INFINITY 2147483647L /* upper bound on time */
96 #define ZOMB 100 /* proc slot used for mailing output */
110 /* Defined actions for crabort() routine */
111 #define NO_ACTION 000
112 #define REMOVE_FIFO 001
113 #define CONSOLE_MSG 002
115 #define BADCD "can't change directory to the crontab directory."
116 #define NOREADDIR "can't read the crontab directory."
118 #define BADJOBOPEN "unable to read your at job."
119 #define BADSHELL "because your login shell \
120 isn't /usr/bin/sh, you can't use cron."
122 #define BADSTAT "can't access your crontab or at-job file. Resubmit it."
123 #define BADPROJID "can't set project id for your job."
124 #define CANTCDHOME "can't change directory to %s.\
125 \nYour commands will not be executed."
126 #define CANTEXECSH "unable to exec the shell, %s, for one of your \
128 #define CANT_STR_LEN (sizeof (CANTEXECSH) > sizeof (CANTCDHOME) ? \
129 sizeof (CANTEXECSH) : sizeof (CANTCDHOME))
130 #define NOREAD "can't read your crontab file. Resubmit it."
131 #define BADTYPE "crontab or at-job file is not a regular file.\n"
132 #define NOSTDIN "unable to create a standard input file for \
133 one of your crontab commands. \
134 \nThat command was not executed."
136 #define NOTALLOWED "you are not authorized to use cron. Sorry."
137 #define STDERRMSG "\n\n********************************************\
138 *****\nCron: The previous message is the \
139 standard output and standard error \
140 \nof one of your cron commands.\n"
142 #define STDOUTERR "one of your commands generated output or errors, \
143 but cron was unable to mail you this output.\
144 \nRemember to redirect standard output and standard \
145 error for each of your commands."
147 #define CLOCK_DRIFT "clock time drifted backwards after event!\n"
148 #define PIDERR "unexpected pid returned %d (ignored)"
149 #define CRONTABERR "Subject: Your crontab file has an error in it\n\n"
150 #define MALLOCERR "out of space, cannot create new string\n"
152 #define DIDFORK didfork
153 #define NOFORK !didfork
155 #define MAILBUFLEN (8*1024)
157 #define MAILBINITFREE (MAILBUFLEN - (sizeof (cte_intro) - 1) \
158 - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1)
160 #define ERR_CRONTABENT 0 /* error in crontab file entry */
161 #define ERR_UNIXERR 1 /* error in some system call */
162 #define ERR_CANTEXECCRON 2 /* error setting up "cron" job environment */
163 #define ERR_CANTEXECAT 3 /* error setting up "at" job environment */
164 #define ERR_NOTREG 4 /* error not a regular file */
166 #define PROJECT "project="
168 #define MAX_LOST_CONTRACTS 2048 /* reset if this many failed abandons */
170 #define FORMAT "%a %b %e %H:%M:%S %Y"
171 static char timebuf
[80];
173 static struct message msgbuf
;
176 int count
; /* usage count */
177 void (*free
)(void *obj
); /* routine that will free obj */
178 void *obj
; /* object */
182 time_t time
; /* time of the event */
183 short etype
; /* what type of event; 0=cron, 1=at */
184 char *cmd
; /* command for cron, job name for at */
185 struct usr
*u
; /* ptr to the owner (usr) of this event */
186 struct event
*link
; /* ptr to another event for this user */
188 struct { /* for crontab events */
189 char *minute
; /* (these */
190 char *hour
; /* fields */
191 char *daymon
; /* are */
192 char *month
; /* from */
193 char *dayweek
; /* crontab) */
194 char *input
; /* ptr to stdin */
195 struct shared
*tz
; /* timezone of this event */
196 struct shared
*home
; /* directory for this event */
197 struct shared
*shell
; /* shell for this event */
199 struct { /* for at events */
200 short exists
; /* for revising at events */
201 int eventid
; /* for el_remove-ing at events */
207 char *name
; /* name of user (e.g. "root") */
208 char *home
; /* home directory for user */
209 uid_t uid
; /* user id */
210 gid_t gid
; /* group id */
211 int aruncnt
; /* counter for running jobs per uid */
212 int cruncnt
; /* counter for running cron jobs per uid */
213 int ctid
; /* for el_remove-ing crontab events */
214 short ctexists
; /* for revising crontab events */
215 struct event
*ctevents
; /* list of this usr's crontab events */
216 struct event
*atevents
; /* list of this usr's at events */
218 }; /* ptr to next user */
222 int njob
; /* limit */
223 int nice
; /* nice for execution */
224 int nwait
; /* wait time to next execution attempt */
225 int nrun
; /* number running */
227 qd
= {100, 2, 60}, /* default values for queue defs */
229 static struct queue qq
;
231 static struct runinfo
235 struct usr
*rusr
; /* pointer to usr struct */
236 char *outfile
; /* file where stdout & stderr are trapped */
237 short jobtype
; /* what type of event: 0=cron, 1=at */
238 char *jobname
; /* command for "cron", jobname for "at" */
239 int mailwhendone
; /* 1 = send mail even if no ouptut */
240 struct runinfo
*next
;
243 static struct miscpid
{
245 struct miscpid
*next
;
248 static pid_t cron_pid
; /* own pid */
249 static char didfork
= 0; /* flag to see if I'm process group leader */
250 static int msgfd
; /* file descriptor for fifo queue */
251 static int ecid
= 1; /* event class id for el_remove(); MUST be set to 1 */
252 static int delayed
; /* is job being rescheduled or did it run first time */
253 static int cwd
; /* current working directory */
254 static struct event
*next_event
; /* the next event to execute */
255 static struct usr
*uhead
; /* ptr to the list of users */
257 /* Variables for error handling at reading crontabs. */
258 static char cte_intro
[] = "Line(s) with errors:\n\n";
259 static char cte_trail1
[] = "\nMax number of errors encountered.";
260 static char cte_trail2
[] = " Evaluation of crontab aborted.\n";
261 static int cte_free
= MAILBINITFREE
; /* Free buffer space */
262 static char *cte_text
= NULL
; /* Text buffer pointer */
263 static char *cte_lp
; /* Next free line in cte_text */
264 static int cte_nvalid
; /* Valid lines found */
266 /* user's default environment for the shell */
267 #define ROOTPATH "PATH=/usr/sbin:/usr/bin"
268 #define NONROOTPATH "PATH=/usr/bin:"
270 static char *Def_supath
= NULL
;
271 static char *Def_path
= NULL
;
272 static char path
[LINE_MAX
] = "PATH=";
273 static char supath
[LINE_MAX
] = "PATH=";
274 static char homedir
[LINE_MAX
] = ENV_HOME
;
275 static char logname
[LINE_MAX
] = "LOGNAME=";
276 static char tzone
[LINE_MAX
] = ENV_TZ
;
277 static char *envinit
[] = {
286 extern char **environ
;
290 static char hzname
[10];
292 static void cronend(int);
293 static void thaw_handler(int);
294 static void child_handler(int);
295 static void child_sigreset(void);
297 static void mod_ctab(char *, time_t);
298 static void mod_atjob(char *, time_t);
299 static void add_atevent(struct usr
*, char *, time_t, int);
300 static void rm_ctevents(struct usr
*);
301 static void cleanup(struct runinfo
*rn
, int r
);
302 static void crabort(char *, int);
303 static void msg(char *fmt
, ...);
304 static void ignore_msg(char *, char *, struct event
*);
305 static void logit(int, struct runinfo
*, int);
306 static void parsqdef(char *);
307 static void defaults();
308 static void initialize(int);
309 static void quedefs(int);
310 static int idle(long);
311 static struct usr
*find_usr(char *);
312 static int ex(struct event
*e
);
313 static void read_dirs(int);
314 static void mail(char *, char *, int);
315 static char *next_field(int, int);
316 static void readcron(struct usr
*, time_t);
317 static int next_ge(int, char *);
318 static void free_if_unused(struct usr
*);
319 static void del_atjob(char *, char *);
320 static void del_ctab(char *);
321 static void resched(int);
322 static int msg_wait(long);
323 static struct runinfo
*rinfo_get(pid_t
);
324 static void rinfo_free(struct runinfo
*rp
);
325 static void mail_result(struct usr
*p
, struct runinfo
*pr
, size_t filesize
);
326 static time_t next_time(struct event
*, time_t);
327 static time_t get_switching_time(int, time_t);
328 static time_t xmktime(struct tm
*);
329 static void process_msg(struct message
*, time_t);
330 static void reap_child(void);
331 static void miscpid_insert(pid_t
);
332 static int miscpid_delete(pid_t
);
333 static void contract_set_template(void);
334 static void contract_clear_template(void);
335 static void contract_abandon_latest(pid_t
);
337 static void cte_init(void);
338 static void cte_add(int, char *);
339 static void cte_valid(void);
340 static int cte_istoomany(void);
341 static void cte_sendmail(char *);
343 static int set_user_cred(const struct usr
*, struct project
*);
345 static struct shared
*create_shared_str(char *str
);
346 static struct shared
*dup_shared(struct shared
*obj
);
347 static void rel_shared(struct shared
*obj
);
348 static void *get_obj(struct shared
*obj
);
350 * last_time is set immediately prior to exection of an event (via ex())
351 * to indicate the last time an event was executed. This was (surely)
352 * it's original intended use.
354 static time_t last_time
, init_time
, t_old
;
355 static int reset_needed
; /* set to 1 when cron(1M) needs to re-initialize */
358 static sigset_t defmask
, sigmask
;
363 extern int audit_cron_session(char *, char *, uid_t
, gid_t
, char *);
364 extern void audit_cron_new_job(char *, int, void *);
365 extern void audit_cron_bad_user(char *);
366 extern void audit_cron_user_acct_expired(char *);
367 extern int audit_cron_create_anc_file(char *, char *, char *, uid_t
);
368 extern int audit_cron_delete_anc_file(char *, char *);
369 extern int audit_cron_is_anc_name(char *);
370 extern int audit_cron_mode();
372 static int cron_conv(int, struct pam_message
**,
373 struct pam_response
**, void *);
375 static struct pam_conv pam_conv
= {cron_conv
, NULL
};
376 static pam_handle_t
*pamh
; /* Authentication handle */
379 * Function to help check a user's credentials.
382 static int verify_user_cred(struct usr
*u
);
385 * Values returned by verify_user_cred and set_user_cred:
389 #define VUC_BADUSER 1
390 #define VUC_NOTINGROUP 2
391 #define VUC_EXPIRED 3
392 #define VUC_NEW_AUTH 4
395 * Modes of process_anc_files function
397 #define CRON_ANC_DELETE 1
398 #define CRON_ANC_CREATE 0
401 * Functions to remove a user or job completely from the running database.
403 static void clean_out_atjobs(struct usr
*u
);
404 static void clean_out_ctab(struct usr
*u
);
405 static void clean_out_user(struct usr
*u
);
406 static void cron_unlink(char *name
);
407 static void process_anc_files(int);
412 extern void el_init(int, time_t, time_t, int);
413 extern int el_add(void *, time_t, int);
414 extern void el_remove(int, int);
415 extern int el_empty(void);
416 extern void *el_first(void);
417 extern void el_delete(void);
419 static int valid_entry(char *, int);
420 static struct usr
*create_ulist(char *, int);
421 static void init_cronevent(char *, int);
422 static void init_atevent(char *, time_t, int, int);
423 static void update_atevent(struct usr
*, char *, time_t, int);
426 main(int argc
, char *argv
[])
429 time_t ne_time
; /* amt of time until next event execution */
430 time_t newtime
, lastmtime
= 0L;
432 struct event
*e
, *e2
, *eprev
;
435 struct sigaction act
;
438 * reset_needed is set to 1 whenever el_add() finds out that a cron
439 * job is scheduled to be run before the time when cron(1M) daemon
441 * Other cases where a reset is needed is when ex() finds that the
442 * event to be executed is being run at the wrong time, or when idle()
443 * determines that time was reset.
444 * We immediately return to the top of the while (TRUE) loop in
445 * main() where the event list is cleared and rebuilt, and reset_needed
451 * Only the privileged user can run this command.
454 crabort(NOTALLOWED
, 0);
457 (void) setlocale(LC_ALL
, "");
458 /* fork unless 'nofork' is specified */
459 if ((argc
<= 1) || (strcmp(argv
[1], "nofork"))) {
460 if (rfork
= fork()) {
461 if (rfork
== (pid_t
)-1) {
468 (void) setpgrp(); /* detach cron from console */
472 (void) signal(SIGHUP
, SIG_IGN
);
473 (void) signal(SIGINT
, SIG_IGN
);
474 (void) signal(SIGQUIT
, SIG_IGN
);
475 (void) signal(SIGTERM
, cronend
);
479 quedefs(DEFAULT
); /* load default queue definitions */
481 msg("*** cron started *** pid = %d", cron_pid
);
483 /* setup THAW handler */
484 act
.sa_handler
= thaw_handler
;
486 (void) sigemptyset(&act
.sa_mask
);
487 (void) sigaction(SIGTHAW
, &act
, NULL
);
489 /* setup CHLD handler */
490 act
.sa_handler
= child_handler
;
492 (void) sigemptyset(&act
.sa_mask
);
493 (void) sigaddset(&act
.sa_mask
, SIGCLD
);
494 (void) sigaction(SIGCLD
, &act
, NULL
);
496 (void) sigemptyset(&defmask
);
497 (void) sigemptyset(&sigmask
);
498 (void) sigaddset(&sigmask
, SIGCLD
);
499 (void) sigaddset(&sigmask
, SIGTHAW
);
500 (void) sigprocmask(SIG_BLOCK
, &sigmask
, NULL
);
504 for (;;) { /* MAIN LOOP */
506 if ((t_old
> t
) || (t
-last_time
> CUSHION
) || reset_needed
) {
509 * the time was set backwards or forward or
510 * refresh is requested.
513 msg("re-scheduling jobs");
515 msg("time was reset, re-initializing");
535 * reset_needed might have been set in the functions
536 * call path from initialize()
544 if (next_event
== NULL
&& !el_empty()) {
545 next_event
= (struct event
*)el_first();
547 if (next_event
== NULL
) {
550 ne_time
= next_event
->time
- t
;
552 cftime(timebuf
, "%C", &next_event
->time
);
553 (void) fprintf(stderr
, "next_time=%ld %s\n",
554 next_event
->time
, timebuf
);
559 * reset_needed may be set in the functions call path
562 if (idle(ne_time
) || reset_needed
) {
568 if (stat(QUEDEFS
, &buf
)) {
569 msg("cannot stat QUEDEFS file");
570 } else if (lastmtime
!= buf
.st_mtime
) {
572 lastmtime
= buf
.st_mtime
;
575 last_time
= next_event
->time
; /* save execution time */
578 * reset_needed may be set in the functions call path
581 if (ex(next_event
) || reset_needed
) {
586 switch (next_event
->etype
) {
588 /* add cronevent back into the main event list */
595 * check if time(0)< last_time. if so, then the
596 * system clock has gone backwards. to prevent this
597 * job from being started twice, we reschedule this
598 * job for the >>next time after last_time<<, and
599 * then set next_event->time to this. note that
600 * crontab's resolution is 1 minute.
603 if (last_time
> time(NULL
)) {
606 * bump up to next 30 second
610 newtime
= 30 - (last_time
% 30);
611 newtime
+= last_time
;
614 * get the next scheduled event,
615 * not the one that we just
619 next_time(next_event
, newtime
);
623 next_time(next_event
, (time_t)0);
626 cftime(timebuf
, "%C", &next_event
->time
);
627 (void) fprintf(stderr
,
628 "pushing back cron event %s at %ld (%s)\n",
629 next_event
->cmd
, next_event
->time
, timebuf
);
632 switch (el_add(next_event
, next_event
->time
,
633 (next_event
->u
)->ctid
)) {
635 ignore_msg("main", "cron", next_event
);
637 case -2: /* event time lower than init time */
643 /* remove at or batch job from system */
649 e
= (next_event
->u
)->atevents
;
651 if (e
== next_event
) {
653 (e
->u
)->atevents
= e
->link
;
655 eprev
->link
= e
->link
;
673 initialize(int firstpass
)
676 (void) fprintf(stderr
, "in initialize\n");
679 /* for mail(1), make sure messages come from root */
680 if (putenv("LOGNAME=root") != 0) {
681 crabort("cannot expand env variable",
682 REMOVE_FIFO
|CONSOLE_MSG
);
684 if (access(FIFO
, R_OK
) == -1) {
685 if (errno
== ENOENT
) {
686 if (mknod(FIFO
, S_IFIFO
|0600, 0) != 0)
687 crabort("cannot create fifo queue",
688 REMOVE_FIFO
|CONSOLE_MSG
);
691 /* didn't fork... init(1M) is waiting */
695 crabort("cannot access fifo queue",
696 REMOVE_FIFO
|CONSOLE_MSG
);
700 /* didn't fork... init(1M) is waiting */
703 * the wait is painful, but we don't want
704 * init respawning this quickly
707 crabort("cannot start cron; FIFO exists", CONSOLE_MSG
);
711 if ((msgfd
= open(FIFO
, O_RDWR
)) < 0) {
713 crabort("cannot open fifo queue", REMOVE_FIFO
|CONSOLE_MSG
);
716 init_time
= time(NULL
);
717 el_init(8, init_time
, (time_t)(60*60*24), 10);
719 init_time
= time(NULL
);
720 el_init(8, init_time
, (time_t)(60*60*24), 10);
723 * read directories, create users list, and add events to the
724 * main event list. Only zero user list on firstpass.
728 read_dirs(firstpass
);
734 /* stdout is log file */
735 if (freopen(ACCTFILE
, "a", stdout
) == NULL
)
736 (void) fprintf(stderr
, "cannot open %s\n", ACCTFILE
);
738 /* log should be root-only */
739 (void) fchmod(1, S_IRUSR
|S_IWUSR
);
741 /* stderr also goes to ACCTFILE */
742 (void) close(fileno(stderr
));
745 (void) freopen("/dev/null", "r", stdin
);
747 contract_set_template();
760 if (chdir(CRONDIR
) == -1)
761 crabort(BADCD
, REMOVE_FIFO
|CONSOLE_MSG
);
763 if ((dir
= opendir(".")) == NULL
)
764 crabort(NOREADDIR
, REMOVE_FIFO
|CONSOLE_MSG
);
765 while ((dp
= readdir(dir
)) != NULL
) {
766 if (!valid_entry(dp
->d_name
, CRONEVENT
))
768 init_cronevent(dp
->d_name
, first
);
770 (void) closedir(dir
);
772 if (chdir(ATDIR
) == -1) {
773 msg("cannot chdir to at directory");
776 if ((dir
= opendir(".")) == NULL
) {
777 msg("cannot read at at directory");
781 while ((dp
= readdir(dir
)) != NULL
) {
782 if (!valid_entry(dp
->d_name
, ATEVENT
))
785 if (((tim
= num(&ptr
)) == 0) || (*ptr
!= '.'))
790 jobtype
= *ptr
- 'a';
791 if (jobtype
>= NQUEUE
) {
792 cron_unlink(dp
->d_name
);
795 init_atevent(dp
->d_name
, tim
, jobtype
, first
);
797 (void) closedir(dir
);
801 valid_entry(char *name
, int type
)
805 if (strcmp(name
, ".") == 0 ||
806 strcmp(name
, "..") == 0)
809 /* skip over ancillary file names */
810 if (audit_cron_is_anc_name(name
))
813 if (stat(name
, &buf
)) {
814 mail(name
, BADSTAT
, ERR_UNIXERR
);
818 if (!S_ISREG(buf
.st_mode
)) {
819 mail(name
, BADTYPE
, ERR_NOTREG
);
823 if (type
== ATEVENT
) {
824 if (!(buf
.st_mode
& ISUID
)) {
833 create_ulist(char *name
, int type
)
837 u
= xcalloc(1, sizeof (struct usr
));
838 u
->name
= xstrdup(name
);
839 if (type
== CRONEVENT
) {
854 init_cronevent(char *name
, int first
)
859 u
= create_ulist(name
, CRONEVENT
);
862 if ((u
= find_usr(name
)) == NULL
) {
863 u
= create_ulist(name
, CRONEVENT
);
868 el_remove(u
->ctid
, 0);
875 init_atevent(char *name
, time_t tim
, int jobtype
, int first
)
880 u
= create_ulist(name
, ATEVENT
);
881 add_atevent(u
, name
, tim
, jobtype
);
883 if ((u
= find_usr(name
)) == NULL
) {
884 u
= create_ulist(name
, ATEVENT
);
885 add_atevent(u
, name
, tim
, jobtype
);
887 update_atevent(u
, name
, tim
, jobtype
);
893 mod_ctab(char *name
, time_t reftime
)
898 char namebuf
[LINE_MAX
];
901 /* skip over ancillary file names */
902 if (audit_cron_is_anc_name(name
))
905 if ((pw
= getpwnam(name
)) == NULL
) {
906 msg("No such user as %s - cron entries not created", name
);
910 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s",
911 CRONDIR
, name
) >= sizeof (namebuf
)) {
912 msg("Too long path name %s - cron entries not created",
921 * a warning message is given by the crontab command so there is
922 * no need to give one here...... use this code if you only want
923 * users with a login shell of /usr/bin/sh to use cron
925 #ifdef BOURNESHELLONLY
926 if ((strcmp(pw
->pw_shell
, "") != 0) &&
927 (strcmp(pw
->pw_shell
, SHELL
) != 0)) {
928 mail(name
, BADSHELL
, ERR_CANTEXECCRON
);
933 if (stat(pname
, &buf
)) {
934 mail(name
, BADSTAT
, ERR_UNIXERR
);
938 if (!S_ISREG(buf
.st_mode
)) {
939 mail(name
, BADTYPE
, ERR_CRONTABENT
);
942 if ((u
= find_usr(name
)) == NULL
) {
944 (void) fprintf(stderr
, "new user (%s) with a crontab\n", name
);
946 u
= create_ulist(name
, CRONEVENT
);
947 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
948 (void) strcpy(u
->home
, pw
->pw_dir
);
951 readcron(u
, reftime
);
955 if (u
->home
!= NULL
) {
956 if (strcmp(u
->home
, pw
->pw_dir
) != 0) {
958 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
959 (void) strcpy(u
->home
, pw
->pw_dir
);
962 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
963 (void) strcpy(u
->home
, pw
->pw_dir
);
968 (void) fprintf(stderr
, "%s now has a crontab\n",
971 /* user didnt have a crontab last time */
974 readcron(u
, reftime
);
978 (void) fprintf(stderr
, "%s has revised his crontab\n", u
->name
);
981 el_remove(u
->ctid
, 0);
982 readcron(u
, reftime
);
988 mod_atjob(char *name
, time_t reftime
)
995 char namebuf
[PATH_MAX
];
1000 if (((tim
= num(&ptr
)) == 0) || (*ptr
!= '.'))
1005 jobtype
= *ptr
- 'a';
1007 /* check for audit ancillary file */
1008 if (audit_cron_is_anc_name(name
))
1012 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s", ATDIR
, name
)
1013 >= sizeof (namebuf
)) {
1020 if (stat(pname
, &buf
) || jobtype
>= NQUEUE
) {
1024 if (!(buf
.st_mode
& ISUID
) || !S_ISREG(buf
.st_mode
)) {
1028 if ((pw
= getpwuid(buf
.st_uid
)) == NULL
) {
1033 * a warning message is given by the at command so there is no
1034 * need to give one here......use this code if you only want
1035 * users with a login shell of /usr/bin/sh to use cron
1037 #ifdef BOURNESHELLONLY
1038 if ((strcmp(pw
->pw_shell
, "") != 0) &&
1039 (strcmp(pw
->pw_shell
, SHELL
) != 0)) {
1040 mail(pw
->pw_name
, BADSHELL
, ERR_CANTEXECAT
);
1045 if ((u
= find_usr(pw
->pw_name
)) == NULL
) {
1047 (void) fprintf(stderr
, "new user (%s) with an at job = %s\n",
1050 u
= create_ulist(pw
->pw_name
, ATEVENT
);
1051 u
->home
= xstrdup(pw
->pw_dir
);
1052 u
->uid
= pw
->pw_uid
;
1053 u
->gid
= pw
->pw_gid
;
1054 add_atevent(u
, name
, tim
, jobtype
);
1056 u
->uid
= pw
->pw_uid
;
1057 u
->gid
= pw
->pw_gid
;
1059 u
->home
= xstrdup(pw
->pw_dir
);
1060 update_atevent(u
, name
, tim
, jobtype
);
1065 add_atevent(struct usr
*u
, char *job
, time_t tim
, int jobtype
)
1069 e
= xmalloc(sizeof (struct event
));
1071 e
->cmd
= xmalloc(strlen(job
) + 1);
1072 (void) strcpy(e
->cmd
, job
);
1074 e
->link
= u
->atevents
;
1076 e
->of
.at
.exists
= TRUE
;
1077 e
->of
.at
.eventid
= ecid
++;
1078 if (tim
< init_time
) /* old job */
1079 e
->time
= init_time
;
1083 (void) fprintf(stderr
, "add_atevent: user=%s, job=%s, time=%ld\n",
1084 u
->name
, e
->cmd
, e
->time
);
1086 if (el_add(e
, e
->time
, e
->of
.at
.eventid
) < 0) {
1087 ignore_msg("add_atevent", "at", e
);
1092 update_atevent(struct usr
*u
, char *name
, time_t tim
, int jobtype
)
1098 if (strcmp(e
->cmd
, name
) == 0) {
1099 e
->of
.at
.exists
= TRUE
;
1107 (void) fprintf(stderr
, "%s has a new at job = %s\n",
1110 add_atevent(u
, name
, tim
, jobtype
);
1114 static char line
[CTLINESIZE
]; /* holds a line from a crontab file */
1115 static int cursor
; /* cursor for the above line */
1118 readcron(struct usr
*u
, time_t reftime
)
1121 * readcron reads in a crontab file for a user (u). The list of
1122 * events for user u is built, and u->events is made to point to
1123 * this list. Each event is also entered into the main event
1126 FILE *cf
; /* cf will be a user's crontab file */
1130 char namebuf
[PATH_MAX
];
1132 struct shared
*tz
= NULL
;
1133 struct shared
*home
= NULL
;
1134 struct shared
*shell
= NULL
;
1137 /* read the crontab file */
1138 cte_init(); /* Init error handling */
1140 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s",
1141 CRONDIR
, u
->name
) >= sizeof (namebuf
)) {
1148 if ((cf
= fopen(pname
, "r")) == NULL
) {
1149 mail(u
->name
, NOREAD
, ERR_UNIXERR
);
1152 while (fgets(line
, CTLINESIZE
, cf
) != NULL
) {
1154 /* process a line of a crontab file */
1156 if (cte_istoomany())
1159 while (line
[cursor
] == ' ' || line
[cursor
] == '\t')
1161 if (line
[cursor
] == '#' || line
[cursor
] == '\n')
1164 if (strncmp(&line
[cursor
], ENV_TZ
,
1165 strlen(ENV_TZ
)) == 0) {
1166 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1170 if (!isvalid_tz(&line
[cursor
+ strlen(ENV_TZ
)], NULL
,
1172 cte_add(lineno
, line
);
1175 if (tz
== NULL
|| strcmp(&line
[cursor
], get_obj(tz
))) {
1177 tz
= create_shared_str(&line
[cursor
]);
1182 if (strncmp(&line
[cursor
], ENV_HOME
,
1183 strlen(ENV_HOME
)) == 0) {
1184 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1188 strcmp(&line
[cursor
], get_obj(home
))) {
1190 home
= create_shared_str(
1191 &line
[cursor
+ strlen(ENV_HOME
)]);
1196 if (strncmp(&line
[cursor
], ENV_SHELL
,
1197 strlen(ENV_SHELL
)) == 0) {
1198 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1201 if (shell
== NULL
||
1202 strcmp(&line
[cursor
], get_obj(shell
))) {
1204 shell
= create_shared_str(&line
[cursor
]);
1209 e
= xmalloc(sizeof (struct event
));
1210 e
->etype
= CRONEVENT
;
1211 if (!(((e
->of
.ct
.minute
= next_field(0, 59)) != NULL
) &&
1212 ((e
->of
.ct
.hour
= next_field(0, 23)) != NULL
) &&
1213 ((e
->of
.ct
.daymon
= next_field(1, 31)) != NULL
) &&
1214 ((e
->of
.ct
.month
= next_field(1, 12)) != NULL
) &&
1215 ((e
->of
.ct
.dayweek
= next_field(0, 6)) != NULL
))) {
1217 cte_add(lineno
, line
);
1220 while (line
[cursor
] == ' ' || line
[cursor
] == '\t')
1222 if (line
[cursor
] == '\n' || line
[cursor
] == '\0')
1224 /* get the command to execute */
1227 while ((line
[cursor
] != '%') &&
1228 (line
[cursor
] != '\n') &&
1229 (line
[cursor
] != '\0') &&
1230 (line
[cursor
] != '\\'))
1232 if (line
[cursor
] == '\\') {
1236 e
->cmd
= xmalloc(cursor
-start
+ 1);
1237 (void) strncpy(e
->cmd
, line
+ start
, cursor
-start
);
1238 e
->cmd
[cursor
-start
] = '\0';
1239 /* see if there is any standard input */
1240 if (line
[cursor
] == '%') {
1241 e
->of
.ct
.input
= xmalloc(strlen(line
)-cursor
+ 1);
1242 (void) strcpy(e
->of
.ct
.input
, line
+ cursor
+ 1);
1243 for (i
= 0; i
< strlen(e
->of
.ct
.input
); i
++) {
1244 if (e
->of
.ct
.input
[i
] == '%')
1245 e
->of
.ct
.input
[i
] = '\n';
1248 e
->of
.ct
.input
= NULL
;
1250 /* set the timezone of this entry */
1251 e
->of
.ct
.tz
= dup_shared(tz
);
1252 /* set the shell of this entry */
1253 e
->of
.ct
.shell
= dup_shared(shell
);
1254 /* set the home of this entry */
1255 e
->of
.ct
.home
= dup_shared(home
);
1256 /* have the event point to it's owner */
1258 /* insert this event at the front of this user's event list */
1259 e
->link
= u
->ctevents
;
1261 /* set the time for the first occurance of this event */
1262 e
->time
= next_time(e
, reftime
);
1263 /* finally, add this event to the main event list */
1264 switch (el_add(e
, e
->time
, u
->ctid
)) {
1266 ignore_msg("readcron", "cron", e
);
1268 case -2: /* event time lower than init time */
1274 cftime(timebuf
, "%C", &e
->time
);
1275 (void) fprintf(stderr
, "inserting cron event %s at %ld (%s)\n",
1276 e
->cmd
, e
->time
, timebuf
);
1279 cte_sendmail(u
->name
); /* mail errors if any to user */
1287 * Below are the functions for handling of errors in crontabs. Concept is to
1288 * collect faulty lines and send one email at the end of the crontab
1289 * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
1290 * of crontab is aborted. Otherwise reading of crontab is continued to the end
1291 * of the file but no further error logging appears.
1296 if (cte_text
== NULL
)
1297 cte_text
= xmalloc(MAILBUFLEN
);
1298 (void) strlcpy(cte_text
, cte_intro
, MAILBUFLEN
);
1299 cte_lp
= cte_text
+ sizeof (cte_intro
) - 1;
1300 cte_free
= MAILBINITFREE
;
1305 cte_add(int lineno
, char *ctline
)
1310 if (cte_free
>= LINELIMIT
) {
1311 (void) sprintf(cte_lp
, "%4d: ", lineno
);
1312 (void) strlcat(cte_lp
, ctline
, LINELIMIT
- 1);
1313 len
= strlen(cte_lp
);
1314 if (cte_lp
[len
- 1] != '\n') {
1315 cte_lp
[len
++] = '\n';
1318 for (p
= cte_lp
; *p
; p
++) {
1319 if (isprint(*p
) || *p
== '\n' || *p
== '\t')
1325 if (cte_free
< LINELIMIT
) {
1326 size_t buflen
= MAILBUFLEN
- (cte_lp
- cte_text
);
1327 (void) strlcpy(cte_lp
, cte_trail1
, buflen
);
1328 if (cte_nvalid
== 0)
1329 (void) strlcat(cte_lp
, cte_trail2
, buflen
);
1344 * Return TRUE only if all lines are faulty. So evaluation of
1345 * a crontab is not aborted if at least one valid line was found.
1347 return (cte_nvalid
== 0 && cte_free
< LINELIMIT
);
1351 cte_sendmail(char *username
)
1353 if (cte_free
< MAILBINITFREE
)
1354 mail(username
, cte_text
, ERR_CRONTABENT
);
1358 * Send mail with error message to a user
1361 mail(char *usrname
, char *mesg
, int format
)
1363 /* mail mails a user a message. */
1366 struct passwd
*ruser_ids
;
1368 int saveerrno
= errno
;
1369 struct utsname name
;
1374 (void) uname(&name
);
1375 if ((fork_val
= fork()) == (pid_t
)-1) {
1376 msg("cron cannot fork\n");
1379 if (fork_val
== 0) {
1381 contract_clear_template();
1382 if ((ruser_ids
= getpwnam(usrname
)) == NULL
)
1384 (void) setuid(ruser_ids
->pw_uid
);
1385 temp
= xmalloc(strlen(MAIL
) + strlen(usrname
) + 2);
1386 (void) sprintf(temp
, "%s %s", MAIL
, usrname
);
1387 pipe
= popen(temp
, "w");
1389 (void) fprintf(pipe
, "To: %s\n", usrname
);
1391 case ERR_CRONTABENT
:
1392 (void) fprintf(pipe
, CRONTABERR
);
1393 (void) fprintf(pipe
, "Your \"crontab\" on %s\n",
1395 (void) fprintf(pipe
, mesg
);
1396 (void) fprintf(pipe
,
1397 "\nEntries or crontab have been ignored\n");
1400 (void) fprintf(pipe
, "Subject: %s\n\n", mesg
);
1401 (void) fprintf(pipe
,
1402 "The error on %s was \"%s\"\n",
1403 name
.nodename
, errmsg(saveerrno
));
1406 case ERR_CANTEXECCRON
:
1407 (void) fprintf(pipe
,
1408 "Subject: Couldn't run your \"cron\" job\n\n");
1409 (void) fprintf(pipe
,
1410 "Your \"cron\" job on %s ", name
.nodename
);
1411 (void) fprintf(pipe
, "couldn't be run\n");
1412 (void) fprintf(pipe
, "%s\n", mesg
);
1413 (void) fprintf(pipe
,
1414 "The error was \"%s\"\n", errmsg(saveerrno
));
1417 case ERR_CANTEXECAT
:
1418 (void) fprintf(pipe
,
1419 "Subject: Couldn't run your \"at\" job\n\n");
1420 (void) fprintf(pipe
, "Your \"at\" job on %s ",
1422 (void) fprintf(pipe
, "couldn't be run\n");
1423 (void) fprintf(pipe
, "%s\n", mesg
);
1424 (void) fprintf(pipe
,
1425 "The error was \"%s\"\n", errmsg(saveerrno
));
1431 (void) pclose(pipe
);
1437 contract_abandon_latest(fork_val
);
1439 if (cron_pid
== getpid()) {
1440 miscpid_insert(fork_val
);
1445 next_field(int lower
, int upper
)
1448 * next_field returns a pointer to a string which holds the next
1449 * field of a line of a crontab file.
1450 * if (numbers in this field are out of range (lower..upper),
1451 * or there is a syntax error) then
1452 * NULL is returned, and a mail message is sent to the
1453 * user telling him which line the error was in.
1457 int num
, num2
, start
;
1459 while ((line
[cursor
] == ' ') || (line
[cursor
] == '\t'))
1462 if (line
[cursor
] == '\0') {
1465 if (line
[cursor
] == '*') {
1467 if ((line
[cursor
] != ' ') && (line
[cursor
] != '\t'))
1470 (void) strcpy(s
, "*");
1474 if (!isdigit(line
[cursor
]))
1478 num
= num
*10 + (line
[cursor
]-'0');
1479 } while (isdigit(line
[++cursor
]));
1480 if ((num
< lower
) || (num
> upper
))
1482 if (line
[cursor
] == '-') {
1483 if (!isdigit(line
[++cursor
]))
1487 num2
= num2
*10 + (line
[cursor
]-'0');
1488 } while (isdigit(line
[++cursor
]));
1489 if ((num2
< lower
) || (num2
> upper
))
1492 if ((line
[cursor
] == ' ') || (line
[cursor
] == '\t'))
1494 if (line
[cursor
] == '\0')
1496 if (line
[cursor
++] != ',')
1499 s
= xmalloc(cursor
-start
+ 1);
1500 (void) strncpy(s
, line
+ start
, cursor
-start
);
1501 s
[cursor
-start
] = '\0';
1505 #define tm_cmp(t1, t2) (\
1506 (t1)->tm_year == (t2)->tm_year && \
1507 (t1)->tm_mon == (t2)->tm_mon && \
1508 (t1)->tm_mday == (t2)->tm_mday && \
1509 (t1)->tm_hour == (t2)->tm_hour && \
1510 (t1)->tm_min == (t2)->tm_min)
1512 #define tm_setup(tp, yr, mon, dy, hr, min, dst) \
1513 (tp)->tm_year = yr; \
1514 (tp)->tm_mon = mon; \
1515 (tp)->tm_mday = dy; \
1516 (tp)->tm_hour = hr; \
1517 (tp)->tm_min = min; \
1518 (tp)->tm_isdst = dst; \
1520 (tp)->tm_wday = 0; \
1524 * modification for bugid 1104537. the second argument to next_time is
1525 * now the value of time(2) to be used. if this is 0, then use the
1526 * current time. otherwise, the second argument is the time from which to
1527 * calculate things. this is useful to correct situations where you've
1528 * gone backwards in time (I.e. the system's internal clock is correcting
1529 * itself backwards).
1535 tz_next_time(struct event
*e
, time_t tflag
)
1538 * returns the integer time for the next occurance of event e.
1539 * the following fields have ranges as indicated:
1540 * PRGM | min hour day of month mon day of week
1541 * ------|-------------------------------------------------------
1542 * cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday)
1543 * time | 0-59 0-23 1-31 0-11 0-6 (0=sunday)
1544 * NOTE: this routine is hard to understand.
1547 struct tm
*tm
, ref_tm
, tmp
, tmp1
, tmp2
;
1548 int tm_mon
, tm_mday
, tm_wday
, wday
, m
, min
, h
, hr
, carry
, day
, days
;
1549 int d1
, day1
, carry1
, d2
, day2
, carry2
, daysahead
, mon
, yr
, db
, wd
;
1551 time_t t
, ref_t
, t1
, t2
, zone_start
;
1553 extern int days_btwn(int, int, int, int, int, int);
1556 t
= time(NULL
); /* original way of doing things */
1561 tm
= &ref_tm
; /* use a local variable and call localtime_r() */
1562 ref_t
= t
; /* keep a copy of the reference time */
1567 (void) localtime_r(&t
, tm
);
1571 tmp
.tm_isdst
= (tm
->tm_isdst
> 0 ? 0 : 1);
1574 * see if we will have timezone switch over, and clock will
1575 * fall back. zone_start will hold the time when it happens
1576 * (ie time of PST -> PDT switch over).
1578 if (tm
->tm_isdst
!= tmp
.tm_isdst
&&
1579 (t1
- t
) == (timezone
- altzone
) &&
1581 zone_start
= get_switching_time(tmp
.tm_isdst
, t
);
1586 tm_mon
= next_ge(tm
->tm_mon
+ 1, e
->of
.ct
.month
) - 1; /* 0-11 */
1587 tm_mday
= next_ge(tm
->tm_mday
, e
->of
.ct
.daymon
); /* 1-31 */
1588 tm_wday
= next_ge(tm
->tm_wday
, e
->of
.ct
.dayweek
); /* 0-6 */
1590 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0 && tm
->tm_wday
!= tm_wday
) ||
1591 (strcmp(e
->of
.ct
.dayweek
, "*") == 0 && tm
->tm_mday
!= tm_mday
) ||
1592 (tm
->tm_mday
!= tm_mday
&& tm
->tm_wday
!= tm_wday
) ||
1593 (tm
->tm_mon
!= tm_mon
)) {
1596 m
= tm
->tm_min
+ (t
== ref_t
? 1 : 0);
1597 if ((tm
->tm_hour
+ 1) <= next_ge(tm
->tm_hour
, e
->of
.ct
.hour
)) {
1600 min
= next_ge(m
%60, e
->of
.ct
.minute
);
1601 carry
= (min
< m
) ? 1 : 0;
1602 h
= tm
->tm_hour
+ carry
;
1603 hr
= next_ge(h
%24, e
->of
.ct
.hour
);
1604 carry
= (hr
< h
) ? 1 : 0;
1606 if (carry
== 0 && today
) {
1607 /* this event must occur today */
1608 tm_setup(&tmp
, tm
->tm_year
, tm
->tm_mon
, tm
->tm_mday
,
1609 hr
, min
, tm
->tm_isdst
);
1611 if ((t1
= xmktime(&tmp1
)) == (time_t)-1) {
1614 if (daylight
&& tmp
.tm_isdst
!= tmp1
.tm_isdst
) {
1615 /* In case we are falling back */
1617 /* we may need to run the job once more. */
1623 * In case we are not in falling back period,
1624 * calculate the time assuming the DST. If the
1625 * date/time is not altered by mktime, it is the
1626 * time to execute the job.
1629 tmp2
.tm_isdst
= tmp1
.tm_isdst
;
1630 if ((t1
= xmktime(&tmp2
)) == (time_t)-1) {
1633 if (tmp1
.tm_isdst
== tmp2
.tm_isdst
&&
1634 tm_cmp(&tmp
, &tmp2
)) {
1636 * We got a valid time.
1641 * If the date does not match even if
1642 * we assume the alternate timezone, then
1643 * it must be the invalid time. eg
1644 * 2am while switching 1:59am to 3am.
1645 * t1 should point the time before the
1646 * switching over as we've calculate the
1647 * time with assuming alternate zone.
1649 if (tmp1
.tm_isdst
!= tmp2
.tm_isdst
) {
1650 t
= get_switching_time(tmp1
.tm_isdst
,
1653 /* does this really happen? */
1654 t
= get_switching_time(tmp1
.tm_isdst
,
1655 t1
- abs(timezone
- altzone
));
1657 if (t
== (time_t)-1) {
1663 if (tm_cmp(&tmp
, &tmp1
)) {
1664 /* got valid time */
1668 * This should never happen, but just in
1669 * case, we fall back to the old code.
1671 if (tm
->tm_min
> min
) {
1672 t
+= (time_t)(hr
-tm
->tm_hour
-1) * HOUR
+
1673 (time_t)(60-tm
->tm_min
+ min
) * MINUTE
;
1675 t
+= (time_t)(hr
-tm
->tm_hour
) * HOUR
+
1676 (time_t)(min
-tm
->tm_min
) * MINUTE
;
1679 t
-= (time_t)tm
->tm_sec
;
1680 (void) localtime_r(&t
, &tmp
);
1681 if ((tm
->tm_isdst
== 0) && (tmp
.tm_isdst
> 0))
1682 t
-= (timezone
- altzone
);
1683 return ((t
<= ref_t
) ? t1
: t
);
1688 * Job won't run today, however if we have a switch over within
1689 * one hour and we will have one hour time drifting back in this
1690 * period, we may need to run the job one more time if the job was
1691 * set to run on this hour of clock.
1698 min
= next_ge(0, e
->of
.ct
.minute
);
1699 hr
= next_ge(0, e
->of
.ct
.hour
);
1702 * calculate the date of the next occurance of this event, which
1703 * will be on a different day than the current
1706 /* check monthly day specification */
1707 d1
= tm
->tm_mday
+ 1;
1708 day1
= next_ge((d1
-1)%days_in_mon(tm
->tm_mon
, tm
->tm_year
) + 1,
1710 carry1
= (day1
< d1
) ? 1 : 0;
1712 /* check weekly day specification */
1713 d2
= tm
->tm_wday
+ 1;
1714 wday
= next_ge(d2
%7, e
->of
.ct
.dayweek
);
1716 daysahead
= 7 - d2
+ wday
;
1718 daysahead
= wday
- d2
;
1719 day2
= (d1
+ daysahead
-1)%days_in_mon(tm
->tm_mon
, tm
->tm_year
) + 1;
1720 carry2
= (day2
< d1
) ? 1 : 0;
1723 * based on their respective specifications, day1, and day2 give
1724 * the day of the month for the next occurance of this event.
1726 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0) &&
1727 (strcmp(e
->of
.ct
.dayweek
, "*") != 0)) {
1731 if ((strcmp(e
->of
.ct
.daymon
, "*") != 0) &&
1732 (strcmp(e
->of
.ct
.dayweek
, "*") == 0)) {
1738 if ((carry1
&& carry2
) || (tm
->tm_mon
!= tm_mon
)) {
1739 /* event does not occur in this month */
1741 mon
= next_ge(m
%12 + 1, e
->of
.ct
.month
) - 1; /* 0..11 */
1742 carry
= (mon
< m
) ? 1 : 0;
1744 /* recompute day1 and day2 */
1745 day1
= next_ge(1, e
->of
.ct
.daymon
);
1746 db
= days_btwn(tm
->tm_mon
, tm
->tm_mday
, tm
->tm_year
, mon
,
1748 wd
= (tm
->tm_wday
+ db
)%7;
1749 /* wd is the day of the week of the first of month mon */
1750 wday
= next_ge(wd
, e
->of
.ct
.dayweek
);
1752 day2
= 1 + 7 - wd
+ wday
;
1754 day2
= 1 + wday
- wd
;
1755 if ((strcmp(e
->of
.ct
.daymon
, "*") != 0) &&
1756 (strcmp(e
->of
.ct
.dayweek
, "*") == 0))
1758 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0) &&
1759 (strcmp(e
->of
.ct
.dayweek
, "*") != 0))
1761 day
= (day1
< day2
) ? day1
: day2
;
1762 } else { /* event occurs in this month */
1764 if (!carry1
&& !carry2
)
1765 day
= (day1
< day2
) ? day1
: day2
;
1773 * now that we have the min, hr, day, mon, yr of the next event,
1774 * figure out what time that turns out to be.
1776 tm_setup(&tmp
, yr
, mon
, day
, hr
, min
, -1);
1778 if ((t1
= xmktime(&tmp2
)) == (time_t)-1) {
1781 if (tm_cmp(&tmp
, &tmp2
)) {
1783 * mktime returns clock for the current time zone. If the
1784 * target date was in fallback period, it needs to be adjusted
1785 * to the time comes first.
1786 * Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
1787 * mktime returns the time in PST, but 1:30am in PDT comes
1788 * first. So reverse the tm_isdst, and see if we have such
1792 int dst
= tmp2
.tm_isdst
;
1795 tmp2
.tm_isdst
= (dst
> 0 ? 0 : 1);
1796 if ((t2
= xmktime(&tmp2
)) == (time_t)-1) {
1799 if (tm_cmp(&tmp
, &tmp2
)) {
1801 * same time/date found in the opposite zone.
1802 * check the clock to see which comes early.
1804 if (t2
> ref_t
&& t2
< t1
) {
1812 * mktime has set different time/date for the given date.
1813 * This means that the next job is scheduled to be run on the
1814 * invalid time. There are three possible invalid date/time.
1815 * 1. Non existing day of the month. such as April 31th.
1816 * 2. Feb 29th in the non-leap year.
1817 * 3. Time gap during the DST switch over.
1819 d1
= days_in_mon(mon
, yr
);
1820 if ((mon
!= 1 && day
> d1
) || (mon
== 1 && day
> 29)) {
1822 * see if we have got a specific date which
1825 if (strcmp(e
->of
.ct
.dayweek
, "*") == 0 &&
1826 mon
== (next_ge((mon
+ 1)%12 + 1,
1827 e
->of
.ct
.month
) - 1) &&
1828 day
<= next_ge(1, e
->of
.ct
.daymon
)) {
1833 * Since the day has gone invalid, we need to go to
1834 * next month, and recalcuate the first occurrence.
1835 * eg the cron tab such as:
1836 * 0 0 1,15,31 1,2,3,4,5 * /usr/bin....
1837 * 2/31 is invalid, so the next job is 3/1.
1842 tmp2
.tm_mday
= 1; /* 1st day of the month */
1845 tmp2
.tm_year
= yr
+ 1;
1847 tmp2
.tm_mon
= mon
+ 1;
1849 if ((t
= xmktime(&tmp2
)) == (time_t)-1) {
1852 } else if (mon
== 1 && day
> d1
) {
1854 * ie 29th in the non-leap year. Forwarding the
1855 * clock to Feb 29th 00:00 (March 1st), and recalculate
1861 if ((t
= xmktime(&tmp2
)) == (time_t)-1) {
1864 } else if (daylight
) {
1866 * Non existing time, eg 2am PST during summer time
1868 * We need to get the correct isdst which we are
1869 * swithing to, by adding time difference to make sure
1870 * that t2 is in the zone being switched.
1873 t2
+= abs(timezone
- altzone
);
1874 (void) localtime_r(&t2
, &tmp2
);
1875 zone_start
= get_switching_time(tmp2
.tm_isdst
,
1876 t1
- abs(timezone
- altzone
));
1877 if (zone_start
== (time_t)-1) {
1883 * This should never happen, but fall back to the
1886 days
= days_btwn(tm
->tm_mon
,
1887 tm
->tm_mday
, tm
->tm_year
, mon
, day
, yr
);
1888 t
+= (time_t)(23-tm
->tm_hour
)*HOUR
1889 + (time_t)(60-tm
->tm_min
)*MINUTE
1890 + (time_t)hr
*HOUR
+ (time_t)min
*MINUTE
1893 t
-= (time_t)tm
->tm_sec
;
1894 (void) localtime_r(&t
, &tmp
);
1895 if ((tm
->tm_isdst
== 0) && (tmp
.tm_isdst
> 0))
1896 t
-= (timezone
- altzone
);
1897 return (t
<= ref_t
? t1
: t
);
1905 next_time(struct event
*e
, time_t tflag
)
1907 if (e
->of
.ct
.tz
!= NULL
) {
1910 (void) putenv((char *)get_obj(e
->of
.ct
.tz
));
1912 ret
= tz_next_time(e
, tflag
);
1913 (void) putenv(tzone
);
1917 return (tz_next_time(e
, tflag
));
1922 * This returns TOD in time_t that zone switch will happen, and this
1923 * will be called when clock fallback is about to happen.
1924 * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
1925 * will fall back to 1:00 PDT. So this function will be called only
1926 * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
1927 * First goes through the common time differences to see if zone
1928 * switch happens at those minutes later. If not, check every minutes
1929 * until 6 hours ahead see if it happens(We might have 45minutes
1933 get_switching_time(int to_dst
, time_t t_ref
)
1936 struct tm tmp
, tmp1
;
1937 int hints
[] = { 60, 120, 30, 90, 0}; /* minutes */
1940 (void) localtime_r(&t_ref
, &tmp
);
1944 if ((t
= xmktime(&tmp1
)) == (time_t)-1)
1945 return ((time_t)-1);
1948 for (i
= 0; hints
[i
] != 0; i
++) {
1949 t1
= t
+ hints
[i
] * 60;
1950 (void) localtime_r(&t1
, &tmp1
);
1951 if (tmp1
.tm_isdst
== to_dst
) {
1953 (void) localtime_r(&t1
, &tmp1
);
1954 if (tmp1
.tm_isdst
!= to_dst
) {
1960 /* ugly, but don't know other than this. */
1963 if ((t
= xmktime(&tmp1
)) == (time_t)-1)
1964 return ((time_t)-1);
1965 while (t
< (t_ref
+ 6*60*60)) { /* 6 hours should be enough */
1966 t
+= 60; /* at least one minute, I assume */
1967 (void) localtime_r(&t
, &tmp
);
1968 if (tmp
.tm_isdst
== to_dst
)
1971 return ((time_t)-1);
1975 xmktime(struct tm
*tmp
)
1979 if ((ret
= mktime(tmp
)) == (time_t)-1) {
1980 if (errno
== EOVERFLOW
) {
1981 return ((time_t)-1);
1983 crabort("internal error: mktime failed",
1984 REMOVE_FIFO
|CONSOLE_MSG
);
1992 next_ge(int current
, char *list
)
1995 * list is a character field as in a crontab file;
1996 * for example: "40, 20, 50-10"
1997 * next_ge returns the next number in the list that is
1998 * greater than or equal to current. if no numbers of list
1999 * are >= current, the smallest element of list is returned.
2000 * NOTE: current must be in the appropriate range.
2004 int n
, n2
, min
, min_gt
;
2006 if (strcmp(list
, "*") == 0)
2012 if ((n
= (int)num(&ptr
)) == current
)
2016 if ((n
> current
) && (n
< min_gt
))
2020 if ((n2
= (int)num(&ptr
)) > n
) {
2021 if ((current
> n
) && (current
<= n2
))
2023 } else { /* range that wraps around */
2034 if (min_gt
!= DUMMY
)
2041 free_if_unused(struct usr
*u
)
2043 struct usr
*cur
, *prev
;
2045 * To make sure a usr structure is idle we must check that
2046 * there are no at jobs queued for the user; the user does
2047 * not have a crontab, and also that there are no running at
2048 * or cron jobs (since the runinfo structure also has a
2049 * pointer to the usr structure).
2051 if (!u
->ctexists
&& u
->atevents
== NULL
&&
2052 u
->cruncnt
== 0 && u
->aruncnt
== 0) {
2054 (void) fprintf(stderr
, "%s removed from usr list\n", u
->name
);
2056 for (cur
= uhead
, prev
= NULL
;
2058 prev
= cur
, cur
= cur
->nextusr
) {
2067 prev
->nextusr
= u
->nextusr
;
2075 del_atjob(char *name
, char *usrname
)
2078 struct event
*e
, *eprev
;
2081 if ((u
= find_usr(usrname
)) == NULL
)
2086 if (strcmp(name
, e
->cmd
) == 0) {
2087 if (next_event
== e
)
2090 u
->atevents
= e
->link
;
2092 eprev
->link
= e
->link
;
2093 el_remove(e
->of
.at
.eventid
, 1);
2107 del_ctab(char *name
)
2112 if ((u
= find_usr(name
)) == NULL
)
2115 el_remove(u
->ctid
, 0);
2123 rm_ctevents(struct usr
*u
)
2125 struct event
*e2
, *e3
;
2128 * see if the next event (to be run by cron) is a cronevent
2129 * owned by this user.
2132 if ((next_event
!= NULL
) &&
2133 (next_event
->etype
== CRONEVENT
) &&
2134 (next_event
->u
== u
)) {
2138 while (e2
!= NULL
) {
2140 rel_shared(e2
->of
.ct
.tz
);
2141 rel_shared(e2
->of
.ct
.shell
);
2142 rel_shared(e2
->of
.ct
.home
);
2143 free(e2
->of
.ct
.minute
);
2144 free(e2
->of
.ct
.hour
);
2145 free(e2
->of
.ct
.daymon
);
2146 free(e2
->of
.ct
.month
);
2147 free(e2
->of
.ct
.dayweek
);
2148 if (e2
->of
.ct
.input
!= NULL
)
2149 free(e2
->of
.ct
.input
);
2159 find_usr(char *uname
)
2165 if (strcmp(u
->name
, uname
) == 0)
2173 * Execute cron command or at/batch job.
2174 * If ever a premature return is added to this function pay attention to
2175 * free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
2185 char *at_cmdfile
= NULL
;
2189 struct project proj
, *pproj
= NULL
;
2192 char buf
[PROJECT_BUFSZ
];
2193 char buf2
[PROJECT_BUFSZ
];
2195 char error
[CANT_STR_LEN
+ PATH_MAX
];
2205 qp
= &qt
[e
->etype
]; /* set pointer to queue defs */
2206 if (qp
->nrun
>= qp
->njob
) {
2207 msg("%c queue max run limit reached", e
->etype
+ 'a');
2212 rp
= rinfo_get(0); /* allocating a new runinfo struct */
2215 * the tempnam() function uses malloc(3C) to allocate space for the
2216 * constructed file name, and returns a pointer to this area, which
2217 * is assigned to rp->outfile. Here rp->outfile is not overwritten.
2220 rp
->outfile
= tempnam(TMPDIR
, PFX
);
2221 rp
->jobtype
= e
->etype
;
2222 if (e
->etype
== CRONEVENT
) {
2223 rp
->jobname
= xmalloc(strlen(e
->cmd
) + 1);
2224 (void) strcpy(rp
->jobname
, e
->cmd
);
2225 /* "cron" jobs only produce mail if there's output */
2226 rp
->mailwhendone
= 0;
2228 at_cmdfile
= xmalloc(strlen(ATDIR
) + strlen(e
->cmd
) + 2);
2229 (void) sprintf(at_cmdfile
, "%s/%s", ATDIR
, e
->cmd
);
2230 if ((atcmdfp
= fopen(at_cmdfile
, "r")) == NULL
) {
2231 if (errno
== ENAMETOOLONG
) {
2232 if (chdir(ATDIR
) == 0)
2233 cron_unlink(e
->cmd
);
2235 cron_unlink(at_cmdfile
);
2237 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECAT
);
2242 rp
->jobname
= xmalloc(strlen(at_cmdfile
) + 1);
2243 (void) strcpy(rp
->jobname
, at_cmdfile
);
2246 * Skip over the first two lines.
2248 (void) fscanf(atcmdfp
, "%*[^\n]\n");
2249 (void) fscanf(atcmdfp
, "%*[^\n]\n");
2250 if (fscanf(atcmdfp
, ": notify by mail: %3s%*[^\n]\n",
2253 * Check to see if we should always send mail
2256 rp
->mailwhendone
= (strcmp(mailvar
, "yes") == 0);
2258 rp
->mailwhendone
= 0;
2261 if (fscanf(atcmdfp
, "\n: project: %d\n", &projid
) == 1) {
2264 (void) fclose(atcmdfp
);
2268 * we make sure that the system time
2269 * hasn't drifted backwards. if it has, el_add() is now
2270 * called, to make sure that the event queue is back in order,
2271 * and we set the delayed flag. cron will pick up the request
2272 * later on at the proper time.
2274 dhltime
= time(NULL
);
2275 if ((dhltime
- e
->time
) < 0) {
2276 msg("clock time drifted backwards!\n");
2277 if (next_event
->etype
== CRONEVENT
) {
2278 msg("correcting cron event\n");
2279 next_event
->time
= next_time(next_event
, dhltime
);
2280 switch (el_add(next_event
, next_event
->time
,
2281 (next_event
->u
)->ctid
)) {
2283 ignore_msg("ex", "cron", next_event
);
2285 case -2: /* event time lower than init time */
2289 } else { /* etype == ATEVENT */
2290 msg("correcting batch event\n");
2291 if (el_add(next_event
, next_event
->time
,
2292 next_event
->of
.at
.eventid
) < 0) {
2293 ignore_msg("ex", "at", next_event
);
2303 if ((rfork
= fork()) == (pid_t
)-1) {
2305 if ((rfork
= fork()) == (pid_t
)-1) {
2314 if (rfork
) { /* parent process */
2315 contract_abandon_latest(rfork
);
2320 if (e
->etype
!= CRONEVENT
)
2325 logit(BCHAR
, rp
, 0);
2332 contract_clear_template();
2334 if (e
->etype
!= CRONEVENT
) {
2335 /* open jobfile as stdin to shell */
2336 if (stat(at_cmdfile
, &buf
)) {
2337 if (errno
== ENAMETOOLONG
) {
2338 if (chdir(ATDIR
) == 0)
2339 cron_unlink(e
->cmd
);
2341 cron_unlink(at_cmdfile
);
2342 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECCRON
);
2345 if (!(buf
.st_mode
&ISUID
)) {
2347 * if setuid bit off, original owner has
2348 * given this file to someone else
2350 cron_unlink(at_cmdfile
);
2353 if ((fd
= open(at_cmdfile
, O_RDONLY
)) == -1) {
2354 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECCRON
);
2355 cron_unlink(at_cmdfile
);
2363 * retrieve the project id of the at job and convert it
2364 * to a project name. fail if it's not a valid project
2365 * or if the user isn't a member of the project.
2367 if (projflag
== 1) {
2368 if ((pproj
= getprojbyid(projid
, &proj
,
2369 (void *)&bufs
.p
.buf
,
2370 sizeof (bufs
.p
.buf
))) == NULL
||
2371 !inproj(e
->u
->name
, pproj
->pj_name
,
2372 bufs
.p
.buf2
, sizeof (bufs
.p
.buf2
))) {
2373 cron_unlink(at_cmdfile
);
2374 mail((e
->u
)->name
, BADPROJID
, ERR_CANTEXECAT
);
2381 * Put process in a new session, and create a new task.
2384 msg("setsid failed with errno = %d. job failed (%s)"
2385 " for user %s", errno
, e
->cmd
, e
->u
->name
);
2386 if (e
->etype
!= CRONEVENT
)
2387 cron_unlink(at_cmdfile
);
2392 * set correct user identification and check his account
2394 r
= set_user_cred(e
->u
, pproj
);
2395 if (r
== VUC_EXPIRED
) {
2396 msg("user (%s) account is expired", e
->u
->name
);
2397 audit_cron_user_acct_expired(e
->u
->name
);
2398 clean_out_user(e
->u
);
2401 if (r
== VUC_NEW_AUTH
) {
2402 msg("user (%s) password has expired", e
->u
->name
);
2403 audit_cron_user_acct_expired(e
->u
->name
);
2404 clean_out_user(e
->u
);
2408 msg("bad user (%s)", e
->u
->name
);
2409 audit_cron_bad_user(e
->u
->name
);
2410 clean_out_user(e
->u
);
2414 * check user and initialize the supplementary group access list.
2415 * bugid 1230784: deleted from parent to avoid cron hang. Now
2416 * only child handles the call.
2419 if (verify_user_cred(e
->u
) != VUC_OK
||
2420 setgid(e
->u
->gid
) == -1 ||
2421 initgroups(e
->u
->name
, e
->u
->gid
) == -1) {
2422 msg("bad user (%s) or setgid failed (%s)",
2423 e
->u
->name
, e
->u
->name
);
2424 audit_cron_bad_user(e
->u
->name
);
2425 clean_out_user(e
->u
);
2429 if ((e
->u
)->uid
== 0) { /* set default path */
2430 /* path settable in defaults file */
2431 envinit
[2] = supath
;
2436 if (e
->etype
!= CRONEVENT
) {
2437 r
= audit_cron_session(e
->u
->name
, NULL
,
2438 e
->u
->uid
, e
->u
->gid
, at_cmdfile
);
2439 cron_unlink(at_cmdfile
);
2441 r
= audit_cron_session(e
->u
->name
, CRONDIR
,
2442 e
->u
->uid
, e
->u
->gid
, NULL
);
2445 msg("cron audit problem. job failed (%s) for user %s",
2446 e
->cmd
, e
->u
->name
);
2450 audit_cron_new_job(e
->cmd
, e
->etype
, (void *)e
);
2452 if (setuid(e
->u
->uid
) == -1) {
2453 msg("setuid failed (%s)", e
->u
->name
);
2454 clean_out_user(e
->u
);
2458 if (e
->etype
== CRONEVENT
) {
2459 /* check for standard input to command */
2460 if (e
->of
.ct
.input
!= NULL
) {
2461 if ((tmpfile
= strdup(TMPINFILE
)) == NULL
) {
2462 mail((e
->u
)->name
, MALLOCERR
,
2466 if ((fd
= mkstemp(tmpfile
)) == -1 ||
2467 (fptr
= fdopen(fd
, "w")) == NULL
) {
2468 mail((e
->u
)->name
, NOSTDIN
,
2470 cron_unlink(tmpfile
);
2474 if ((fwrite(e
->of
.ct
.input
, sizeof (char),
2475 strlen(e
->of
.ct
.input
), fptr
)) !=
2476 strlen(e
->of
.ct
.input
)) {
2477 mail((e
->u
)->name
, NOSTDIN
, ERR_CANTEXECCRON
);
2478 cron_unlink(tmpfile
);
2481 (void) fclose(fptr
);
2484 if (fseek(fptr
, (off_t
)0, SEEK_SET
) != -1) {
2490 cron_unlink(tmpfile
);
2492 (void) fclose(fptr
);
2493 } else if ((fd
= open("/dev/null", O_RDONLY
)) > 0) {
2499 /* redirect stdout and stderr for the shell */
2500 if ((fd
= open(rp
->outfile
, O_WRONLY
|O_CREAT
|O_EXCL
, OUTMODE
)) == 1)
2501 fd
= open("/dev/null", O_WRONLY
);
2503 if (fd
>= 0 && fd
!= 1)
2506 if (fd
>= 0 && fd
!= 2) {
2512 if (e
->etype
== CRONEVENT
&& e
->of
.ct
.home
!= NULL
) {
2513 home
= (char *)get_obj(e
->of
.ct
.home
);
2515 home
= (e
->u
)->home
;
2517 (void) strlcat(homedir
, home
, sizeof (homedir
));
2518 (void) strlcat(logname
, (e
->u
)->name
, sizeof (logname
));
2520 if (chdir(home
) == -1) {
2521 snprintf(bufs
.error
, sizeof (bufs
.error
), CANTCDHOME
, home
);
2522 mail((e
->u
)->name
, bufs
.error
,
2523 e
->etype
== CRONEVENT
? ERR_CANTEXECCRON
:
2531 * make sure that all file descriptors EXCEPT 0, 1 and 2
2536 if ((e
->u
)->uid
!= 0)
2537 (void) nice(qp
->nice
);
2538 if (e
->etype
== CRONEVENT
) {
2540 (void) putenv((char *)get_obj(e
->of
.ct
.tz
));
2542 if (e
->of
.ct
.shell
) {
2545 sh
= (char *)get_obj(e
->of
.ct
.shell
);
2546 name
= strrchr(sh
, '/');
2553 sh
+= strlen(ENV_SHELL
);
2554 (void) execl(sh
, name
, "-c", e
->cmd
, 0);
2556 (void) execl(SHELL
, "sh", "-c", e
->cmd
, 0);
2559 } else { /* type == ATEVENT */
2560 (void) execl(SHELL
, "sh", 0);
2563 snprintf(bufs
.error
, sizeof (bufs
.error
), CANTEXECSH
, sh
);
2564 mail((e
->u
)->name
, bufs
.error
,
2565 e
->etype
== CRONEVENT
? ERR_CANTEXECCRON
: ERR_CANTEXECAT
);
2572 * When timed out to run the job, return 0.
2573 * If for some reasons we need to reschedule jobs, return 1.
2583 if (msg_wait(t
) != 0) {
2584 /* we need to run next job immediately */
2591 /* We got THAW or REFRESH message */
2596 if (last_time
> now
) {
2597 /* clock has been reset to backward */
2601 if (next_event
== NULL
&& !el_empty()) {
2602 next_event
= (struct event
*)el_first();
2605 if (next_event
== NULL
)
2608 t
= (long)next_event
->time
- now
;
2614 * This used to be in the idle(), but moved to the separate function.
2615 * This called from various place when cron needs to reap the
2616 * child. It includes the situation that cron hit maxrun, and needs
2617 * to reschedule the job.
2627 pid
= waitpid((pid_t
)-1, &prc
, WNOHANG
);
2632 "wait returned %x for process %d\n", prc
, pid
);
2634 if ((rp
= rinfo_get(pid
)) == NULL
) {
2635 if (miscpid_delete(pid
) == 0) {
2636 /* not found in anywhere */
2639 } else if (rp
->que
== ZOMB
) {
2640 (void) unlink(rp
->outfile
);
2649 cleanup(struct runinfo
*pr
, int rc
)
2655 logit(ECHAR
, pr
, rc
);
2658 if (pr
->que
!= CRONEVENT
)
2663 if (lstat(pr
->outfile
, &buf
) == 0) {
2664 if (!S_ISLNK(buf
.st_mode
) &&
2665 (buf
.st_size
> 0 || pr
->mailwhendone
)) {
2666 /* mail user stdout and stderr */
2668 if ((pr
->pid
= fork()) < 0) {
2670 * if fork fails try forever in doubling
2671 * retry times, up to 16 seconds
2673 (void) sleep(nextfork
);
2675 nextfork
+= nextfork
;
2677 } else if (pr
->pid
== 0) {
2679 contract_clear_template();
2681 mail_result(p
, pr
, buf
.st_size
);
2684 contract_abandon_latest(pr
->pid
);
2690 (void) unlink(pr
->outfile
);
2701 * Mail stdout and stderr of a job to user. Get uid for real user and become
2702 * that person. We do this so that mail won't come from root since this
2703 * could be a security hole. If failure, quit - don't send mail as root.
2706 mail_result(struct usr
*p
, struct runinfo
*pr
, size_t filesize
)
2708 struct passwd
*ruser_ids
;
2711 struct utsname name
;
2715 char *lowname
= (pr
->jobtype
== CRONEVENT
? "cron" : "at");
2717 (void) uname(&name
);
2718 if ((ruser_ids
= getpwnam(p
->name
)) == NULL
)
2720 (void) setuid(ruser_ids
->pw_uid
);
2722 cmd
= xmalloc(strlen(MAIL
) + strlen(p
->name
)+2);
2723 (void) sprintf(cmd
, "%s %s", MAIL
, p
->name
);
2724 mailpipe
= popen(cmd
, "w");
2726 if (mailpipe
== NULL
)
2728 (void) fprintf(mailpipe
, "To: %s\n", p
->name
);
2729 (void) fprintf(mailpipe
, "Subject: %s <%s@%s> %s\n",
2730 (pr
->jobtype
== CRONEVENT
? "Cron" : "At"),
2731 p
->name
, name
.nodename
, pr
->jobname
);
2734 * RFC3834 (Section 5) defines the Auto-Submitted header to prevent
2735 * vacation replies, et al, from being sent in response to
2736 * machine-generated mail.
2738 (void) fprintf(mailpipe
, "Auto-Submitted: auto-generated\n");
2741 * Additional headers for mail filtering and diagnostics:
2743 (void) fprintf(mailpipe
, "X-Mailer: cron (%s %s)\n", name
.sysname
,
2745 (void) fprintf(mailpipe
, "X-Cron-User: %s\n", p
->name
);
2746 (void) fprintf(mailpipe
, "X-Cron-Host: %s\n", name
.nodename
);
2747 (void) fprintf(mailpipe
, "X-Cron-Job-Name: %s\n", pr
->jobname
);
2748 (void) fprintf(mailpipe
, "X-Cron-Job-Type: %s\n", lowname
);
2753 * (Temporary file is fopen'ed with "r", secure open.)
2755 (void) fprintf(mailpipe
, "\n");
2757 (st
= fopen(pr
->outfile
, "r")) != NULL
) {
2758 while ((nbytes
= fread(iobuf
, sizeof (char), BUFSIZ
, st
)) != 0)
2759 (void) fwrite(iobuf
, sizeof (char), nbytes
, mailpipe
);
2762 (void) fprintf(mailpipe
, "Job completed with no output.\n");
2764 (void) pclose(mailpipe
);
2775 struct timespec tout
, *toutp
;
2776 static int pending_msg
;
2777 static time_t pending_reftime
;
2780 process_msg(&msgbuf
, pending_reftime
);
2786 FD_SET(msgfd
, &fds
);
2789 if (tim
!= INFINITY
) {
2790 #ifdef CRON_MAXSLEEP
2792 * CRON_MAXSLEEP can be defined to have cron periodically wake
2793 * up, so that cron can detect a change of TOD and adjust the
2794 * sleep time more frequently.
2796 tim
= (tim
> CRON_MAXSLEEP
) ? CRON_MAXSLEEP
: tim
;
2803 cnt
= pselect(msgfd
+ 1, &fds
, NULL
, NULL
, toutp
, &defmask
);
2804 if (cnt
== -1 && errno
!= EINTR
)
2805 perror("! pselect");
2807 /* pselect timeout or interrupted */
2812 if ((cnt
= read(msgfd
, &msg
, sizeof (msg
))) != sizeof (msg
)) {
2813 if (cnt
!= -1 || errno
!= EAGAIN
)
2817 reftime
= time(NULL
);
2818 if (next_event
!= NULL
&& reftime
>= next_event
->time
) {
2820 * we need to run the job before reloading crontab.
2822 (void) memcpy(&msgbuf
, &msg
, sizeof (msg
));
2824 pending_reftime
= reftime
;
2827 process_msg(&msg
, reftime
);
2832 * process the message supplied via pipe. This will be called either
2833 * immediately after cron read the message from pipe, or idle time
2834 * if the message was pending due to the job execution.
2837 process_msg(struct message
*pmsg
, time_t reftime
)
2839 if (pmsg
->etype
== NULL
)
2842 switch (pmsg
->etype
) {
2844 if (pmsg
->action
== DELETE
)
2845 del_atjob(pmsg
->fname
, pmsg
->logname
);
2847 mod_atjob(pmsg
->fname
, (time_t)0);
2850 if (pmsg
->action
== DELETE
)
2851 del_ctab(pmsg
->fname
);
2853 mod_ctab(pmsg
->fname
, reftime
);
2860 msg("message received - bad format");
2863 if (next_event
!= NULL
) {
2864 if (next_event
->etype
== CRONEVENT
) {
2865 switch (el_add(next_event
, next_event
->time
,
2866 (next_event
->u
)->ctid
)) {
2868 ignore_msg("process_msg", "cron", next_event
);
2870 case -2: /* event time lower than init time */
2874 } else { /* etype == ATEVENT */
2875 if (el_add(next_event
, next_event
->time
,
2876 next_event
->of
.at
.eventid
) < 0) {
2877 ignore_msg("process_msg", "at", next_event
);
2882 (void) fflush(stdout
);
2887 * Allocate a new or find an existing runinfo structure
2889 static struct runinfo
*
2890 rinfo_get(pid_t pid
)
2894 if (pid
== 0) { /* allocate a new entry */
2895 rp
= xcalloc(1, sizeof (struct runinfo
));
2896 rp
->next
= rthead
; /* link the entry into the list */
2900 /* search the list for an existing entry */
2901 for (rp
= rthead
; rp
!= NULL
; rp
= rp
->next
) {
2909 * Free a runinfo structure and its associated memory
2912 rinfo_free(struct runinfo
*entry
)
2914 struct runinfo
**rpp
;
2918 (void) fprintf(stderr
, "freeing job %s\n", entry
->jobname
);
2920 for (rpp
= &rthead
; (rp
= *rpp
) != NULL
; rpp
= &rp
->next
) {
2922 *rpp
= rp
->next
; /* unlink the entry */
2933 thaw_handler(int sig
)
2943 crabort("SIGTERM", REMOVE_FIFO
);
2948 child_handler(int sig
)
2954 child_sigreset(void)
2956 (void) signal(SIGCLD
, SIG_DFL
);
2957 (void) sigprocmask(SIG_SETMASK
, &defmask
, NULL
);
2961 * crabort() - handle exits out of cron
2964 crabort(char *mssg
, int action
)
2968 if (action
& REMOVE_FIFO
) {
2969 /* FIFO vanishes when cron finishes */
2970 if (unlink(FIFO
) < 0)
2971 perror("cron could not unlink FIFO");
2974 if (action
& CONSOLE_MSG
) {
2975 /* write error msg to console */
2976 if ((c
= open(CONSOLE
, O_WRONLY
)) >= 0) {
2977 (void) write(c
, "cron aborted: ", 14);
2978 (void) write(c
, mssg
, strlen(mssg
));
2979 (void) write(c
, "\n", 1);
2984 /* always log the message */
2986 msg("******* CRON ABORTED ********");
2991 * msg() - time-stamped error reporting function
3002 (void) fflush(stdout
);
3004 (void) fprintf(stderr
, "! ");
3006 va_start(args
, fmt
);
3007 (void) vfprintf(stderr
, fmt
, args
);
3010 (void) strftime(timebuf
, sizeof (timebuf
), FORMAT
, localtime(&t
));
3011 (void) fprintf(stderr
, " %s\n", timebuf
);
3013 (void) fflush(stderr
);
3017 ignore_msg(char *func_name
, char *job_type
, struct event
*event
)
3019 msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)",
3020 func_name
, job_type
,
3021 event
->u
->name
? event
->u
->name
: "unknown",
3022 event
->cmd
? event
->cmd
: "unknown",
3027 logit(int cc
, struct runinfo
*rp
, int rc
)
3037 (void) printf("%c CMD: %s\n", cc
, next_event
->cmd
);
3038 (void) strftime(timebuf
, sizeof (timebuf
), FORMAT
, localtime(&t
));
3039 (void) printf("%c %s %u %c %s",
3040 cc
, (rp
->rusr
)->name
, rp
->pid
, QUE(rp
->que
), timebuf
);
3041 if ((ret
= TSTAT(rc
)) != 0)
3042 (void) printf(" ts=%d", ret
);
3043 if ((ret
= RCODE(rc
)) != 0)
3044 (void) printf(" rc=%d", ret
);
3045 (void) putchar('\n');
3046 (void) fflush(stdout
);
3054 /* run job at a later time */
3055 nt
= next_event
->time
+ delay
;
3056 if (next_event
->etype
== CRONEVENT
) {
3057 next_event
->time
= next_time(next_event
, (time_t)0);
3058 if (nt
< next_event
->time
)
3059 next_event
->time
= nt
;
3060 switch (el_add(next_event
, next_event
->time
,
3061 (next_event
->u
)->ctid
)) {
3063 ignore_msg("resched", "cron", next_event
);
3065 case -2: /* event time lower than init time */
3070 msg("rescheduling a cron job");
3073 add_atevent(next_event
->u
, next_event
->cmd
, nt
, next_event
->etype
);
3074 msg("rescheduling at job");
3085 /* set up default queue definitions */
3086 for (i
= 0; i
< NQUEUE
; i
++) {
3087 qt
[i
].njob
= qd
.njob
;
3088 qt
[i
].nice
= qd
.nice
;
3089 qt
[i
].nwait
= qd
.nwait
;
3091 if (action
== DEFAULT
)
3093 if ((fd
= fopen(QUEDEFS
, "r")) == NULL
) {
3094 msg("cannot open quedefs file");
3095 msg("using default queue definitions");
3098 while (fgets(qbuf
, QBUFSIZ
, fd
) != NULL
) {
3099 if ((j
= qbuf
[0]-'a') < 0 || j
>= NQUEUE
|| qbuf
[1] != '.')
3102 qt
[j
].njob
= qq
.njob
;
3103 qt
[j
].nice
= qq
.nice
;
3104 qt
[j
].nwait
= qq
.nwait
;
3110 parsqdef(char *name
)
3117 while (isdigit(*name
)) {
3136 * defaults - read defaults from /etc/default/cron
3146 * get HZ value for environment
3148 if ((hz
= getenv("HZ")) == (char *)NULL
)
3149 (void) sprintf(hzname
, "HZ=%d", HZ
);
3151 (void) snprintf(hzname
, sizeof (hzname
), "HZ=%s", hz
);
3153 * get TZ value for environment
3155 (void) snprintf(tzone
, sizeof (tzone
), "TZ=%s",
3156 ((tz
= getenv("TZ")) != NULL
) ? tz
: DEFTZ
);
3158 if (defopen(DEFFILE
) == 0) {
3160 flags
= defcntl(DC_GETFLAGS
, 0);
3161 TURNOFF(flags
, DC_CASE
);
3162 (void) defcntl(DC_SETFLAGS
, flags
);
3164 if (((deflog
= defread("CRONLOG=")) == NULL
) ||
3165 (*deflog
== 'N') || (*deflog
== 'n'))
3169 /* fix for 1087611 - allow paths to be set in defaults file */
3170 if ((Def_path
= defread("PATH=")) != NULL
) {
3171 (void) strlcat(path
, Def_path
, LINE_MAX
);
3173 (void) strlcpy(path
, NONROOTPATH
, LINE_MAX
);
3175 if ((Def_supath
= defread("SUPATH=")) != NULL
) {
3176 (void) strlcat(supath
, Def_supath
, LINE_MAX
);
3178 (void) strlcpy(supath
, ROOTPATH
, LINE_MAX
);
3180 (void) defopen(NULL
);
3185 * Determine if a user entry for a job is still ok. The method used here
3186 * is a lot (about 75x) faster than using setgrent() / getgrent()
3187 * endgrent(). It should be safe because we use the sysconf to determine
3188 * the max, and it tolerates the max being 0.
3192 verify_user_cred(struct usr
*u
)
3195 size_t numUsrGrps
= 0;
3196 size_t numOrigGrps
= 0;
3201 * Maximum number of groups a user may be in concurrently. This
3202 * is a value which we obtain at runtime through a sysconf()
3206 static size_t nGroupsMax
= (size_t)-1;
3209 * Arrays for cron user's group list, constructed at startup to
3210 * be nGroupsMax elements long, used for verifying user
3211 * credentials prior to execution.
3214 static gid_t
*UsrGrps
;
3215 static gid_t
*OrigGrps
;
3217 if ((pw
= getpwnam(u
->name
)) == NULL
)
3218 return (VUC_BADUSER
);
3219 if (u
->home
!= NULL
) {
3220 if (strcmp(u
->home
, pw
->pw_dir
) != 0) {
3222 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
3223 (void) strcpy(u
->home
, pw
->pw_dir
);
3226 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
3227 (void) strcpy(u
->home
, pw
->pw_dir
);
3229 if (u
->uid
!= pw
->pw_uid
)
3230 u
->uid
= pw
->pw_uid
;
3231 if (u
->gid
!= pw
->pw_gid
)
3232 u
->gid
= pw
->pw_gid
;
3235 * Create the group id lists needed for job credential
3239 if (nGroupsMax
== (size_t)-1) {
3240 if ((nGroupsMax
= sysconf(_SC_NGROUPS_MAX
)) > 0) {
3241 UsrGrps
= xcalloc(nGroupsMax
, sizeof (gid_t
));
3242 OrigGrps
= xcalloc(nGroupsMax
, sizeof (gid_t
));
3246 (void) fprintf(stderr
, "nGroupsMax = %ld\n", nGroupsMax
);
3251 (void) fprintf(stderr
, "verify_user_cred (%s-%d)\n", pw
->pw_name
,
3253 (void) fprintf(stderr
, "verify_user_cred: pw->pw_gid = %d, "
3254 "u->gid = %d\n", pw
->pw_gid
, u
->gid
);
3257 retval
= (u
->gid
== pw
->pw_gid
) ? VUC_OK
: VUC_NOTINGROUP
;
3259 if (nGroupsMax
> 0) {
3260 numOrigGrps
= getgroups(nGroupsMax
, OrigGrps
);
3262 (void) initgroups(pw
->pw_name
, pw
->pw_gid
);
3263 numUsrGrps
= getgroups(nGroupsMax
, UsrGrps
);
3265 for (i
= 0; i
< numUsrGrps
; i
++) {
3266 if (UsrGrps
[i
] == u
->gid
) {
3273 (void) setgroups(numOrigGrps
, OrigGrps
);
3278 (void) fprintf(stderr
, "verify_user_cred: VUC = %d\n", retval
);
3285 set_user_cred(const struct usr
*u
, struct project
*pproj
)
3287 static char *progname
= "cron";
3288 int r
= 0, rval
= 0;
3290 if ((r
= pam_start(progname
, u
->name
, &pam_conv
, &pamh
))
3293 msg("pam_start returns %d\n", r
);
3296 goto set_eser_cred_exit
;
3299 r
= pam_acct_mgmt(pamh
, 0);
3301 msg("pam_acc_mgmt returns %d\n", r
);
3303 if (r
== PAM_ACCT_EXPIRED
) {
3305 goto set_eser_cred_exit
;
3307 if (r
== PAM_NEW_AUTHTOK_REQD
) {
3308 rval
= VUC_NEW_AUTH
;
3309 goto set_eser_cred_exit
;
3311 if (r
!= PAM_SUCCESS
) {
3313 goto set_eser_cred_exit
;
3316 if (pproj
!= NULL
) {
3317 size_t sz
= sizeof (PROJECT
) + strlen(pproj
->pj_name
);
3318 char *buf
= alloca(sz
);
3320 (void) snprintf(buf
, sz
, PROJECT
"%s", pproj
->pj_name
);
3321 (void) pam_set_item(pamh
, PAM_RESOURCE
, buf
);
3324 r
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
);
3325 if (r
!= PAM_SUCCESS
)
3329 (void) pam_end(pamh
, r
);
3334 clean_out_user(struct usr
*u
)
3336 if (next_event
->u
== u
) {
3341 clean_out_atjobs(u
);
3346 clean_out_atjobs(struct usr
*u
)
3348 struct event
*ev
, *pv
;
3350 for (pv
= NULL
, ev
= u
->atevents
;
3352 pv
= ev
, ev
= ev
->link
, free(pv
)) {
3353 el_remove(ev
->of
.at
.eventid
, 1);
3355 cron_unlink(ev
->cmd
);
3358 if (strlen(ATDIR
) + strlen(ev
->cmd
) + 2
3360 (void) sprintf(buf
, "%s/%s", ATDIR
, ev
->cmd
);
3371 clean_out_ctab(struct usr
*u
)
3374 el_remove(u
->ctid
, 0);
3380 cron_unlink(char *name
)
3385 if (r
== 0 || (r
== -1 && errno
== ENOENT
)) {
3386 (void) audit_cron_delete_anc_file(name
, NULL
);
3391 create_anc_ctab(struct event
*e
)
3393 if (audit_cron_create_anc_file(e
->u
->name
,
3394 (cwd
== CRON
) ? NULL
:CRONDIR
,
3395 e
->u
->name
, e
->u
->uid
) == -1) {
3396 process_anc_files(CRON_ANC_DELETE
);
3397 crabort("cannot create ancillary files for crontabs",
3398 REMOVE_FIFO
|CONSOLE_MSG
);
3403 delete_anc_ctab(struct event
*e
)
3405 (void) audit_cron_delete_anc_file(e
->u
->name
,
3406 (cwd
== CRON
) ? NULL
:CRONDIR
);
3410 create_anc_atjob(struct event
*e
)
3412 if (!e
->of
.at
.exists
)
3415 if (audit_cron_create_anc_file(e
->cmd
,
3416 (cwd
== AT
) ? NULL
:ATDIR
,
3417 e
->u
->name
, e
->u
->uid
) == -1) {
3418 process_anc_files(CRON_ANC_DELETE
);
3419 crabort("cannot create ancillary files for atjobs",
3420 REMOVE_FIFO
|CONSOLE_MSG
);
3425 delete_anc_atjob(struct event
*e
)
3427 if (!e
->of
.at
.exists
)
3430 (void) audit_cron_delete_anc_file(e
->cmd
,
3431 (cwd
== AT
) ? NULL
:ATDIR
);
3436 process_anc_files(int del
)
3438 struct usr
*u
= uhead
;
3441 if (!audit_cron_mode())
3445 if (u
->ctexists
&& u
->ctevents
!= NULL
) {
3452 if ((e
= e
->link
) == NULL
)
3457 if (u
->atevents
!= NULL
) {
3461 delete_anc_atjob(e
);
3463 create_anc_atjob(e
);
3464 if ((e
= e
->link
) == NULL
)
3469 if ((u
= u
->nextusr
) == NULL
)
3476 cron_conv(int num_msg
, struct pam_message
**msgs
,
3477 struct pam_response
**response
, void *appdata_ptr
)
3479 struct pam_message
**m
= msgs
;
3482 for (i
= 0; i
< num_msg
; i
++) {
3483 switch (m
[i
]->msg_style
) {
3486 if (m
[i
]->msg
!= NULL
) {
3487 (void) msg("%s\n", m
[i
]->msg
);
3499 * Cron creates process for other than job. Mail process is the
3500 * one which rinfo does not cover. Therefore, miscpid will keep
3501 * track of the pids executed from cron. Otherwise, we will see
3502 * "unexpected pid returned.." messages appear in the log file.
3505 miscpid_insert(pid_t pid
)
3509 mp
= xmalloc(sizeof (*mp
));
3511 mp
->next
= miscpid_head
;
3516 miscpid_delete(pid_t pid
)
3518 struct miscpid
*mp
, *omp
;
3522 for (mp
= miscpid_head
; mp
!= NULL
; mp
= mp
->next
) {
3523 if (mp
->pid
== pid
) {
3531 omp
->next
= mp
->next
;
3533 miscpid_head
= NULL
;
3540 * Establish contract terms such that all children are in abandoned
3541 * process contracts.
3544 contract_set_template(void)
3548 if ((fd
= open64(CTFS_ROOT
"/process/template", O_RDWR
)) < 0)
3549 crabort("cannot open process contract template",
3550 REMOVE_FIFO
| CONSOLE_MSG
);
3552 if (ct_pr_tmpl_set_param(fd
, 0) ||
3553 ct_tmpl_set_informative(fd
, 0) ||
3554 ct_pr_tmpl_set_fatal(fd
, CT_PR_EV_HWERR
))
3555 crabort("cannot establish contract template terms",
3556 REMOVE_FIFO
| CONSOLE_MSG
);
3558 if (ct_tmpl_activate(fd
))
3559 crabort("cannot activate contract template",
3560 REMOVE_FIFO
| CONSOLE_MSG
);
3566 * Clear active process contract template.
3569 contract_clear_template(void)
3573 if ((fd
= open64(CTFS_ROOT
"/process/template", O_RDWR
)) < 0)
3574 crabort("cannot open process contract template",
3575 REMOVE_FIFO
| CONSOLE_MSG
);
3577 if (ct_tmpl_clear(fd
))
3578 crabort("cannot clear contract template",
3579 REMOVE_FIFO
| CONSOLE_MSG
);
3585 * Abandon latest process contract unconditionally. If we have leaked [some
3586 * critical amount], exit such that the kernel reaps our contracts.
3589 contract_abandon_latest(pid_t pid
)
3593 static uint_t cts_lost
;
3595 if (cts_lost
> MAX_LOST_CONTRACTS
)
3596 crabort("repeated failure to abandon contracts",
3597 REMOVE_FIFO
| CONSOLE_MSG
);
3599 if (r
= contract_latest(&id
)) {
3600 msg("could not obtain latest contract for "
3601 "PID %ld: %s", pid
, strerror(r
));
3606 if (r
= contract_abandon_id(id
)) {
3607 msg("could not abandon latest contract %ld: %s", id
,
3614 static struct shared
*
3615 create_shared(void *obj
, void * (*obj_alloc
)(void *obj
),
3616 void (*obj_free
)(void *))
3620 if ((out
= xmalloc(sizeof (struct shared
))) == NULL
) {
3623 if ((out
->obj
= obj_alloc(obj
)) == NULL
) {
3628 out
->free
= obj_free
;
3633 static struct shared
*
3634 create_shared_str(char *str
)
3636 return (create_shared(str
, (void *(*)(void *))strdup
, free
));
3639 static struct shared
*
3640 dup_shared(struct shared
*obj
)
3649 rel_shared(struct shared
*obj
)
3651 if (obj
&& (--obj
->count
) == 0) {
3652 obj
->free(obj
->obj
);
3658 get_obj(struct shared
*obj
)