1 /*
2 * CDDL HEADER START
3 *
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.
7 *
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.
12 *
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]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 *
25 * Copyright 2013 Joshua M. Clulow <josh@sysmgr.org>
26 *
27 * Copyright (c) 2014 Gary Mills
28 * Copyright (c) 2016 by Delphix. All rights reserved.
29 * Copyright 2022 Sebastian Wiedenroth
30 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
31 */
32
33 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
34 /* All Rights Reserved */
35
36 /* Copyright (c) 1987, 1988 Microsoft Corporation */
37 /* All Rights Reserved */
38
39 #include <sys/contract/process.h>
40 #include <sys/ctfs.h>
41 #include <sys/param.h>
42 #include <sys/resource.h>
43 #include <sys/stat.h>
44 #include <sys/task.h>
45 #include <sys/time.h>
46 #include <sys/types.h>
47 #include <sys/utsname.h>
48 #include <sys/wait.h>
49
50 #include <security/pam_appl.h>
51
52 #include <alloca.h>
53 #include <ctype.h>
54 #include <deflt.h>
55 #include <dirent.h>
56 #include <errno.h>
57 #include <fcntl.h>
58 #include <grp.h>
59 #include <libcontract.h>
60 #include <libcontract_priv.h>
61 #include <limits.h>
62 #include <locale.h>
63 #include <poll.h>
64 #include <project.h>
65 #include <pwd.h>
66 #include <signal.h>
67 #include <stdarg.h>
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
71 #include <stropts.h>
72 #include <time.h>
73 #include <unistd.h>
74 #include <libzoneinfo.h>
75
76 #include "cron.h"
77
78 /*
79 * #define DEBUG
80 */
81
82 #define MAIL "/usr/bin/mail" /* mail program to use */
83 #define CONSOLE "/dev/console" /* where messages go when cron dies */
84
85 #define TMPINFILE "/tmp/crinXXXXXX" /* file to put stdin in for cmd */
86 #define TMPDIR "/tmp"
87 #define PFX "crout"
88 #define TMPOUTFILE "/tmp/croutXXXXXX" /* file to place stdout, stderr */
89
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 */
93
94 #define INFINITY 2147483647L /* upper bound on time */
95 #define CUSHION 180L
96 #define ZOMB 100 /* proc slot used for mailing output */
97
98 #define JOBF 'j'
99 #define NICEF 'n'
100 #define USERF 'u'
101 #define WAITF 'w'
102
103 #define BCHAR '>'
104 #define ECHAR '<'
105
106 #define DEFAULT 0
107 #define LOAD 1
108 #define QBUFSIZ 80
109
110 /* Defined actions for crabort() routine */
111 #define NO_ACTION 000
112 #define REMOVE_FIFO 001
113 #define CONSOLE_MSG 002
114
115 #define BADCD "can't change directory to the crontab directory."
116 #define NOREADDIR "can't read the crontab directory."
117
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."
121
122 #define BADSTAT "can't access your crontab or at-job file. Resubmit it."
123 #define BADPROJID "can't set project id for your job."
124 #define CANTCDHOME "can't change directory to %s.\
125 \nYour commands will not be executed."
126 #define CANTEXECSH "unable to exec the shell, %s, for one of your \
127 commands."
128 #define CANT_STR_LEN (sizeof (CANTEXECSH) > sizeof (CANTCDHOME) ? \
129 sizeof (CANTEXECSH) : sizeof (CANTCDHOME))
130 #define NOREAD "can't read your crontab file. Resubmit it."
131 #define BADTYPE "crontab or at-job file is not a regular file.\n"
132 #define NOSTDIN "unable to create a standard input file for \
133 one of your crontab commands. \
134 \nThat command was not executed."
135
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"
141
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."
146
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"
151
152 #define DIDFORK didfork
153 #define NOFORK !didfork
154
155 #define MAILBUFLEN (8*1024)
156 #define LINELIMIT 80
157 #define MAILBINITFREE (MAILBUFLEN - (sizeof (cte_intro) - 1) \
158 - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1)
159
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 */
165
166 #define PROJECT "project="
167
168 #define MAX_LOST_CONTRACTS 2048 /* reset if this many failed abandons */
169
170 #define FORMAT "%a %b %e %H:%M:%S %Y"
171 static char timebuf[80];
172
173 static struct message msgbuf;
174
175 struct shared {
176 int count; /* usage count */
177 void (*free)(void *obj); /* routine that will free obj */
178 void *obj; /* object */
179 };
180
181 struct event {
182 time_t time; /* time of the event */
183 short etype; /* what type of event; 0=cron, 1=at */
184 char *cmd; /* command for cron, job name for at */
185 struct usr *u; /* ptr to the owner (usr) of this event */
186 struct event *link; /* ptr to another event for this user */
187 union {
188 struct { /* for crontab events */
189 char *minute; /* (these */
190 char *hour; /* fields */
191 char *daymon; /* are */
192 char *month; /* from */
193 char *dayweek; /* crontab) */
194 char *input; /* ptr to stdin */
195 struct shared *tz; /* timezone of this event */
196 struct shared *home; /* directory for this event */
197 struct shared *shell; /* shell for this event */
198 uint32_t max_random_delay; /* max. random delay */
199 } ct;
200 struct { /* for at events */
201 short exists; /* for revising at events */
202 int eventid; /* for el_remove-ing at events */
203 } at;
204 } of;
205 };
206
207 struct usr {
208 char *name; /* name of user (e.g. "root") */
209 char *home; /* home directory for user */
210 uid_t uid; /* user id */
211 gid_t gid; /* group id */
212 int aruncnt; /* counter for running jobs per uid */
213 int cruncnt; /* counter for running cron jobs per uid */
214 int ctid; /* for el_remove-ing crontab events */
215 short ctexists; /* for revising crontab events */
216 struct event *ctevents; /* list of this usr's crontab events */
217 struct event *atevents; /* list of this usr's at events */
218 struct usr *nextusr;
219 }; /* ptr to next user */
220
221 static struct queue
222 {
223 int njob; /* limit */
224 int nice; /* nice for execution */
225 int nwait; /* wait time to next execution attempt */
226 int nrun; /* number running */
227 }
228 qd = {100, 2, 60}, /* default values for queue defs */
229 qt[NQUEUE];
230 static struct queue qq;
231
232 static struct runinfo
233 {
234 pid_t pid;
235 short que;
236 struct usr *rusr; /* pointer to usr struct */
237 char *outfile; /* file where stdout & stderr are trapped */
238 short jobtype; /* what type of event: 0=cron, 1=at */
239 char *jobname; /* command for "cron", jobname for "at" */
240 int mailwhendone; /* 1 = send mail even if no ouptut */
241 struct runinfo *next;
242 } *rthead;
243
244 static struct miscpid {
245 pid_t pid;
246 struct miscpid *next;
247 } *miscpid_head;
248
249 static pid_t cron_pid; /* own pid */
250 static char didfork = 0; /* flag to see if I'm process group leader */
251 static int msgfd; /* file descriptor for fifo queue */
252 static int ecid = 1; /* event class id for el_remove(); MUST be set to 1 */
253 static int delayed; /* is job being rescheduled or did it run first time */
254 static int cwd; /* current working directory */
255 static struct event *next_event; /* the next event to execute */
256 static struct usr *uhead; /* ptr to the list of users */
257
258 /* Variables for error handling at reading crontabs. */
259 static char cte_intro[] = "Line(s) with errors:\n\n";
260 static char cte_trail1[] = "\nMax number of errors encountered.";
261 static char cte_trail2[] = " Evaluation of crontab aborted.\n";
262 static int cte_free = MAILBINITFREE; /* Free buffer space */
263 static char *cte_text = NULL; /* Text buffer pointer */
264 static char *cte_lp; /* Next free line in cte_text */
265 static int cte_nvalid; /* Valid lines found */
266
267 /* user's default environment for the shell */
268 #define ROOTPATH "PATH=/usr/sbin:/usr/bin"
269 #define NONROOTPATH "PATH=/usr/bin:"
270
271 static char *Def_supath = NULL;
272 static char *Def_path = NULL;
273 static char path[LINE_MAX] = "PATH=";
274 static char supath[LINE_MAX] = "PATH=";
275 static char homedir[LINE_MAX] = ENV_HOME;
276 static char logname[LINE_MAX] = "LOGNAME=";
277 static char tzone[LINE_MAX] = ENV_TZ;
278 static char *envinit[] = {
279 homedir,
280 logname,
281 ROOTPATH,
282 "SHELL=/usr/bin/sh",
283 tzone,
284 NULL
285 };
286
287 extern char **environ;
288
289 #define DEFTZ "GMT"
290 static int log = 0;
291 static char hzname[10];
292
293 static void cronend(int);
294 static void thaw_handler(int);
295 static void child_handler(int);
296 static void child_sigreset(void);
297
298 static void mod_ctab(char *, time_t);
299 static void mod_atjob(char *, time_t);
300 static void add_atevent(struct usr *, char *, time_t, int);
301 static void rm_ctevents(struct usr *);
302 static void cleanup(struct runinfo *rn, int r);
303 static void crabort(char *, int);
304 static void msg(char *fmt, ...);
305 static void ignore_msg(char *, char *, struct event *);
306 static void logit(int, struct runinfo *, int);
307 static void parsqdef(char *);
308 static void defaults();
309 static void initialize(int);
310 static void quedefs(int);
311 static int idle(long);
312 static struct usr *find_usr(char *);
313 static int ex(struct event *e);
314 static void read_dirs(int);
315 static void mail(char *, char *, 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);
336
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 *);
342
343 static int set_user_cred(const struct usr *, struct project *);
344
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);
349 /*
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.
353 */
354 static time_t last_time, init_time, t_old;
355 static int reset_needed; /* set to 1 when cron(8) needs to re-initialize */
356
357 static int refresh;
358 static sigset_t defmask, sigmask;
359
360 /*
361 * BSM hooks
362 */
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();
371
372 static int cron_conv(int, const struct pam_message **,
373 struct pam_response **, void *);
374
375 static struct pam_conv pam_conv = {cron_conv, NULL};
376 static pam_handle_t *pamh; /* Authentication handle */
377
378 /*
379 * Function to help check a user's credentials.
380 */
381
382 static int verify_user_cred(struct usr *u);
383
384 /*
385 * Values returned by verify_user_cred and set_user_cred:
386 */
387
388 #define VUC_OK 0
389 #define VUC_BADUSER 1
390 #define VUC_NOTINGROUP 2
391 #define VUC_EXPIRED 3
392 #define VUC_NEW_AUTH 4
393
394 /*
395 * Modes of process_anc_files function
396 */
397 #define CRON_ANC_DELETE 1
398 #define CRON_ANC_CREATE 0
399
400 /*
401 * Functions to remove a user or job completely from the running database.
402 */
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);
408
409 /*
410 * functions in elm.c
411 */
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);
418
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);
424
425 int
main(int argc,char * argv[])426 main(int argc, char *argv[])
427 {
428 time_t t;
429 time_t ne_time; /* amt of time until next event execution */
430 time_t newtime, lastmtime = 0L;
431 struct usr *u;
432 struct event *e, *e2, *eprev;
433 struct stat buf;
434 pid_t rfork;
435 struct sigaction act;
436
437 /*
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(8) daemon
440 * initialized.
441 * Other cases where a reset is needed is when ex() finds that the
442 * event to be executed is being run at the wrong time, or when idle()
443 * determines that time was reset.
444 * We immediately return to the top of the while (TRUE) loop in
445 * main() where the event list is cleared and rebuilt, and reset_needed
446 * is set back to 0.
447 */
448 reset_needed = 0;
449
450 /*
451 * Only the privileged user can run this command.
452 */
453 if (getuid() != 0)
454 crabort(NOTALLOWED, 0);
455
456 begin:
457 (void) setlocale(LC_ALL, "");
458 /* fork unless 'nofork' is specified */
459 if ((argc <= 1) || (strcmp(argv[1], "nofork"))) {
460 if ((rfork = fork()) != 0) {
461 if (rfork == (pid_t)-1) {
462 (void) sleep(30);
463 goto begin;
464 }
465 return (0);
466 }
467 didfork++;
468 (void) setpgrp(); /* detach cron from console */
469 }
470
471 (void) umask(022);
472 (void) signal(SIGHUP, SIG_IGN);
473 (void) signal(SIGINT, SIG_IGN);
474 (void) signal(SIGQUIT, SIG_IGN);
475 (void) signal(SIGTERM, cronend);
476
477 defaults();
478 initialize(1);
479 quedefs(DEFAULT); /* load default queue definitions */
480 cron_pid = getpid();
481 msg("*** cron started *** pid = %d", cron_pid);
482
483 /* setup THAW handler */
484 act.sa_handler = thaw_handler;
485 act.sa_flags = 0;
486 (void) sigemptyset(&act.sa_mask);
487 (void) sigaction(SIGTHAW, &act, NULL);
488
489 /* setup CHLD handler */
490 act.sa_handler = child_handler;
491 act.sa_flags = 0;
492 (void) sigemptyset(&act.sa_mask);
493 (void) sigaddset(&act.sa_mask, SIGCLD);
494 (void) sigaction(SIGCLD, &act, NULL);
495
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);
501
502 t_old = init_time;
503 last_time = t_old;
504 for (;;) { /* MAIN LOOP */
505 t = time(NULL);
506 if ((t_old > t) || (t-last_time > CUSHION) || reset_needed) {
507 reset_needed = 0;
508 /*
509 * the time was set backwards or forward or
510 * refresh is requested.
511 */
512 if (refresh)
513 msg("re-scheduling jobs");
514 else
515 msg("time was reset, re-initializing");
516 el_delete();
517 u = uhead;
518 while (u != NULL) {
519 rm_ctevents(u);
520 e = u->atevents;
521 while (e != NULL) {
522 free(e->cmd);
523 e2 = e->link;
524 free(e);
525 e = e2;
526 }
527 u->atevents = NULL;
528 u = u->nextusr;
529 }
530 (void) close(msgfd);
531 initialize(0);
532 t = time(NULL);
533 last_time = t;
534 /*
535 * reset_needed might have been set in the functions
536 * call path from initialize()
537 */
538 if (reset_needed) {
539 continue;
540 }
541 }
542 t_old = t;
543
544 if (next_event == NULL && !el_empty()) {
545 next_event = (struct event *)el_first();
546 }
547 if (next_event == NULL) {
548 ne_time = INFINITY;
549 } else {
550 ne_time = next_event->time - t;
551 #ifdef DEBUG
552 cftime(timebuf, "%+", &next_event->time);
553 (void) fprintf(stderr, "next_time=%ld %s\n",
554 next_event->time, timebuf);
555 #endif
556 }
557 if (ne_time > 0) {
558 /*
559 * reset_needed may be set in the functions call path
560 * from idle()
561 */
562 if (idle(ne_time) || reset_needed) {
563 reset_needed = 1;
564 continue;
565 }
566 }
567
568 if (stat(QUEDEFS, &buf)) {
569 msg("cannot stat QUEDEFS file");
570 } else if (lastmtime != buf.st_mtime) {
571 quedefs(LOAD);
572 lastmtime = buf.st_mtime;
573 }
574
575 last_time = next_event->time; /* save execution time */
576
577 /*
578 * reset_needed may be set in the functions call path
579 * from ex()
580 */
581 if (ex(next_event) || reset_needed) {
582 reset_needed = 1;
583 continue;
584 }
585
586 switch (next_event->etype) {
587 case CRONEVENT:
588 /* add cronevent back into the main event list */
589 if (delayed) {
590 delayed = 0;
591 break;
592 }
593
594 /*
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.
601 */
602
603 if (last_time > time(NULL)) {
604 msg(CLOCK_DRIFT);
605 /*
606 * bump up to next 30 second
607 * increment
608 * 1 <= newtime <= 30
609 */
610 newtime = 30 - (last_time % 30);
611 newtime += last_time;
612
613 /*
614 * get the next scheduled event,
615 * not the one that we just
616 * kicked off!
617 */
618 next_event->time =
619 next_time(next_event, newtime);
620 t_old = time(NULL);
621 } else {
622 next_event->time =
623 next_time(next_event, (time_t)0);
624 }
625 #ifdef DEBUG
626 cftime(timebuf, "%+", &next_event->time);
627 (void) fprintf(stderr,
628 "pushing back cron event %s at %ld (%s)\n",
629 next_event->cmd, next_event->time, timebuf);
630 #endif
631
632 switch (el_add(next_event, next_event->time,
633 (next_event->u)->ctid)) {
634 case -1:
635 ignore_msg("main", "cron", next_event);
636 break;
637 case -2: /* event time lower than init time */
638 reset_needed = 1;
639 break;
640 }
641 break;
642 default:
643 /* remove at or batch job from system */
644 if (delayed) {
645 delayed = 0;
646 break;
647 }
648 eprev = NULL;
649 e = (next_event->u)->atevents;
650 while (e != NULL) {
651 if (e == next_event) {
652 if (eprev == NULL)
653 (e->u)->atevents = e->link;
654 else
655 eprev->link = e->link;
656 free(e->cmd);
657 free(e);
658 break;
659 } else {
660 eprev = e;
661 e = e->link;
662 }
663 }
664 break;
665 }
666 next_event = NULL;
667 }
668
669 /*NOTREACHED*/
670 }
671
672 static void
initialize(int firstpass)673 initialize(int firstpass)
674 {
675 #ifdef DEBUG
676 (void) fprintf(stderr, "in initialize\n");
677 #endif
678 if (firstpass) {
679 /* for mail(1), make sure messages come from root */
680 if (putenv("LOGNAME=root") != 0) {
681 crabort("cannot expand env variable",
682 REMOVE_FIFO|CONSOLE_MSG);
683 }
684 if (access(FIFO, R_OK) == -1) {
685 if (errno == ENOENT) {
686 if (mknod(FIFO, S_IFIFO|0600, 0) != 0)
687 crabort("cannot create fifo queue",
688 REMOVE_FIFO|CONSOLE_MSG);
689 } else {
690 if (NOFORK) {
691 /* didn't fork... init(8) is waiting */
692 (void) sleep(60);
693 }
694 perror("FIFO");
695 crabort("cannot access fifo queue",
696 REMOVE_FIFO|CONSOLE_MSG);
697 }
698 } else {
699 if (NOFORK) {
700 /* didn't fork... init(8) is waiting */
701 (void) sleep(60);
702 /*
703 * the wait is painful, but we don't want
704 * init respawning this quickly
705 */
706 }
707 crabort("cannot start cron; FIFO exists", CONSOLE_MSG);
708 }
709 }
710
711 if ((msgfd = open(FIFO, O_RDWR)) < 0) {
712 perror("! open");
713 crabort("cannot open fifo queue", REMOVE_FIFO|CONSOLE_MSG);
714 }
715
716 init_time = time(NULL);
717 el_init(8, init_time, (time_t)(60*60*24), 10);
718
719 init_time = time(NULL);
720 el_init(8, init_time, (time_t)(60*60*24), 10);
721
722 /*
723 * read directories, create users list, and add events to the
724 * main event list. Only zero user list on firstpass.
725 */
726 if (firstpass)
727 uhead = NULL;
728 read_dirs(firstpass);
729 next_event = NULL;
730
731 if (!firstpass)
732 return;
733
734 /* stdout is log file */
735 if (freopen(ACCTFILE, "a", stdout) == NULL)
736 (void) fprintf(stderr, "cannot open %s\n", ACCTFILE);
737
738 /* log should be root-only */
739 (void) fchmod(1, S_IRUSR|S_IWUSR);
740
741 /* stderr also goes to ACCTFILE */
742 (void) close(fileno(stderr));
743 (void) dup(1);
744 /* null for stdin */
745 (void) freopen("/dev/null", "r", stdin);
746
747 contract_set_template();
748 }
749
750 static void
read_dirs(int first)751 read_dirs(int first)
752 {
753 DIR *dir;
754 struct dirent *dp;
755 char *ptr;
756 int jobtype;
757 time_t tim;
758
759
760 if (chdir(CRONDIR) == -1)
761 crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG);
762 cwd = CRON;
763 if ((dir = opendir(".")) == NULL)
764 crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG);
765 while ((dp = readdir(dir)) != NULL) {
766 if (!valid_entry(dp->d_name, CRONEVENT))
767 continue;
768 init_cronevent(dp->d_name, first);
769 }
770 (void) closedir(dir);
771
772 if (chdir(ATDIR) == -1) {
773 msg("cannot chdir to at directory");
774 return;
775 }
776 if ((dir = opendir(".")) == NULL) {
777 msg("cannot read at at directory");
778 return;
779 }
780 cwd = AT;
781 while ((dp = readdir(dir)) != NULL) {
782 if (!valid_entry(dp->d_name, ATEVENT))
783 continue;
784 ptr = dp->d_name;
785 if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
786 continue;
787 ptr++;
788 if (!isalpha(*ptr))
789 continue;
790 jobtype = *ptr - 'a';
791 if (jobtype >= NQUEUE) {
792 cron_unlink(dp->d_name);
793 continue;
794 }
795 init_atevent(dp->d_name, tim, jobtype, first);
796 }
797 (void) closedir(dir);
798 }
799
800 static int
valid_entry(char * name,int type)801 valid_entry(char *name, int type)
802 {
803 struct stat buf;
804
805 if (strcmp(name, ".") == 0 ||
806 strcmp(name, "..") == 0)
807 return (0);
808
809 /* skip over ancillary file names */
810 if (audit_cron_is_anc_name(name))
811 return (0);
812
813 if (stat(name, &buf)) {
814 mail(name, BADSTAT, ERR_UNIXERR);
815 cron_unlink(name);
816 return (0);
817 }
818 if (!S_ISREG(buf.st_mode)) {
819 mail(name, BADTYPE, ERR_NOTREG);
820 cron_unlink(name);
821 return (0);
822 }
823 if (type == ATEVENT) {
824 if (!(buf.st_mode & ISUID)) {
825 cron_unlink(name);
826 return (0);
827 }
828 }
829 return (1);
830 }
831
832 struct usr *
create_ulist(char * name,int type)833 create_ulist(char *name, int type)
834 {
835 struct usr *u;
836
837 u = xcalloc(1, sizeof (struct usr));
838 u->name = xstrdup(name);
839 if (type == CRONEVENT) {
840 u->ctexists = TRUE;
841 u->ctid = ecid++;
842 } else {
843 u->ctexists = FALSE;
844 u->ctid = 0;
845 }
846 u->uid = (uid_t)-1;
847 u->gid = (uid_t)-1;
848 u->nextusr = uhead;
849 uhead = u;
850 return (u);
851 }
852
853 void
init_cronevent(char * name,int first)854 init_cronevent(char *name, int first)
855 {
856 struct usr *u;
857
858 if (first) {
859 u = create_ulist(name, CRONEVENT);
860 readcron(u, 0);
861 } else {
862 if ((u = find_usr(name)) == NULL) {
863 u = create_ulist(name, CRONEVENT);
864 readcron(u, 0);
865 } else {
866 u->ctexists = TRUE;
867 rm_ctevents(u);
868 el_remove(u->ctid, 0);
869 readcron(u, 0);
870 }
871 }
872 }
873
874 void
init_atevent(char * name,time_t tim,int jobtype,int first)875 init_atevent(char *name, time_t tim, int jobtype, int first)
876 {
877 struct usr *u;
878
879 if (first) {
880 u = create_ulist(name, ATEVENT);
881 add_atevent(u, name, tim, jobtype);
882 } else {
883 if ((u = find_usr(name)) == NULL) {
884 u = create_ulist(name, ATEVENT);
885 add_atevent(u, name, tim, jobtype);
886 } else {
887 update_atevent(u, name, tim, jobtype);
888 }
889 }
890 }
891
892 static void
mod_ctab(char * name,time_t reftime)893 mod_ctab(char *name, time_t reftime)
894 {
895 struct passwd *pw;
896 struct stat buf;
897 struct usr *u;
898 char namebuf[LINE_MAX];
899 char *pname;
900
901 /* skip over ancillary file names */
902 if (audit_cron_is_anc_name(name))
903 return;
904
905 if ((pw = getpwnam(name)) == NULL) {
906 msg("No such user as %s - cron entries not created", name);
907 return;
908 }
909 if (cwd != CRON) {
910 if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
911 CRONDIR, name) >= sizeof (namebuf)) {
912 msg("Too long path name %s - cron entries not created",
913 namebuf);
914 return;
915 }
916 pname = namebuf;
917 } else {
918 pname = name;
919 }
920 /*
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
924 */
925 #ifdef BOURNESHELLONLY
926 if ((strcmp(pw->pw_shell, "") != 0) &&
927 (strcmp(pw->pw_shell, SHELL) != 0)) {
928 mail(name, BADSHELL, ERR_CANTEXECCRON);
929 cron_unlink(pname);
930 return;
931 }
932 #endif
933 if (stat(pname, &buf)) {
934 mail(name, BADSTAT, ERR_UNIXERR);
935 cron_unlink(pname);
936 return;
937 }
938 if (!S_ISREG(buf.st_mode)) {
939 mail(name, BADTYPE, ERR_CRONTABENT);
940 return;
941 }
942 if ((u = find_usr(name)) == NULL) {
943 #ifdef DEBUG
944 (void) fprintf(stderr, "new user (%s) with a crontab\n", name);
945 #endif
946 u = create_ulist(name, CRONEVENT);
947 u->home = xmalloc(strlen(pw->pw_dir) + 1);
948 (void) strcpy(u->home, pw->pw_dir);
949 u->uid = pw->pw_uid;
950 u->gid = pw->pw_gid;
951 readcron(u, reftime);
952 } else {
953 u->uid = pw->pw_uid;
954 u->gid = pw->pw_gid;
955 if (u->home != NULL) {
956 if (strcmp(u->home, pw->pw_dir) != 0) {
957 free(u->home);
958 u->home = xmalloc(strlen(pw->pw_dir) + 1);
959 (void) strcpy(u->home, pw->pw_dir);
960 }
961 } else {
962 u->home = xmalloc(strlen(pw->pw_dir) + 1);
963 (void) strcpy(u->home, pw->pw_dir);
964 }
965 u->ctexists = TRUE;
966 if (u->ctid == 0) {
967 #ifdef DEBUG
968 (void) fprintf(stderr, "%s now has a crontab\n",
969 u->name);
970 #endif
971 /* user didnt have a crontab last time */
972 u->ctid = ecid++;
973 u->ctevents = NULL;
974 readcron(u, reftime);
975 return;
976 }
977 #ifdef DEBUG
978 (void) fprintf(stderr, "%s has revised their crontab\n",
979 u->name);
980 #endif
981 rm_ctevents(u);
982 el_remove(u->ctid, 0);
983 readcron(u, reftime);
984 }
985 }
986
987 static void
mod_atjob(char * name,time_t reftime __unused)988 mod_atjob(char *name, time_t reftime __unused)
989 {
990 char *ptr;
991 time_t tim;
992 struct passwd *pw;
993 struct stat buf;
994 struct usr *u;
995 char namebuf[PATH_MAX];
996 char *pname;
997 int jobtype;
998
999 ptr = name;
1000 if (((tim = num(&ptr)) == 0) || (*ptr != '.'))
1001 return;
1002 ptr++;
1003 if (!isalpha(*ptr))
1004 return;
1005 jobtype = *ptr - 'a';
1006
1007 /* check for audit ancillary file */
1008 if (audit_cron_is_anc_name(name))
1009 return;
1010
1011 if (cwd != AT) {
1012 if (snprintf(namebuf, sizeof (namebuf), "%s/%s", ATDIR, name)
1013 >= sizeof (namebuf)) {
1014 return;
1015 }
1016 pname = namebuf;
1017 } else {
1018 pname = name;
1019 }
1020 if (stat(pname, &buf) || jobtype >= NQUEUE) {
1021 cron_unlink(pname);
1022 return;
1023 }
1024 if (!(buf.st_mode & ISUID) || !S_ISREG(buf.st_mode)) {
1025 cron_unlink(pname);
1026 return;
1027 }
1028 if ((pw = getpwuid(buf.st_uid)) == NULL) {
1029 cron_unlink(pname);
1030 return;
1031 }
1032 /*
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
1036 */
1037 #ifdef BOURNESHELLONLY
1038 if ((strcmp(pw->pw_shell, "") != 0) &&
1039 (strcmp(pw->pw_shell, SHELL) != 0)) {
1040 mail(pw->pw_name, BADSHELL, ERR_CANTEXECAT);
1041 cron_unlink(pname);
1042 return;
1043 }
1044 #endif
1045 if ((u = find_usr(pw->pw_name)) == NULL) {
1046 #ifdef DEBUG
1047 (void) fprintf(stderr, "new user (%s) with an at job = %s\n",
1048 pw->pw_name, name);
1049 #endif
1050 u = create_ulist(pw->pw_name, ATEVENT);
1051 u->home = xstrdup(pw->pw_dir);
1052 u->uid = pw->pw_uid;
1053 u->gid = pw->pw_gid;
1054 add_atevent(u, name, tim, jobtype);
1055 } else {
1056 u->uid = pw->pw_uid;
1057 u->gid = pw->pw_gid;
1058 free(u->home);
1059 u->home = xstrdup(pw->pw_dir);
1060 update_atevent(u, name, tim, jobtype);
1061 }
1062 }
1063
1064 static void
add_atevent(struct usr * u,char * job,time_t tim,int jobtype)1065 add_atevent(struct usr *u, char *job, time_t tim, int jobtype)
1066 {
1067 struct event *e;
1068
1069 e = xmalloc(sizeof (struct event));
1070 e->etype = jobtype;
1071 e->cmd = xmalloc(strlen(job) + 1);
1072 (void) strcpy(e->cmd, job);
1073 e->u = u;
1074 e->link = u->atevents;
1075 u->atevents = e;
1076 e->of.at.exists = TRUE;
1077 e->of.at.eventid = ecid++;
1078 if (tim < init_time) /* old job */
1079 e->time = init_time;
1080 else
1081 e->time = tim;
1082 #ifdef DEBUG
1083 (void) fprintf(stderr, "add_atevent: user=%s, job=%s, time=%ld\n",
1084 u->name, e->cmd, e->time);
1085 #endif
1086 if (el_add(e, e->time, e->of.at.eventid) < 0) {
1087 ignore_msg("add_atevent", "at", e);
1088 }
1089 }
1090
1091 void
update_atevent(struct usr * u,char * name,time_t tim,int jobtype)1092 update_atevent(struct usr *u, char *name, time_t tim, int jobtype)
1093 {
1094 struct event *e;
1095
1096 e = u->atevents;
1097 while (e != NULL) {
1098 if (strcmp(e->cmd, name) == 0) {
1099 e->of.at.exists = TRUE;
1100 break;
1101 } else {
1102 e = e->link;
1103 }
1104 }
1105 if (e == NULL) {
1106 #ifdef DEBUG
1107 (void) fprintf(stderr, "%s has a new at job = %s\n",
1108 u->name, name);
1109 #endif
1110 add_atevent(u, name, tim, jobtype);
1111 }
1112 }
1113
1114 static char line[CTLINESIZE]; /* holds a line from a crontab file */
1115 static int cursor; /* cursor for the above line */
1116
1117 static void
readcron(struct usr * u,time_t reftime)1118 readcron(struct usr *u, time_t reftime)
1119 {
1120 /*
1121 * readcron reads in a crontab file for a user (u). The list of
1122 * events for user u is built, and u->events is made to point to
1123 * this list. Each event is also entered into the main event
1124 * list.
1125 */
1126 FILE *cf; /* cf will be a user's crontab file */
1127 struct event *e;
1128 int start;
1129 unsigned int i;
1130 char namebuf[PATH_MAX];
1131 char *pname;
1132 struct shared *tz = NULL;
1133 struct shared *home = NULL;
1134 struct shared *shell = NULL;
1135 uint32_t max_random_delay = 0;
1136 int lineno = 0;
1137 const char *errstr;
1138
1139 /* read the crontab file */
1140 cte_init(); /* Init error handling */
1141 if (cwd != CRON) {
1142 if (snprintf(namebuf, sizeof (namebuf), "%s/%s",
1143 CRONDIR, u->name) >= sizeof (namebuf)) {
1144 return;
1145 }
1146 pname = namebuf;
1147 } else {
1148 pname = u->name;
1149 }
1150 if ((cf = fopen(pname, "r")) == NULL) {
1151 mail(u->name, NOREAD, ERR_UNIXERR);
1152 return;
1153 }
1154 while (fgets(line, CTLINESIZE, cf) != NULL) {
1155 char *tmp;
1156 /* process a line of a crontab file */
1157 lineno++;
1158 if (cte_istoomany())
1159 break;
1160 cursor = 0;
1161 while (line[cursor] == ' ' || line[cursor] == '\t')
1162 cursor++;
1163 if (line[cursor] == '#' || line[cursor] == '\n')
1164 continue;
1165
1166 if (strncmp(&line[cursor], ENV_TZ,
1167 strlen(ENV_TZ)) == 0) {
1168 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1169 *tmp = '\0';
1170 }
1171
1172 if (!isvalid_tz(&line[cursor + strlen(ENV_TZ)], NULL,
1173 _VTZ_ALL)) {
1174 cte_add(lineno, line);
1175 break;
1176 }
1177 if (tz == NULL || strcmp(&line[cursor], get_obj(tz))) {
1178 rel_shared(tz);
1179 tz = create_shared_str(&line[cursor]);
1180 }
1181 continue;
1182 }
1183
1184 if (strncmp(&line[cursor], ENV_HOME,
1185 strlen(ENV_HOME)) == 0) {
1186 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1187 *tmp = '\0';
1188 }
1189 if (home == NULL ||
1190 strcmp(&line[cursor], get_obj(home))) {
1191 rel_shared(home);
1192 home = create_shared_str(
1193 &line[cursor + strlen(ENV_HOME)]);
1194 }
1195 continue;
1196 }
1197
1198 if (strncmp(&line[cursor], ENV_SHELL,
1199 strlen(ENV_SHELL)) == 0) {
1200 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1201 *tmp = '\0';
1202 }
1203 if (shell == NULL ||
1204 strcmp(&line[cursor], get_obj(shell))) {
1205 rel_shared(shell);
1206 shell = create_shared_str(&line[cursor]);
1207 }
1208 continue;
1209 }
1210
1211 if (strncmp(&line[cursor], ENV_RANDOM_DELAY,
1212 strlen(ENV_RANDOM_DELAY)) == 0) {
1213 if ((tmp = strchr(&line[cursor], '\n')) != NULL) {
1214 *tmp = '\0';
1215 }
1216
1217 max_random_delay = strtonum(
1218 &line[cursor + strlen(ENV_RANDOM_DELAY)], 0,
1219 UINT32_MAX / 60, &errstr);
1220 if (errstr != NULL) {
1221 cte_add(lineno, line);
1222 break;
1223 }
1224
1225 continue;
1226 }
1227
1228 e = xmalloc(sizeof (struct event));
1229 e->etype = CRONEVENT;
1230
1231 if (next_field(0, 59, line, &cursor,
1232 &e->of.ct.minute) != CFOK ||
1233 next_field(0, 23, line, &cursor, &e->of.ct.hour) != CFOK ||
1234 next_field(1, 31, line, &cursor,
1235 &e->of.ct.daymon) != CFOK ||
1236 next_field(1, 12, line, &cursor, &e->of.ct.month) != CFOK ||
1237 next_field(0, 6, line, &cursor,
1238 &e->of.ct.dayweek) != CFOK) {
1239 #ifdef DEBUG
1240 (void) fprintf(stderr, "Error: %d %s", lineno, line);
1241 #endif
1242 free(e);
1243 cte_add(lineno, line);
1244 continue;
1245 }
1246 while (line[cursor] == ' ' || line[cursor] == '\t')
1247 cursor++;
1248 if (line[cursor] == '\n' || line[cursor] == '\0')
1249 continue;
1250 /* get the command to execute */
1251 start = cursor;
1252 again:
1253 while ((line[cursor] != '%') &&
1254 (line[cursor] != '\n') &&
1255 (line[cursor] != '\0') &&
1256 (line[cursor] != '\\'))
1257 cursor++;
1258 if (line[cursor] == '\\') {
1259 cursor += 2;
1260 goto again;
1261 }
1262 e->cmd = xmalloc(cursor-start + 1);
1263 (void) strncpy(e->cmd, line + start, cursor-start);
1264 e->cmd[cursor-start] = '\0';
1265 /* see if there is any standard input */
1266 if (line[cursor] == '%') {
1267 e->of.ct.input = xmalloc(strlen(line)-cursor + 1);
1268 (void) strcpy(e->of.ct.input, line + cursor + 1);
1269 for (i = 0; i < strlen(e->of.ct.input); i++) {
1270 if (e->of.ct.input[i] == '%')
1271 e->of.ct.input[i] = '\n';
1272 }
1273 } else {
1274 e->of.ct.input = NULL;
1275 }
1276 /* set the timezone of this entry */
1277 e->of.ct.tz = dup_shared(tz);
1278 /* set the shell of this entry */
1279 e->of.ct.shell = dup_shared(shell);
1280 /* set the home of this entry */
1281 e->of.ct.home = dup_shared(home);
1282 /* set the maximum random delay */
1283 e->of.ct.max_random_delay = max_random_delay;
1284 /* have the event point to it's owner */
1285 e->u = u;
1286 /* insert this event at the front of this user's event list */
1287 e->link = u->ctevents;
1288 u->ctevents = e;
1289 /* set the time for the first occurance of this event */
1290 e->time = next_time(e, reftime);
1291 /* finally, add this event to the main event list */
1292 switch (el_add(e, e->time, u->ctid)) {
1293 case -1:
1294 ignore_msg("readcron", "cron", e);
1295 break;
1296 case -2: /* event time lower than init time */
1297 reset_needed = 1;
1298 break;
1299 }
1300 cte_valid();
1301 #ifdef DEBUG
1302 cftime(timebuf, "%+", &e->time);
1303 (void) fprintf(stderr, "inserting cron event %s at %ld (%s)\n",
1304 e->cmd, e->time, timebuf);
1305 #endif
1306 }
1307 cte_sendmail(u->name); /* mail errors if any to user */
1308 (void) fclose(cf);
1309 rel_shared(tz);
1310 rel_shared(shell);
1311 rel_shared(home);
1312 }
1313
1314 /*
1315 * Below are the functions for handling of errors in crontabs. Concept is to
1316 * collect faulty lines and send one email at the end of the crontab
1317 * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
1318 * of crontab is aborted. Otherwise reading of crontab is continued to the end
1319 * of the file but no further error logging appears.
1320 */
1321 static void
cte_init()1322 cte_init()
1323 {
1324 if (cte_text == NULL)
1325 cte_text = xmalloc(MAILBUFLEN);
1326 (void) strlcpy(cte_text, cte_intro, MAILBUFLEN);
1327 cte_lp = cte_text + sizeof (cte_intro) - 1;
1328 cte_free = MAILBINITFREE;
1329 cte_nvalid = 0;
1330 }
1331
1332 static void
cte_add(int lineno,char * ctline)1333 cte_add(int lineno, char *ctline)
1334 {
1335 int len;
1336 char *p;
1337
1338 if (cte_free >= LINELIMIT) {
1339 (void) sprintf(cte_lp, "%4d: ", lineno);
1340 (void) strlcat(cte_lp, ctline, LINELIMIT - 1);
1341 len = strlen(cte_lp);
1342 if (cte_lp[len - 1] != '\n') {
1343 cte_lp[len++] = '\n';
1344 cte_lp[len] = '\0';
1345 }
1346 for (p = cte_lp; *p; p++) {
1347 if (isprint(*p) || *p == '\n' || *p == '\t')
1348 continue;
1349 *p = '.';
1350 }
1351 cte_lp += len;
1352 cte_free -= len;
1353 if (cte_free < LINELIMIT) {
1354 size_t buflen = MAILBUFLEN - (cte_lp - cte_text);
1355 (void) strlcpy(cte_lp, cte_trail1, buflen);
1356 if (cte_nvalid == 0)
1357 (void) strlcat(cte_lp, cte_trail2, buflen);
1358 }
1359 }
1360 }
1361
1362 static void
cte_valid()1363 cte_valid()
1364 {
1365 cte_nvalid++;
1366 }
1367
1368 static int
cte_istoomany()1369 cte_istoomany()
1370 {
1371 /*
1372 * Return TRUE only if all lines are faulty. So evaluation of
1373 * a crontab is not aborted if at least one valid line was found.
1374 */
1375 return (cte_nvalid == 0 && cte_free < LINELIMIT);
1376 }
1377
1378 static void
cte_sendmail(char * username)1379 cte_sendmail(char *username)
1380 {
1381 if (cte_free < MAILBINITFREE)
1382 mail(username, cte_text, ERR_CRONTABENT);
1383 }
1384
1385 /*
1386 * Send mail with error message to a user
1387 */
1388 static void
mail(char * usrname,char * mesg,int format)1389 mail(char *usrname, char *mesg, int format)
1390 {
1391 /* mail mails a user a message. */
1392 FILE *pipe;
1393 char *temp;
1394 struct passwd *ruser_ids;
1395 pid_t fork_val;
1396 int saveerrno = errno;
1397 struct utsname name;
1398
1399 #ifdef TESTING
1400 return;
1401 #endif
1402 (void) uname(&name);
1403 if ((fork_val = fork()) == (pid_t)-1) {
1404 msg("cron cannot fork\n");
1405 return;
1406 }
1407 if (fork_val == 0) {
1408 child_sigreset();
1409 contract_clear_template();
1410 if ((ruser_ids = getpwnam(usrname)) == NULL)
1411 exit(0);
1412 (void) setuid(ruser_ids->pw_uid);
1413 temp = xmalloc(strlen(MAIL) + strlen(usrname) + 2);
1414 (void) sprintf(temp, "%s %s", MAIL, usrname);
1415 pipe = popen(temp, "w");
1416 if (pipe != NULL) {
1417 (void) fprintf(pipe, "To: %s\n", usrname);
1418 switch (format) {
1419 case ERR_CRONTABENT:
1420 (void) fprintf(pipe, CRONTABERR);
1421 (void) fprintf(pipe, "Your \"crontab\" on %s\n",
1422 name.nodename);
1423 (void) fprintf(pipe, mesg);
1424 (void) fprintf(pipe,
1425 "\nEntries or crontab have been ignored\n");
1426 break;
1427 case ERR_UNIXERR:
1428 (void) fprintf(pipe, "Subject: %s\n\n", mesg);
1429 (void) fprintf(pipe,
1430 "The error on %s was \"%s\"\n",
1431 name.nodename, errmsg(saveerrno));
1432 break;
1433
1434 case ERR_CANTEXECCRON:
1435 (void) fprintf(pipe,
1436 "Subject: Couldn't run your \"cron\" job\n\n");
1437 (void) fprintf(pipe,
1438 "Your \"cron\" job on %s ", name.nodename);
1439 (void) fprintf(pipe, "couldn't be run\n");
1440 (void) fprintf(pipe, "%s\n", mesg);
1441 (void) fprintf(pipe,
1442 "The error was \"%s\"\n", errmsg(saveerrno));
1443 break;
1444
1445 case ERR_CANTEXECAT:
1446 (void) fprintf(pipe,
1447 "Subject: Couldn't run your \"at\" job\n\n");
1448 (void) fprintf(pipe, "Your \"at\" job on %s ",
1449 name.nodename);
1450 (void) fprintf(pipe, "couldn't be run\n");
1451 (void) fprintf(pipe, "%s\n", mesg);
1452 (void) fprintf(pipe,
1453 "The error was \"%s\"\n", errmsg(saveerrno));
1454 break;
1455
1456 default:
1457 break;
1458 }
1459 (void) pclose(pipe);
1460 }
1461 free(temp);
1462 exit(0);
1463 }
1464
1465 contract_abandon_latest(fork_val);
1466
1467 if (cron_pid == getpid()) {
1468 miscpid_insert(fork_val);
1469 }
1470 }
1471
1472 #define tm_cmp(t1, t2) (\
1473 (t1)->tm_year == (t2)->tm_year && \
1474 (t1)->tm_mon == (t2)->tm_mon && \
1475 (t1)->tm_mday == (t2)->tm_mday && \
1476 (t1)->tm_hour == (t2)->tm_hour && \
1477 (t1)->tm_min == (t2)->tm_min)
1478
1479 #define tm_setup(tp, yr, mon, dy, hr, min, dst) \
1480 (tp)->tm_year = yr; \
1481 (tp)->tm_mon = mon; \
1482 (tp)->tm_mday = dy; \
1483 (tp)->tm_hour = hr; \
1484 (tp)->tm_min = min; \
1485 (tp)->tm_isdst = dst; \
1486 (tp)->tm_sec = 0; \
1487 (tp)->tm_wday = 0; \
1488 (tp)->tm_yday = 0;
1489
1490 /*
1491 * modification for bugid 1104537. the second argument to next_time is
1492 * now the value of time(2) to be used. if this is 0, then use the
1493 * current time. otherwise, the second argument is the time from which to
1494 * calculate things. this is useful to correct situations where you've
1495 * gone backwards in time (I.e. the system's internal clock is correcting
1496 * itself backwards).
1497 */
1498
1499
1500
1501 static time_t
tz_next_time(struct event * e,time_t tflag)1502 tz_next_time(struct event *e, time_t tflag)
1503 {
1504 /*
1505 * returns the integer time for the next occurance of event e.
1506 * the following fields have ranges as indicated:
1507 * PRGM | min hour day of month mon day of week
1508 * ------|-------------------------------------------------------
1509 * cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday)
1510 * time | 0-59 0-23 1-31 0-11 0-6 (0=sunday)
1511 * NOTE: this routine is hard to understand.
1512 */
1513
1514 struct tm *tm, ref_tm, tmp, tmp1, tmp2;
1515 int tm_mon, tm_mday, tm_wday, wday, m, min, h, hr, carry, day, days;
1516 int d1, day1, carry1, d2, day2, carry2, daysahead, mon, yr, db, wd;
1517 int today;
1518 time_t t, ref_t, t1, t2, zone_start;
1519 int fallback;
1520 extern int days_btwn(int, int, int, int, int, int);
1521
1522 if (tflag == 0) {
1523 t = time(NULL); /* original way of doing things */
1524 } else {
1525 t = tflag;
1526 }
1527
1528 tm = &ref_tm; /* use a local variable and call localtime_r() */
1529 ref_t = t; /* keep a copy of the reference time */
1530
1531 recalc:
1532 fallback = 0;
1533
1534 (void) localtime_r(&t, tm);
1535
1536 if (daylight) {
1537 tmp = *tm;
1538 tmp.tm_isdst = (tm->tm_isdst > 0 ? 0 : 1);
1539 t1 = xmktime(&tmp);
1540 /*
1541 * see if we will have timezone switch over, and clock will
1542 * fall back. zone_start will hold the time when it happens
1543 * (ie time of PST -> PDT switch over).
1544 */
1545 if (tm->tm_isdst != tmp.tm_isdst &&
1546 (t1 - t) == (timezone - altzone) &&
1547 tm_cmp(tm, &tmp)) {
1548 zone_start = get_switching_time(tmp.tm_isdst, t);
1549 fallback = 1;
1550 }
1551 }
1552
1553 tm_mon = next_ge(tm->tm_mon + 1, e->of.ct.month) - 1; /* 0-11 */
1554 tm_mday = next_ge(tm->tm_mday, e->of.ct.daymon); /* 1-31 */
1555 tm_wday = next_ge(tm->tm_wday, e->of.ct.dayweek); /* 0-6 */
1556 today = TRUE;
1557 if ((strcmp(e->of.ct.daymon, "*") == 0 && tm->tm_wday != tm_wday) ||
1558 (strcmp(e->of.ct.dayweek, "*") == 0 && tm->tm_mday != tm_mday) ||
1559 (tm->tm_mday != tm_mday && tm->tm_wday != tm_wday) ||
1560 (tm->tm_mon != tm_mon)) {
1561 today = FALSE;
1562 }
1563 m = tm->tm_min + (t == ref_t ? 1 : 0);
1564 if ((tm->tm_hour + 1) <= next_ge(tm->tm_hour, e->of.ct.hour)) {
1565 m = 0;
1566 }
1567 min = next_ge(m%60, e->of.ct.minute);
1568 carry = (min < m) ? 1 : 0;
1569 h = tm->tm_hour + carry;
1570 hr = next_ge(h%24, e->of.ct.hour);
1571 carry = (hr < h) ? 1 : 0;
1572
1573 if (carry == 0 && today) {
1574 /* this event must occur today */
1575 tm_setup(&tmp, tm->tm_year, tm->tm_mon, tm->tm_mday,
1576 hr, min, tm->tm_isdst);
1577 tmp1 = tmp;
1578 if ((t1 = xmktime(&tmp1)) == (time_t)-1) {
1579 return (0);
1580 }
1581 if (daylight && tmp.tm_isdst != tmp1.tm_isdst) {
1582 /* In case we are falling back */
1583 if (fallback) {
1584 /* we may need to run the job once more. */
1585 t = zone_start;
1586 goto recalc;
1587 }
1588
1589 /*
1590 * In case we are not in falling back period,
1591 * calculate the time assuming the DST. If the
1592 * date/time is not altered by mktime, it is the
1593 * time to execute the job.
1594 */
1595 tmp2 = tmp;
1596 tmp2.tm_isdst = tmp1.tm_isdst;
1597 if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
1598 return (0);
1599 }
1600 if (tmp1.tm_isdst == tmp2.tm_isdst &&
1601 tm_cmp(&tmp, &tmp2)) {
1602 /*
1603 * We got a valid time.
1604 */
1605 return (t1);
1606 } else {
1607 /*
1608 * If the date does not match even if
1609 * we assume the alternate timezone, then
1610 * it must be the invalid time. eg
1611 * 2am while switching 1:59am to 3am.
1612 * t1 should point the time before the
1613 * switching over as we've calculate the
1614 * time with assuming alternate zone.
1615 */
1616 if (tmp1.tm_isdst != tmp2.tm_isdst) {
1617 t = get_switching_time(tmp1.tm_isdst,
1618 t1);
1619 } else {
1620 /* does this really happen? */
1621 t = get_switching_time(tmp1.tm_isdst,
1622 t1 - abs(timezone - altzone));
1623 }
1624 if (t == (time_t)-1) {
1625 return (0);
1626 }
1627 }
1628 goto recalc;
1629 }
1630 if (tm_cmp(&tmp, &tmp1)) {
1631 /* got valid time */
1632 return (t1);
1633 } else {
1634 /*
1635 * This should never happen, but just in
1636 * case, we fall back to the old code.
1637 */
1638 if (tm->tm_min > min) {
1639 t += (time_t)(hr-tm->tm_hour-1) * HOUR +
1640 (time_t)(60-tm->tm_min + min) * MINUTE;
1641 } else {
1642 t += (time_t)(hr-tm->tm_hour) * HOUR +
1643 (time_t)(min-tm->tm_min) * MINUTE;
1644 }
1645 t1 = t;
1646 t -= (time_t)tm->tm_sec;
1647 (void) localtime_r(&t, &tmp);
1648 if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
1649 t -= (timezone - altzone);
1650 return ((t <= ref_t) ? t1 : t);
1651 }
1652 }
1653
1654 /*
1655 * Job won't run today, however if we have a switch over within
1656 * one hour and we will have one hour time drifting back in this
1657 * period, we may need to run the job one more time if the job was
1658 * set to run on this hour of clock.
1659 */
1660 if (fallback) {
1661 t = zone_start;
1662 goto recalc;
1663 }
1664
1665 min = next_ge(0, e->of.ct.minute);
1666 hr = next_ge(0, e->of.ct.hour);
1667
1668 /*
1669 * calculate the date of the next occurance of this event, which
1670 * will be on a different day than the current
1671 */
1672
1673 /* check monthly day specification */
1674 d1 = tm->tm_mday + 1;
1675 day1 = next_ge((d1-1)%days_in_mon(tm->tm_mon, tm->tm_year) + 1,
1676 e->of.ct.daymon);
1677 carry1 = (day1 < d1) ? 1 : 0;
1678
1679 /* check weekly day specification */
1680 d2 = tm->tm_wday + 1;
1681 wday = next_ge(d2%7, e->of.ct.dayweek);
1682 if (wday < d2)
1683 daysahead = 7 - d2 + wday;
1684 else
1685 daysahead = wday - d2;
1686 day2 = (d1 + daysahead-1)%days_in_mon(tm->tm_mon, tm->tm_year) + 1;
1687 carry2 = (day2 < d1) ? 1 : 0;
1688
1689 /*
1690 * based on their respective specifications, day1, and day2 give
1691 * the day of the month for the next occurance of this event.
1692 */
1693 if ((strcmp(e->of.ct.daymon, "*") == 0) &&
1694 (strcmp(e->of.ct.dayweek, "*") != 0)) {
1695 day1 = day2;
1696 carry1 = carry2;
1697 }
1698 if ((strcmp(e->of.ct.daymon, "*") != 0) &&
1699 (strcmp(e->of.ct.dayweek, "*") == 0)) {
1700 day2 = day1;
1701 carry2 = carry1;
1702 }
1703
1704 yr = tm->tm_year;
1705 if ((carry1 && carry2) || (tm->tm_mon != tm_mon)) {
1706 /* event does not occur in this month */
1707 m = tm->tm_mon + 1;
1708 mon = next_ge(m%12 + 1, e->of.ct.month) - 1; /* 0..11 */
1709 carry = (mon < m) ? 1 : 0;
1710 yr += carry;
1711 /* recompute day1 and day2 */
1712 day1 = next_ge(1, e->of.ct.daymon);
1713 db = days_btwn(tm->tm_mon, tm->tm_mday, tm->tm_year, mon,
1714 1, yr) + 1;
1715 wd = (tm->tm_wday + db)%7;
1716 /* wd is the day of the week of the first of month mon */
1717 wday = next_ge(wd, e->of.ct.dayweek);
1718 if (wday < wd)
1719 day2 = 1 + 7 - wd + wday;
1720 else
1721 day2 = 1 + wday - wd;
1722 if ((strcmp(e->of.ct.daymon, "*") != 0) &&
1723 (strcmp(e->of.ct.dayweek, "*") == 0))
1724 day2 = day1;
1725 if ((strcmp(e->of.ct.daymon, "*") == 0) &&
1726 (strcmp(e->of.ct.dayweek, "*") != 0))
1727 day1 = day2;
1728 day = (day1 < day2) ? day1 : day2;
1729 } else { /* event occurs in this month */
1730 mon = tm->tm_mon;
1731 if (!carry1 && !carry2)
1732 day = (day1 < day2) ? day1 : day2;
1733 else if (!carry1)
1734 day = day1;
1735 else
1736 day = day2;
1737 }
1738
1739 /*
1740 * now that we have the min, hr, day, mon, yr of the next event,
1741 * figure out what time that turns out to be.
1742 */
1743 tm_setup(&tmp, yr, mon, day, hr, min, -1);
1744 tmp2 = tmp;
1745 if ((t1 = xmktime(&tmp2)) == (time_t)-1) {
1746 return (0);
1747 }
1748 if (tm_cmp(&tmp, &tmp2)) {
1749 /*
1750 * mktime returns clock for the current time zone. If the
1751 * target date was in fallback period, it needs to be adjusted
1752 * to the time comes first.
1753 * Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
1754 * mktime returns the time in PST, but 1:30am in PDT comes
1755 * first. So reverse the tm_isdst, and see if we have such
1756 * time/date.
1757 */
1758 if (daylight) {
1759 int dst = tmp2.tm_isdst;
1760
1761 tmp2 = tmp;
1762 tmp2.tm_isdst = (dst > 0 ? 0 : 1);
1763 if ((t2 = xmktime(&tmp2)) == (time_t)-1) {
1764 return (0);
1765 }
1766 if (tm_cmp(&tmp, &tmp2)) {
1767 /*
1768 * same time/date found in the opposite zone.
1769 * check the clock to see which comes early.
1770 */
1771 if (t2 > ref_t && t2 < t1) {
1772 t1 = t2;
1773 }
1774 }
1775 }
1776 return (t1);
1777 } else {
1778 /*
1779 * mktime has set different time/date for the given date.
1780 * This means that the next job is scheduled to be run on the
1781 * invalid time. There are three possible invalid date/time.
1782 * 1. Non existing day of the month. such as April 31th.
1783 * 2. Feb 29th in the non-leap year.
1784 * 3. Time gap during the DST switch over.
1785 */
1786 d1 = days_in_mon(mon, yr);
1787 if ((mon != 1 && day > d1) || (mon == 1 && day > 29)) {
1788 /*
1789 * see if we have got a specific date which
1790 * is invalid.
1791 */
1792 if (strcmp(e->of.ct.dayweek, "*") == 0 &&
1793 mon == (next_ge((mon + 1)%12 + 1,
1794 e->of.ct.month) - 1) &&
1795 day <= next_ge(1, e->of.ct.daymon)) {
1796 /* job never run */
1797 return (0);
1798 }
1799 /*
1800 * Since the day has gone invalid, we need to go to
1801 * next month, and recalcuate the first occurrence.
1802 * eg the cron tab such as:
1803 * 0 0 1,15,31 1,2,3,4,5 * /usr/bin....
1804 * 2/31 is invalid, so the next job is 3/1.
1805 */
1806 tmp2 = tmp;
1807 tmp2.tm_min = 0;
1808 tmp2.tm_hour = 0;
1809 tmp2.tm_mday = 1; /* 1st day of the month */
1810 if (mon == 11) {
1811 tmp2.tm_mon = 0;
1812 tmp2.tm_year = yr + 1;
1813 } else {
1814 tmp2.tm_mon = mon + 1;
1815 }
1816 if ((t = xmktime(&tmp2)) == (time_t)-1) {
1817 return (0);
1818 }
1819 } else if (mon == 1 && day > d1) {
1820 /*
1821 * ie 29th in the non-leap year. Forwarding the
1822 * clock to Feb 29th 00:00 (March 1st), and recalculate
1823 * the next time.
1824 */
1825 tmp2 = tmp;
1826 tmp2.tm_min = 0;
1827 tmp2.tm_hour = 0;
1828 if ((t = xmktime(&tmp2)) == (time_t)-1) {
1829 return (0);
1830 }
1831 } else if (daylight) {
1832 /*
1833 * Non existing time, eg 2am PST during summer time
1834 * switch.
1835 * We need to get the correct isdst which we are
1836 * swithing to, by adding time difference to make sure
1837 * that t2 is in the zone being switched.
1838 */
1839 t2 = t1;
1840 t2 += abs(timezone - altzone);
1841 (void) localtime_r(&t2, &tmp2);
1842 zone_start = get_switching_time(tmp2.tm_isdst,
1843 t1 - abs(timezone - altzone));
1844 if (zone_start == (time_t)-1) {
1845 return (0);
1846 }
1847 t = zone_start;
1848 } else {
1849 /*
1850 * This should never happen, but fall back to the
1851 * old code.
1852 */
1853 days = days_btwn(tm->tm_mon,
1854 tm->tm_mday, tm->tm_year, mon, day, yr);
1855 t += (time_t)(23-tm->tm_hour)*HOUR
1856 + (time_t)(60-tm->tm_min)*MINUTE
1857 + (time_t)hr*HOUR + (time_t)min*MINUTE
1858 + (time_t)days*DAY;
1859 t1 = t;
1860 t -= (time_t)tm->tm_sec;
1861 (void) localtime_r(&t, &tmp);
1862 if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0))
1863 t -= (timezone - altzone);
1864 return (t <= ref_t ? t1 : t);
1865 }
1866 goto recalc;
1867 }
1868 /*NOTREACHED*/
1869 }
1870
1871 static time_t
next_time(struct event * e,time_t tflag)1872 next_time(struct event *e, time_t tflag)
1873 {
1874 time_t ret;
1875
1876 if (e->of.ct.tz != NULL) {
1877 (void) putenv((char *)get_obj(e->of.ct.tz));
1878 tzset();
1879 ret = tz_next_time(e, tflag);
1880 (void) putenv(tzone);
1881 tzset();
1882 } else {
1883 ret = tz_next_time(e, tflag);
1884 }
1885
1886 if (e->of.ct.max_random_delay > 0) {
1887 ret += arc4random_uniform(e->of.ct.max_random_delay * 60 - 1);
1888 }
1889 return (ret);
1890 }
1891
1892 /*
1893 * This returns TOD in time_t that zone switch will happen, and this
1894 * will be called when clock fallback is about to happen.
1895 * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
1896 * will fall back to 1:00 PDT. So this function will be called only
1897 * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
1898 * First goes through the common time differences to see if zone
1899 * switch happens at those minutes later. If not, check every minutes
1900 * until 6 hours ahead see if it happens(We might have 45minutes
1901 * fallback).
1902 */
1903 static time_t
get_switching_time(int to_dst,time_t t_ref)1904 get_switching_time(int to_dst, time_t t_ref)
1905 {
1906 time_t t, t1;
1907 struct tm tmp, tmp1;
1908 int hints[] = { 60, 120, 30, 90, 0}; /* minutes */
1909 int i;
1910
1911 (void) localtime_r(&t_ref, &tmp);
1912 tmp1 = tmp;
1913 tmp1.tm_sec = 0;
1914 tmp1.tm_min = 0;
1915 if ((t = xmktime(&tmp1)) == (time_t)-1)
1916 return ((time_t)-1);
1917
1918 /* fast path */
1919 for (i = 0; hints[i] != 0; i++) {
1920 t1 = t + hints[i] * 60;
1921 (void) localtime_r(&t1, &tmp1);
1922 if (tmp1.tm_isdst == to_dst) {
1923 t1--;
1924 (void) localtime_r(&t1, &tmp1);
1925 if (tmp1.tm_isdst != to_dst) {
1926 return (t1 + 1);
1927 }
1928 }
1929 }
1930
1931 /* ugly, but don't know other than this. */
1932 tmp1 = tmp;
1933 tmp1.tm_sec = 0;
1934 if ((t = xmktime(&tmp1)) == (time_t)-1)
1935 return ((time_t)-1);
1936 while (t < (t_ref + 6*60*60)) { /* 6 hours should be enough */
1937 t += 60; /* at least one minute, I assume */
1938 (void) localtime_r(&t, &tmp);
1939 if (tmp.tm_isdst == to_dst)
1940 return (t);
1941 }
1942 return ((time_t)-1);
1943 }
1944
1945 static time_t
xmktime(struct tm * tmp)1946 xmktime(struct tm *tmp)
1947 {
1948 time_t ret;
1949
1950 if ((ret = mktime(tmp)) == (time_t)-1) {
1951 if (errno == EOVERFLOW) {
1952 return ((time_t)-1);
1953 }
1954 crabort("internal error: mktime failed",
1955 REMOVE_FIFO|CONSOLE_MSG);
1956 }
1957 return (ret);
1958 }
1959
1960 #define DUMMY 100
1961
1962 static int
next_ge(int current,char * list)1963 next_ge(int current, char *list)
1964 {
1965 /*
1966 * list is a character field as in a crontab file;
1967 * for example: "40, 20, 50-10"
1968 * next_ge returns the next number in the list that is
1969 * greater than or equal to current. if no numbers of list
1970 * are >= current, the smallest element of list is returned.
1971 * NOTE: current must be in the appropriate range.
1972 */
1973
1974 char *ptr;
1975 int n, n2, min, min_gt;
1976
1977 if (strcmp(list, "*") == 0)
1978 return (current);
1979 ptr = list;
1980 min = DUMMY;
1981 min_gt = DUMMY;
1982 for (;;) {
1983 if ((n = (int)num(&ptr)) == current)
1984 return (current);
1985 if (n < min)
1986 min = n;
1987 if ((n > current) && (n < min_gt))
1988 min_gt = n;
1989 if (*ptr == '-') {
1990 ptr++;
1991 if ((n2 = (int)num(&ptr)) > n) {
1992 if ((current > n) && (current <= n2))
1993 return (current);
1994 } else { /* range that wraps around */
1995 if (current > n)
1996 return (current);
1997 if (current <= n2)
1998 return (current);
1999 }
2000 }
2001 if (*ptr == '\0')
2002 break;
2003 ptr += 1;
2004 }
2005 if (min_gt != DUMMY)
2006 return (min_gt);
2007 else
2008 return (min);
2009 }
2010
2011 static void
free_if_unused(struct usr * u)2012 free_if_unused(struct usr *u)
2013 {
2014 struct usr *cur, *prev;
2015 /*
2016 * To make sure a usr structure is idle we must check that
2017 * there are no at jobs queued for the user; the user does
2018 * not have a crontab, and also that there are no running at
2019 * or cron jobs (since the runinfo structure also has a
2020 * pointer to the usr structure).
2021 */
2022 if (!u->ctexists && u->atevents == NULL &&
2023 u->cruncnt == 0 && u->aruncnt == 0) {
2024 #ifdef DEBUG
2025 (void) fprintf(stderr, "%s removed from usr list\n", u->name);
2026 #endif
2027 for (cur = uhead, prev = NULL;
2028 cur != u;
2029 prev = cur, cur = cur->nextusr) {
2030 if (cur == NULL) {
2031 return;
2032 }
2033 }
2034
2035 if (prev == NULL)
2036 uhead = u->nextusr;
2037 else
2038 prev->nextusr = u->nextusr;
2039 free(u->name);
2040 free(u->home);
2041 free(u);
2042 }
2043 }
2044
2045 static void
del_atjob(char * name,char * usrname)2046 del_atjob(char *name, char *usrname)
2047 {
2048
2049 struct event *e, *eprev;
2050 struct usr *u;
2051
2052 if ((u = find_usr(usrname)) == NULL)
2053 return;
2054 e = u->atevents;
2055 eprev = NULL;
2056 while (e != NULL) {
2057 if (strcmp(name, e->cmd) == 0) {
2058 if (next_event == e)
2059 next_event = NULL;
2060 if (eprev == NULL)
2061 u->atevents = e->link;
2062 else
2063 eprev->link = e->link;
2064 el_remove(e->of.at.eventid, 1);
2065 free(e->cmd);
2066 free(e);
2067 break;
2068 } else {
2069 eprev = e;
2070 e = e->link;
2071 }
2072 }
2073
2074 free_if_unused(u);
2075 }
2076
2077 static void
del_ctab(char * name)2078 del_ctab(char *name)
2079 {
2080
2081 struct usr *u;
2082
2083 if ((u = find_usr(name)) == NULL)
2084 return;
2085 rm_ctevents(u);
2086 el_remove(u->ctid, 0);
2087 u->ctid = 0;
2088 u->ctexists = 0;
2089
2090 free_if_unused(u);
2091 }
2092
2093 static void
rm_ctevents(struct usr * u)2094 rm_ctevents(struct usr *u)
2095 {
2096 struct event *e2, *e3;
2097
2098 /*
2099 * see if the next event (to be run by cron) is a cronevent
2100 * owned by this user.
2101 */
2102
2103 if ((next_event != NULL) &&
2104 (next_event->etype == CRONEVENT) &&
2105 (next_event->u == u)) {
2106 next_event = NULL;
2107 }
2108 e2 = u->ctevents;
2109 while (e2 != NULL) {
2110 free(e2->cmd);
2111 rel_shared(e2->of.ct.tz);
2112 rel_shared(e2->of.ct.shell);
2113 rel_shared(e2->of.ct.home);
2114 free(e2->of.ct.minute);
2115 free(e2->of.ct.hour);
2116 free(e2->of.ct.daymon);
2117 free(e2->of.ct.month);
2118 free(e2->of.ct.dayweek);
2119 if (e2->of.ct.input != NULL)
2120 free(e2->of.ct.input);
2121 e3 = e2->link;
2122 free(e2);
2123 e2 = e3;
2124 }
2125 u->ctevents = NULL;
2126 }
2127
2128
2129 static struct usr *
find_usr(char * uname)2130 find_usr(char *uname)
2131 {
2132 struct usr *u;
2133
2134 u = uhead;
2135 while (u != NULL) {
2136 if (strcmp(u->name, uname) == 0)
2137 return (u);
2138 u = u->nextusr;
2139 }
2140 return (NULL);
2141 }
2142
2143 /*
2144 * Execute cron command or at/batch job.
2145 * If ever a premature return is added to this function pay attention to
2146 * free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
2147 */
2148 static int
ex(struct event * e)2149 ex(struct event *e)
2150 {
2151 int r;
2152 int fd;
2153 pid_t rfork;
2154 FILE *atcmdfp;
2155 char mailvar[4];
2156 char *at_cmdfile = NULL;
2157 struct stat buf;
2158 struct queue *qp;
2159 struct runinfo *rp;
2160 struct project proj, *pproj = NULL;
2161 union {
2162 struct {
2163 char buf[PROJECT_BUFSZ];
2164 char buf2[PROJECT_BUFSZ];
2165 } p;
2166 char error[CANT_STR_LEN + PATH_MAX];
2167 } bufs;
2168 char *tmpfile;
2169 FILE *fptr;
2170 time_t dhltime;
2171 projid_t projid;
2172 int projflag = 0;
2173 char *home;
2174 char *sh;
2175
2176 qp = &qt[e->etype]; /* set pointer to queue defs */
2177 if (qp->nrun >= qp->njob) {
2178 msg("%c queue max run limit reached", e->etype + 'a');
2179 resched(qp->nwait);
2180 return (0);
2181 }
2182
2183 rp = rinfo_get(0); /* allocating a new runinfo struct */
2184
2185 /*
2186 * the tempnam() function uses malloc(3C) to allocate space for the
2187 * constructed file name, and returns a pointer to this area, which
2188 * is assigned to rp->outfile. Here rp->outfile is not overwritten.
2189 */
2190
2191 rp->outfile = tempnam(TMPDIR, PFX);
2192 rp->jobtype = e->etype;
2193 if (e->etype == CRONEVENT) {
2194 rp->jobname = xmalloc(strlen(e->cmd) + 1);
2195 (void) strcpy(rp->jobname, e->cmd);
2196 /* "cron" jobs only produce mail if there's output */
2197 rp->mailwhendone = 0;
2198 } else {
2199 at_cmdfile = xmalloc(strlen(ATDIR) + strlen(e->cmd) + 2);
2200 (void) sprintf(at_cmdfile, "%s/%s", ATDIR, e->cmd);
2201 if ((atcmdfp = fopen(at_cmdfile, "r")) == NULL) {
2202 if (errno == ENAMETOOLONG) {
2203 if (chdir(ATDIR) == 0)
2204 cron_unlink(e->cmd);
2205 } else {
2206 cron_unlink(at_cmdfile);
2207 }
2208 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECAT);
2209 free(at_cmdfile);
2210 rinfo_free(rp);
2211 return (0);
2212 }
2213 rp->jobname = xmalloc(strlen(at_cmdfile) + 1);
2214 (void) strcpy(rp->jobname, at_cmdfile);
2215
2216 /*
2217 * Skip over the first two lines.
2218 */
2219 (void) fscanf(atcmdfp, "%*[^\n]\n");
2220 (void) fscanf(atcmdfp, "%*[^\n]\n");
2221 if (fscanf(atcmdfp, ": notify by mail: %3s%*[^\n]\n",
2222 mailvar) == 1) {
2223 /*
2224 * Check to see if we should always send mail
2225 * to the owner.
2226 */
2227 rp->mailwhendone = (strcmp(mailvar, "yes") == 0);
2228 } else {
2229 rp->mailwhendone = 0;
2230 }
2231
2232 if (fscanf(atcmdfp, "\n: project: %d\n", &projid) == 1) {
2233 projflag = 1;
2234 }
2235 (void) fclose(atcmdfp);
2236 }
2237
2238 /*
2239 * we make sure that the system time
2240 * hasn't drifted backwards. if it has, el_add() is now
2241 * called, to make sure that the event queue is back in order,
2242 * and we set the delayed flag. cron will pick up the request
2243 * later on at the proper time.
2244 */
2245 dhltime = time(NULL);
2246 if ((dhltime - e->time) < 0) {
2247 msg("clock time drifted backwards!\n");
2248 if (next_event->etype == CRONEVENT) {
2249 msg("correcting cron event\n");
2250 next_event->time = next_time(next_event, dhltime);
2251 switch (el_add(next_event, next_event->time,
2252 (next_event->u)->ctid)) {
2253 case -1:
2254 ignore_msg("ex", "cron", next_event);
2255 break;
2256 case -2: /* event time lower than init time */
2257 reset_needed = 1;
2258 break;
2259 }
2260 } else { /* etype == ATEVENT */
2261 msg("correcting batch event\n");
2262 if (el_add(next_event, next_event->time,
2263 next_event->of.at.eventid) < 0) {
2264 ignore_msg("ex", "at", next_event);
2265 }
2266 }
2267 delayed++;
2268 t_old = time(NULL);
2269 free(at_cmdfile);
2270 rinfo_free(rp);
2271 return (0);
2272 }
2273
2274 if ((rfork = fork()) == (pid_t)-1) {
2275 reap_child();
2276 if ((rfork = fork()) == (pid_t)-1) {
2277 msg("cannot fork");
2278 free(at_cmdfile);
2279 rinfo_free(rp);
2280 resched(60);
2281 (void) sleep(30);
2282 return (0);
2283 }
2284 }
2285 if (rfork) { /* parent process */
2286 contract_abandon_latest(rfork);
2287
2288 ++qp->nrun;
2289 rp->pid = rfork;
2290 rp->que = e->etype;
2291 if (e->etype != CRONEVENT)
2292 (e->u)->aruncnt++;
2293 else
2294 (e->u)->cruncnt++;
2295 rp->rusr = (e->u);
2296 logit(BCHAR, rp, 0);
2297 free(at_cmdfile);
2298
2299 return (0);
2300 }
2301
2302 child_sigreset();
2303 contract_clear_template();
2304
2305 if (e->etype != CRONEVENT) {
2306 /* open jobfile as stdin to shell */
2307 if (stat(at_cmdfile, &buf)) {
2308 if (errno == ENAMETOOLONG) {
2309 if (chdir(ATDIR) == 0)
2310 cron_unlink(e->cmd);
2311 } else
2312 cron_unlink(at_cmdfile);
2313 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2314 exit(1);
2315 }
2316 if (!(buf.st_mode&ISUID)) {
2317 /*
2318 * if setuid bit off, original owner has
2319 * given this file to someone else
2320 */
2321 cron_unlink(at_cmdfile);
2322 exit(1);
2323 }
2324 if ((fd = open(at_cmdfile, O_RDONLY)) == -1) {
2325 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON);
2326 cron_unlink(at_cmdfile);
2327 exit(1);
2328 }
2329 if (fd != 0) {
2330 (void) dup2(fd, 0);
2331 (void) close(fd);
2332 }
2333 /*
2334 * retrieve the project id of the at job and convert it
2335 * to a project name. fail if it's not a valid project
2336 * or if the user isn't a member of the project.
2337 */
2338 if (projflag == 1) {
2339 if ((pproj = getprojbyid(projid, &proj,
2340 (void *)&bufs.p.buf,
2341 sizeof (bufs.p.buf))) == NULL ||
2342 !inproj(e->u->name, pproj->pj_name,
2343 bufs.p.buf2, sizeof (bufs.p.buf2))) {
2344 cron_unlink(at_cmdfile);
2345 mail((e->u)->name, BADPROJID, ERR_CANTEXECAT);
2346 exit(1);
2347 }
2348 }
2349 }
2350
2351 /*
2352 * Put process in a new session, and create a new task.
2353 */
2354 if (setsid() < 0) {
2355 msg("setsid failed with errno = %d. job failed (%s)"
2356 " for user %s", errno, e->cmd, e->u->name);
2357 if (e->etype != CRONEVENT)
2358 cron_unlink(at_cmdfile);
2359 exit(1);
2360 }
2361
2362 /*
2363 * set correct user identification and check their account
2364 */
2365 r = set_user_cred(e->u, pproj);
2366 if (r == VUC_EXPIRED) {
2367 msg("user (%s) account is expired", e->u->name);
2368 audit_cron_user_acct_expired(e->u->name);
2369 clean_out_user(e->u);
2370 exit(1);
2371 }
2372 if (r == VUC_NEW_AUTH) {
2373 msg("user (%s) password has expired", e->u->name);
2374 audit_cron_user_acct_expired(e->u->name);
2375 clean_out_user(e->u);
2376 exit(1);
2377 }
2378 if (r != VUC_OK) {
2379 msg("bad user (%s)", e->u->name);
2380 audit_cron_bad_user(e->u->name);
2381 clean_out_user(e->u);
2382 exit(1);
2383 }
2384 /*
2385 * check user and initialize the supplementary group access list.
2386 * bugid 1230784: deleted from parent to avoid cron hang. Now
2387 * only child handles the call.
2388 */
2389
2390 if (verify_user_cred(e->u) != VUC_OK ||
2391 setgid(e->u->gid) == -1 ||
2392 initgroups(e->u->name, e->u->gid) == -1) {
2393 msg("bad user (%s) or setgid failed (%s)",
2394 e->u->name, e->u->name);
2395 audit_cron_bad_user(e->u->name);
2396 clean_out_user(e->u);
2397 exit(1);
2398 }
2399
2400 if ((e->u)->uid == 0) { /* set default path */
2401 /* path settable in defaults file */
2402 envinit[2] = supath;
2403 } else {
2404 envinit[2] = path;
2405 }
2406
2407 if (e->etype != CRONEVENT) {
2408 r = audit_cron_session(e->u->name, NULL,
2409 e->u->uid, e->u->gid, at_cmdfile);
2410 cron_unlink(at_cmdfile);
2411 } else {
2412 r = audit_cron_session(e->u->name, CRONDIR,
2413 e->u->uid, e->u->gid, NULL);
2414 }
2415 if (r != 0) {
2416 msg("cron audit problem. job failed (%s) for user %s",
2417 e->cmd, e->u->name);
2418 exit(1);
2419 }
2420
2421 audit_cron_new_job(e->cmd, e->etype, (void *)e);
2422
2423 if (setuid(e->u->uid) == -1) {
2424 msg("setuid failed (%s)", e->u->name);
2425 clean_out_user(e->u);
2426 exit(1);
2427 }
2428
2429 if (e->etype == CRONEVENT) {
2430 /* check for standard input to command */
2431 if (e->of.ct.input != NULL) {
2432 if ((tmpfile = strdup(TMPINFILE)) == NULL) {
2433 mail((e->u)->name, MALLOCERR,
2434 ERR_CANTEXECCRON);
2435 exit(1);
2436 }
2437 if ((fd = mkstemp(tmpfile)) == -1 ||
2438 (fptr = fdopen(fd, "w")) == NULL) {
2439 mail((e->u)->name, NOSTDIN,
2440 ERR_CANTEXECCRON);
2441 cron_unlink(tmpfile);
2442 free(tmpfile);
2443 exit(1);
2444 }
2445 if ((fwrite(e->of.ct.input, sizeof (char),
2446 strlen(e->of.ct.input), fptr)) !=
2447 strlen(e->of.ct.input)) {
2448 mail((e->u)->name, NOSTDIN, ERR_CANTEXECCRON);
2449 cron_unlink(tmpfile);
2450 free(tmpfile);
2451 (void) close(fd);
2452 (void) fclose(fptr);
2453 exit(1);
2454 }
2455 if (fseek(fptr, (off_t)0, SEEK_SET) != -1) {
2456 if (fd != 0) {
2457 (void) dup2(fd, 0);
2458 (void) close(fd);
2459 }
2460 }
2461 cron_unlink(tmpfile);
2462 free(tmpfile);
2463 (void) fclose(fptr);
2464 } else if ((fd = open("/dev/null", O_RDONLY)) > 0) {
2465 (void) dup2(fd, 0);
2466 (void) close(fd);
2467 }
2468 }
2469
2470 /* redirect stdout and stderr for the shell */
2471 if ((fd = open(rp->outfile, O_WRONLY|O_CREAT|O_EXCL, OUTMODE)) == 1)
2472 fd = open("/dev/null", O_WRONLY);
2473
2474 if (fd >= 0 && fd != 1)
2475 (void) dup2(fd, 1);
2476
2477 if (fd >= 0 && fd != 2) {
2478 (void) dup2(fd, 2);
2479 if (fd != 1)
2480 (void) close(fd);
2481 }
2482
2483 if (e->etype == CRONEVENT && e->of.ct.home != NULL) {
2484 home = (char *)get_obj(e->of.ct.home);
2485 } else {
2486 home = (e->u)->home;
2487 }
2488 (void) strlcat(homedir, home, sizeof (homedir));
2489 (void) strlcat(logname, (e->u)->name, sizeof (logname));
2490 environ = envinit;
2491 if (chdir(home) == -1) {
2492 snprintf(bufs.error, sizeof (bufs.error), CANTCDHOME, home);
2493 mail((e->u)->name, bufs.error,
2494 e->etype == CRONEVENT ? ERR_CANTEXECCRON :
2495 ERR_CANTEXECAT);
2496 exit(1);
2497 }
2498 #ifdef TESTING
2499 exit(1);
2500 #endif
2501 /*
2502 * make sure that all file descriptors EXCEPT 0, 1 and 2
2503 * will be closed.
2504 */
2505 closefrom(3);
2506
2507 if ((e->u)->uid != 0)
2508 (void) nice(qp->nice);
2509 if (e->etype == CRONEVENT) {
2510 if (e->of.ct.tz) {
2511 (void) putenv((char *)get_obj(e->of.ct.tz));
2512 }
2513 if (e->of.ct.shell) {
2514 char *name;
2515
2516 sh = (char *)get_obj(e->of.ct.shell);
2517 name = strrchr(sh, '/');
2518 if (name == NULL)
2519 name = sh;
2520 else
2521 name++;
2522
2523 (void) putenv(sh);
2524 sh += strlen(ENV_SHELL);
2525 (void) execl(sh, name, "-c", e->cmd, 0);
2526 } else {
2527 (void) execl(SHELL, "sh", "-c", e->cmd, 0);
2528 sh = SHELL;
2529 }
2530 } else { /* type == ATEVENT */
2531 (void) execl(SHELL, "sh", 0);
2532 sh = SHELL;
2533 }
2534 snprintf(bufs.error, sizeof (bufs.error), CANTEXECSH, sh);
2535 mail((e->u)->name, bufs.error,
2536 e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT);
2537 exit(1);
2538 /*NOTREACHED*/
2539 }
2540
2541 /*
2542 * Main idle loop.
2543 * When timed out to run the job, return 0.
2544 * If for some reasons we need to reschedule jobs, return 1.
2545 */
2546 static int
idle(long t)2547 idle(long t)
2548 {
2549 time_t now;
2550
2551 refresh = 0;
2552
2553 while (t > 0L) {
2554 if (msg_wait(t) != 0) {
2555 /* we need to run next job immediately */
2556 return (0);
2557 }
2558
2559 reap_child();
2560
2561 if (refresh) {
2562 /* We got THAW or REFRESH message */
2563 return (1);
2564 }
2565
2566 now = time(NULL);
2567 if (last_time > now) {
2568 /* clock has been reset to backward */
2569 return (1);
2570 }
2571
2572 if (next_event == NULL && !el_empty()) {
2573 next_event = (struct event *)el_first();
2574 }
2575
2576 if (next_event == NULL)
2577 t = INFINITY;
2578 else
2579 t = (long)next_event->time - now;
2580 }
2581 return (0);
2582 }
2583
2584 /*
2585 * This used to be in the idle(), but moved to the separate function.
2586 * This called from various place when cron needs to reap the
2587 * child. It includes the situation that cron hit maxrun, and needs
2588 * to reschedule the job.
2589 */
2590 static void
reap_child()2591 reap_child()
2592 {
2593 pid_t pid;
2594 int prc;
2595 struct runinfo *rp;
2596
2597 for (;;) {
2598 pid = waitpid((pid_t)-1, &prc, WNOHANG);
2599 if (pid <= 0)
2600 break;
2601 #ifdef DEBUG
2602 fprintf(stderr,
2603 "wait returned %x for process %d\n", prc, pid);
2604 #endif
2605 if ((rp = rinfo_get(pid)) == NULL) {
2606 if (miscpid_delete(pid) == 0) {
2607 /* not found in anywhere */
2608 msg(PIDERR, pid);
2609 }
2610 } else if (rp->que == ZOMB) {
2611 (void) unlink(rp->outfile);
2612 rinfo_free(rp);
2613 } else {
2614 cleanup(rp, prc);
2615 }
2616 }
2617 }
2618
2619 static void
cleanup(struct runinfo * pr,int rc)2620 cleanup(struct runinfo *pr, int rc)
2621 {
2622 int nextfork = 1;
2623 struct usr *p;
2624 struct stat buf;
2625
2626 logit(ECHAR, pr, rc);
2627 --qt[pr->que].nrun;
2628 p = pr->rusr;
2629 if (pr->que != CRONEVENT)
2630 --p->aruncnt;
2631 else
2632 --p->cruncnt;
2633
2634 if (lstat(pr->outfile, &buf) == 0) {
2635 if (!S_ISLNK(buf.st_mode) &&
2636 (buf.st_size > 0 || pr->mailwhendone)) {
2637 /* mail user stdout and stderr */
2638 for (;;) {
2639 if ((pr->pid = fork()) < 0) {
2640 /*
2641 * if fork fails try forever in doubling
2642 * retry times, up to 16 seconds
2643 */
2644 (void) sleep(nextfork);
2645 if (nextfork < 16)
2646 nextfork += nextfork;
2647 continue;
2648 } else if (pr->pid == 0) {
2649 child_sigreset();
2650 contract_clear_template();
2651
2652 mail_result(p, pr, buf.st_size);
2653 /* NOTREACHED */
2654 } else {
2655 contract_abandon_latest(pr->pid);
2656 pr->que = ZOMB;
2657 break;
2658 }
2659 }
2660 } else {
2661 (void) unlink(pr->outfile);
2662 rinfo_free(pr);
2663 }
2664 } else {
2665 rinfo_free(pr);
2666 }
2667
2668 free_if_unused(p);
2669 }
2670
2671 /*
2672 * Mail stdout and stderr of a job to user. Get uid for real user and become
2673 * that person. We do this so that mail won't come from root since this
2674 * could be a security hole. If failure, quit - don't send mail as root.
2675 */
2676 static void
mail_result(struct usr * p,struct runinfo * pr,size_t filesize)2677 mail_result(struct usr *p, struct runinfo *pr, size_t filesize)
2678 {
2679 struct passwd *ruser_ids;
2680 FILE *mailpipe;
2681 FILE *st;
2682 struct utsname name;
2683 int nbytes;
2684 char iobuf[BUFSIZ];
2685 char *cmd;
2686 char *lowname = (pr->jobtype == CRONEVENT ? "cron" : "at");
2687
2688 (void) uname(&name);
2689 if ((ruser_ids = getpwnam(p->name)) == NULL)
2690 exit(0);
2691 (void) setuid(ruser_ids->pw_uid);
2692
2693 cmd = xmalloc(strlen(MAIL) + strlen(p->name)+2);
2694 (void) sprintf(cmd, "%s %s", MAIL, p->name);
2695 mailpipe = popen(cmd, "w");
2696 free(cmd);
2697 if (mailpipe == NULL)
2698 exit(127);
2699 (void) fprintf(mailpipe, "To: %s\n", p->name);
2700 (void) fprintf(mailpipe, "Subject: %s <%s@%s> %s\n",
2701 (pr->jobtype == CRONEVENT ? "Cron" : "At"),
2702 p->name, name.nodename, pr->jobname);
2703
2704 /*
2705 * RFC3834 (Section 5) defines the Auto-Submitted header to prevent
2706 * vacation replies, et al, from being sent in response to
2707 * machine-generated mail.
2708 */
2709 (void) fprintf(mailpipe, "Auto-Submitted: auto-generated\n");
2710
2711 /*
2712 * Additional headers for mail filtering and diagnostics:
2713 */
2714 (void) fprintf(mailpipe, "X-Mailer: cron (%s %s)\n", name.sysname,
2715 name.release);
2716 (void) fprintf(mailpipe, "X-Cron-User: %s\n", p->name);
2717 (void) fprintf(mailpipe, "X-Cron-Host: %s\n", name.nodename);
2718 (void) fprintf(mailpipe, "X-Cron-Job-Name: %s\n", pr->jobname);
2719 (void) fprintf(mailpipe, "X-Cron-Job-Type: %s\n", lowname);
2720
2721 /*
2722 * Message Body:
2723 *
2724 * (Temporary file is fopen'ed with "r", secure open.)
2725 */
2726 (void) fprintf(mailpipe, "\n");
2727 if (filesize > 0 &&
2728 (st = fopen(pr->outfile, "r")) != NULL) {
2729 while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0)
2730 (void) fwrite(iobuf, sizeof (char), nbytes, mailpipe);
2731 (void) fclose(st);
2732 } else {
2733 (void) fprintf(mailpipe, "Job completed with no output.\n");
2734 }
2735 (void) pclose(mailpipe);
2736 exit(0);
2737 }
2738
2739 static int
msg_wait(long tim)2740 msg_wait(long tim)
2741 {
2742 struct message msg;
2743 int cnt;
2744 time_t reftime;
2745 fd_set fds;
2746 struct timespec tout, *toutp;
2747 static int pending_msg;
2748 static time_t pending_reftime;
2749
2750 if (pending_msg) {
2751 process_msg(&msgbuf, pending_reftime);
2752 pending_msg = 0;
2753 return (0);
2754 }
2755
2756 FD_ZERO(&fds);
2757 FD_SET(msgfd, &fds);
2758
2759 toutp = NULL;
2760 if (tim != INFINITY) {
2761 #ifdef CRON_MAXSLEEP
2762 /*
2763 * CRON_MAXSLEEP can be defined to have cron periodically wake
2764 * up, so that cron can detect a change of TOD and adjust the
2765 * sleep time more frequently.
2766 */
2767 tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim;
2768 #endif
2769 tout.tv_nsec = 0;
2770 tout.tv_sec = tim;
2771 toutp = &tout;
2772 }
2773
2774 cnt = pselect(msgfd + 1, &fds, NULL, NULL, toutp, &defmask);
2775 if (cnt == -1 && errno != EINTR)
2776 perror("! pselect");
2777
2778 /* pselect timeout or interrupted */
2779 if (cnt <= 0)
2780 return (0);
2781
2782 errno = 0;
2783 if ((cnt = read(msgfd, &msg, sizeof (msg))) != sizeof (msg)) {
2784 if (cnt != -1 || errno != EAGAIN)
2785 perror("! read");
2786 return (0);
2787 }
2788 reftime = time(NULL);
2789 if (next_event != NULL && reftime >= next_event->time) {
2790 /*
2791 * we need to run the job before reloading crontab.
2792 */
2793 (void) memcpy(&msgbuf, &msg, sizeof (msg));
2794 pending_msg = 1;
2795 pending_reftime = reftime;
2796 return (1);
2797 }
2798 process_msg(&msg, reftime);
2799 return (0);
2800 }
2801
2802 /*
2803 * process the message supplied via pipe. This will be called either
2804 * immediately after cron read the message from pipe, or idle time
2805 * if the message was pending due to the job execution.
2806 */
2807 static void
process_msg(struct message * pmsg,time_t reftime)2808 process_msg(struct message *pmsg, time_t reftime)
2809 {
2810 if (pmsg->etype == 0)
2811 return;
2812
2813 switch (pmsg->etype) {
2814 case AT:
2815 if (pmsg->action == DELETE)
2816 del_atjob(pmsg->fname, pmsg->logname);
2817 else
2818 mod_atjob(pmsg->fname, (time_t)0);
2819 break;
2820 case CRON:
2821 if (pmsg->action == DELETE)
2822 del_ctab(pmsg->fname);
2823 else
2824 mod_ctab(pmsg->fname, reftime);
2825 break;
2826 case REFRESH:
2827 refresh = 1;
2828 pmsg->etype = 0;
2829 return;
2830 default:
2831 msg("message received - bad format");
2832 break;
2833 }
2834 if (next_event != NULL) {
2835 if (next_event->etype == CRONEVENT) {
2836 switch (el_add(next_event, next_event->time,
2837 (next_event->u)->ctid)) {
2838 case -1:
2839 ignore_msg("process_msg", "cron", next_event);
2840 break;
2841 case -2: /* event time lower than init time */
2842 reset_needed = 1;
2843 break;
2844 }
2845 } else { /* etype == ATEVENT */
2846 if (el_add(next_event, next_event->time,
2847 next_event->of.at.eventid) < 0) {
2848 ignore_msg("process_msg", "at", next_event);
2849 }
2850 }
2851 next_event = NULL;
2852 }
2853 (void) fflush(stdout);
2854 pmsg->etype = 0;
2855 }
2856
2857 /*
2858 * Allocate a new or find an existing runinfo structure
2859 */
2860 static struct runinfo *
rinfo_get(pid_t pid)2861 rinfo_get(pid_t pid)
2862 {
2863 struct runinfo *rp;
2864
2865 if (pid == 0) { /* allocate a new entry */
2866 rp = xcalloc(1, sizeof (struct runinfo));
2867 rp->next = rthead; /* link the entry into the list */
2868 rthead = rp;
2869 return (rp);
2870 }
2871 /* search the list for an existing entry */
2872 for (rp = rthead; rp != NULL; rp = rp->next) {
2873 if (rp->pid == pid)
2874 break;
2875 }
2876 return (rp);
2877 }
2878
2879 /*
2880 * Free a runinfo structure and its associated memory
2881 */
2882 static void
rinfo_free(struct runinfo * entry)2883 rinfo_free(struct runinfo *entry)
2884 {
2885 struct runinfo **rpp;
2886 struct runinfo *rp;
2887
2888 #ifdef DEBUG
2889 (void) fprintf(stderr, "freeing job %s\n", entry->jobname);
2890 #endif
2891 for (rpp = &rthead; (rp = *rpp) != NULL; rpp = &rp->next) {
2892 if (rp == entry) {
2893 *rpp = rp->next; /* unlink the entry */
2894 free(rp->outfile);
2895 free(rp->jobname);
2896 free(rp);
2897 break;
2898 }
2899 }
2900 }
2901
2902 static void
thaw_handler(int sig __unused)2903 thaw_handler(int sig __unused)
2904 {
2905 refresh = 1;
2906 }
2907
2908
2909 static void
cronend(int sig __unused)2910 cronend(int sig __unused)
2911 {
2912 crabort("SIGTERM", REMOVE_FIFO);
2913 }
2914
2915 static void
child_handler(int sig __unused)2916 child_handler(int sig __unused)
2917 {
2918 ;
2919 }
2920
2921 static void
child_sigreset(void)2922 child_sigreset(void)
2923 {
2924 (void) signal(SIGCLD, SIG_DFL);
2925 (void) sigprocmask(SIG_SETMASK, &defmask, NULL);
2926 }
2927
2928 /*
2929 * crabort() - handle exits out of cron
2930 */
2931 static void
crabort(char * mssg,int action)2932 crabort(char *mssg, int action)
2933 {
2934 int c;
2935
2936 if (action & REMOVE_FIFO) {
2937 /* FIFO vanishes when cron finishes */
2938 if (unlink(FIFO) < 0)
2939 perror("cron could not unlink FIFO");
2940 }
2941
2942 if (action & CONSOLE_MSG) {
2943 /* write error msg to console */
2944 if ((c = open(CONSOLE, O_WRONLY)) >= 0) {
2945 (void) write(c, "cron aborted: ", 14);
2946 (void) write(c, mssg, strlen(mssg));
2947 (void) write(c, "\n", 1);
2948 (void) close(c);
2949 }
2950 }
2951
2952 /* always log the message */
2953 msg(mssg);
2954 msg("******* CRON ABORTED ********");
2955 exit(1);
2956 }
2957
2958 /*
2959 * msg() - time-stamped error reporting function
2960 */
2961 /*PRINTFLIKE1*/
2962 static void
msg(char * fmt,...)2963 msg(char *fmt, ...)
2964 {
2965 va_list args;
2966 time_t t;
2967
2968 t = time(NULL);
2969
2970 (void) fflush(stdout);
2971
2972 (void) fprintf(stderr, "! ");
2973
2974 va_start(args, fmt);
2975 (void) vfprintf(stderr, fmt, args);
2976 va_end(args);
2977
2978 (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
2979 (void) fprintf(stderr, " %s\n", timebuf);
2980
2981 (void) fflush(stderr);
2982 }
2983
2984 static void
ignore_msg(char * func_name,char * job_type,struct event * event)2985 ignore_msg(char *func_name, char *job_type, struct event *event)
2986 {
2987 msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)",
2988 func_name, job_type,
2989 event->u->name ? event->u->name : "unknown",
2990 event->cmd ? event->cmd : "unknown",
2991 event->time);
2992 }
2993
2994 static void
logit(int cc,struct runinfo * rp,int rc)2995 logit(int cc, struct runinfo *rp, int rc)
2996 {
2997 time_t t;
2998 int ret;
2999
3000 if (!log)
3001 return;
3002
3003 t = time(NULL);
3004 if (cc == BCHAR)
3005 (void) printf("%c CMD: %s\n", cc, next_event->cmd);
3006 (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t));
3007 (void) printf("%c %s %u %c %s",
3008 cc, (rp->rusr)->name, rp->pid, QUE(rp->que), timebuf);
3009 if ((ret = TSTAT(rc)) != 0)
3010 (void) printf(" ts=%d", ret);
3011 if ((ret = RCODE(rc)) != 0)
3012 (void) printf(" rc=%d", ret);
3013 (void) putchar('\n');
3014 (void) fflush(stdout);
3015 }
3016
3017 static void
resched(int delay)3018 resched(int delay)
3019 {
3020 time_t nt;
3021
3022 /* run job at a later time */
3023 nt = next_event->time + delay;
3024 if (next_event->etype == CRONEVENT) {
3025 next_event->time = next_time(next_event, (time_t)0);
3026 if (nt < next_event->time)
3027 next_event->time = nt;
3028 switch (el_add(next_event, next_event->time,
3029 (next_event->u)->ctid)) {
3030 case -1:
3031 ignore_msg("resched", "cron", next_event);
3032 break;
3033 case -2: /* event time lower than init time */
3034 reset_needed = 1;
3035 break;
3036 }
3037 delayed = 1;
3038 msg("rescheduling a cron job");
3039 return;
3040 }
3041 add_atevent(next_event->u, next_event->cmd, nt, next_event->etype);
3042 msg("rescheduling at job");
3043 }
3044
3045 static void
quedefs(int action)3046 quedefs(int action)
3047 {
3048 int i;
3049 int j;
3050 char qbuf[QBUFSIZ];
3051 FILE *fd;
3052
3053 /* set up default queue definitions */
3054 for (i = 0; i < NQUEUE; i++) {
3055 qt[i].njob = qd.njob;
3056 qt[i].nice = qd.nice;
3057 qt[i].nwait = qd.nwait;
3058 }
3059 if (action == DEFAULT)
3060 return;
3061 if ((fd = fopen(QUEDEFS, "r")) == NULL) {
3062 msg("cannot open quedefs file");
3063 msg("using default queue definitions");
3064 return;
3065 }
3066 while (fgets(qbuf, QBUFSIZ, fd) != NULL) {
3067 if ((j = qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.')
3068 continue;
3069 parsqdef(&qbuf[2]);
3070 qt[j].njob = qq.njob;
3071 qt[j].nice = qq.nice;
3072 qt[j].nwait = qq.nwait;
3073 }
3074 (void) fclose(fd);
3075 }
3076
3077 static void
parsqdef(char * name)3078 parsqdef(char *name)
3079 {
3080 int i;
3081
3082 qq = qd;
3083 while (*name) {
3084 i = 0;
3085 while (isdigit(*name)) {
3086 i *= 10;
3087 i += *name++ - '0';
3088 }
3089 switch (*name++) {
3090 case JOBF:
3091 qq.njob = i;
3092 break;
3093 case NICEF:
3094 qq.nice = i;
3095 break;
3096 case WAITF:
3097 qq.nwait = i;
3098 break;
3099 }
3100 }
3101 }
3102
3103 /*
3104 * defaults - read defaults from /etc/default/cron
3105 */
3106 static void
defaults()3107 defaults()
3108 {
3109 int flags;
3110 char *deflog;
3111 char *hz, *tz;
3112
3113 /*
3114 * get HZ value for environment
3115 */
3116 if ((hz = getenv("HZ")) == (char *)NULL)
3117 (void) sprintf(hzname, "HZ=%d", HZ);
3118 else
3119 (void) snprintf(hzname, sizeof (hzname), "HZ=%s", hz);
3120 /*
3121 * get TZ value for environment
3122 */
3123 (void) snprintf(tzone, sizeof (tzone), "TZ=%s",
3124 ((tz = getenv("TZ")) != NULL) ? tz : DEFTZ);
3125
3126 if (defopen(DEFFILE) == 0) {
3127 /* ignore case */
3128 flags = defcntl(DC_GETFLAGS, 0);
3129 TURNOFF(flags, DC_CASE);
3130 (void) defcntl(DC_SETFLAGS, flags);
3131
3132 if (((deflog = defread("CRONLOG=")) == NULL) ||
3133 (*deflog == 'N') || (*deflog == 'n'))
3134 log = 0;
3135 else
3136 log = 1;
3137 /* fix for 1087611 - allow paths to be set in defaults file */
3138 if ((Def_path = defread("PATH=")) != NULL) {
3139 (void) strlcat(path, Def_path, LINE_MAX);
3140 } else {
3141 (void) strlcpy(path, NONROOTPATH, LINE_MAX);
3142 }
3143 if ((Def_supath = defread("SUPATH=")) != NULL) {
3144 (void) strlcat(supath, Def_supath, LINE_MAX);
3145 } else {
3146 (void) strlcpy(supath, ROOTPATH, LINE_MAX);
3147 }
3148 (void) defopen(NULL);
3149 }
3150 }
3151
3152 /*
3153 * Determine if a user entry for a job is still ok. The method used here
3154 * is a lot (about 75x) faster than using setgrent() / getgrent()
3155 * endgrent(). It should be safe because we use the sysconf to determine
3156 * the max, and it tolerates the max being 0.
3157 */
3158
3159 static int
verify_user_cred(struct usr * u)3160 verify_user_cred(struct usr *u)
3161 {
3162 struct passwd *pw;
3163 size_t numUsrGrps = 0;
3164 size_t numOrigGrps = 0;
3165 size_t i;
3166 int retval;
3167
3168 /*
3169 * Maximum number of groups a user may be in concurrently. This
3170 * is a value which we obtain at runtime through a sysconf()
3171 * call.
3172 */
3173
3174 static size_t nGroupsMax = (size_t)-1;
3175
3176 /*
3177 * Arrays for cron user's group list, constructed at startup to
3178 * be nGroupsMax elements long, used for verifying user
3179 * credentials prior to execution.
3180 */
3181
3182 static gid_t *UsrGrps;
3183 static gid_t *OrigGrps;
3184
3185 if ((pw = getpwnam(u->name)) == NULL)
3186 return (VUC_BADUSER);
3187 if (u->home != NULL) {
3188 if (strcmp(u->home, pw->pw_dir) != 0) {
3189 free(u->home);
3190 u->home = xmalloc(strlen(pw->pw_dir) + 1);
3191 (void) strcpy(u->home, pw->pw_dir);
3192 }
3193 } else {
3194 u->home = xmalloc(strlen(pw->pw_dir) + 1);
3195 (void) strcpy(u->home, pw->pw_dir);
3196 }
3197 if (u->uid != pw->pw_uid)
3198 u->uid = pw->pw_uid;
3199 if (u->gid != pw->pw_gid)
3200 u->gid = pw->pw_gid;
3201
3202 /*
3203 * Create the group id lists needed for job credential
3204 * verification.
3205 */
3206
3207 if (nGroupsMax == (size_t)-1) {
3208 if ((nGroupsMax = sysconf(_SC_NGROUPS_MAX)) > 0) {
3209 UsrGrps = xcalloc(nGroupsMax, sizeof (gid_t));
3210 OrigGrps = xcalloc(nGroupsMax, sizeof (gid_t));
3211 }
3212
3213 #ifdef DEBUG
3214 (void) fprintf(stderr, "nGroupsMax = %ld\n", nGroupsMax);
3215 #endif
3216 }
3217
3218 #ifdef DEBUG
3219 (void) fprintf(stderr, "verify_user_cred (%s-%d)\n", pw->pw_name,
3220 pw->pw_uid);
3221 (void) fprintf(stderr, "verify_user_cred: pw->pw_gid = %d, "
3222 "u->gid = %d\n", pw->pw_gid, u->gid);
3223 #endif
3224
3225 retval = (u->gid == pw->pw_gid) ? VUC_OK : VUC_NOTINGROUP;
3226
3227 if (nGroupsMax > 0) {
3228 numOrigGrps = getgroups(nGroupsMax, OrigGrps);
3229
3230 (void) initgroups(pw->pw_name, pw->pw_gid);
3231 numUsrGrps = getgroups(nGroupsMax, UsrGrps);
3232
3233 for (i = 0; i < numUsrGrps; i++) {
3234 if (UsrGrps[i] == u->gid) {
3235 retval = VUC_OK;
3236 break;
3237 }
3238 }
3239
3240 if (OrigGrps) {
3241 (void) setgroups(numOrigGrps, OrigGrps);
3242 }
3243 }
3244
3245 #ifdef DEBUG
3246 (void) fprintf(stderr, "verify_user_cred: VUC = %d\n", retval);
3247 #endif
3248
3249 return (retval);
3250 }
3251
3252 static int
set_user_cred(const struct usr * u,struct project * pproj)3253 set_user_cred(const struct usr *u, struct project *pproj)
3254 {
3255 static char *progname = "cron";
3256 int r = 0, rval = 0;
3257
3258 if ((r = pam_start(progname, u->name, &pam_conv, &pamh))
3259 != PAM_SUCCESS) {
3260 #ifdef DEBUG
3261 msg("pam_start returns %d\n", r);
3262 #endif
3263 rval = VUC_BADUSER;
3264 goto set_eser_cred_exit;
3265 }
3266
3267 r = pam_acct_mgmt(pamh, 0);
3268 #ifdef DEBUG
3269 msg("pam_acc_mgmt returns %d\n", r);
3270 #endif
3271 if (r == PAM_ACCT_EXPIRED) {
3272 rval = VUC_EXPIRED;
3273 goto set_eser_cred_exit;
3274 }
3275 if (r == PAM_NEW_AUTHTOK_REQD) {
3276 rval = VUC_NEW_AUTH;
3277 goto set_eser_cred_exit;
3278 }
3279 if (r != PAM_SUCCESS) {
3280 rval = VUC_BADUSER;
3281 goto set_eser_cred_exit;
3282 }
3283
3284 if (pproj != NULL) {
3285 size_t sz = sizeof (PROJECT) + strlen(pproj->pj_name);
3286 char *buf = alloca(sz);
3287
3288 (void) snprintf(buf, sz, PROJECT "%s", pproj->pj_name);
3289 (void) pam_set_item(pamh, PAM_RESOURCE, buf);
3290 }
3291
3292 r = pam_setcred(pamh, PAM_ESTABLISH_CRED);
3293 if (r != PAM_SUCCESS)
3294 rval = VUC_BADUSER;
3295
3296 set_eser_cred_exit:
3297 (void) pam_end(pamh, r);
3298 return (rval);
3299 }
3300
3301 static void
clean_out_user(struct usr * u)3302 clean_out_user(struct usr *u)
3303 {
3304 if (next_event->u == u) {
3305 next_event = NULL;
3306 }
3307
3308 clean_out_ctab(u);
3309 clean_out_atjobs(u);
3310 free_if_unused(u);
3311 }
3312
3313 static void
clean_out_atjobs(struct usr * u)3314 clean_out_atjobs(struct usr *u)
3315 {
3316 struct event *ev, *pv;
3317
3318 for (pv = NULL, ev = u->atevents;
3319 ev != NULL;
3320 pv = ev, ev = ev->link, free(pv)) {
3321 el_remove(ev->of.at.eventid, 1);
3322 if (cwd == AT)
3323 cron_unlink(ev->cmd);
3324 else {
3325 char buf[PATH_MAX];
3326 if (strlen(ATDIR) + strlen(ev->cmd) + 2
3327 < PATH_MAX) {
3328 (void) sprintf(buf, "%s/%s", ATDIR, ev->cmd);
3329 cron_unlink(buf);
3330 }
3331 }
3332 free(ev->cmd);
3333 }
3334
3335 u->atevents = NULL;
3336 }
3337
3338 static void
clean_out_ctab(struct usr * u)3339 clean_out_ctab(struct usr *u)
3340 {
3341 rm_ctevents(u);
3342 el_remove(u->ctid, 0);
3343 u->ctid = 0;
3344 u->ctexists = 0;
3345 }
3346
3347 static void
cron_unlink(char * name)3348 cron_unlink(char *name)
3349 {
3350 int r;
3351
3352 r = unlink(name);
3353 if (r == 0 || (r == -1 && errno == ENOENT)) {
3354 (void) audit_cron_delete_anc_file(name, NULL);
3355 }
3356 }
3357
3358 static void
create_anc_ctab(struct event * e)3359 create_anc_ctab(struct event *e)
3360 {
3361 if (audit_cron_create_anc_file(e->u->name,
3362 (cwd == CRON) ? NULL:CRONDIR,
3363 e->u->name, e->u->uid) == -1) {
3364 process_anc_files(CRON_ANC_DELETE);
3365 crabort("cannot create ancillary files for crontabs",
3366 REMOVE_FIFO|CONSOLE_MSG);
3367 }
3368 }
3369
3370 static void
delete_anc_ctab(struct event * e)3371 delete_anc_ctab(struct event *e)
3372 {
3373 (void) audit_cron_delete_anc_file(e->u->name,
3374 (cwd == CRON) ? NULL:CRONDIR);
3375 }
3376
3377 static void
create_anc_atjob(struct event * e)3378 create_anc_atjob(struct event *e)
3379 {
3380 if (!e->of.at.exists)
3381 return;
3382
3383 if (audit_cron_create_anc_file(e->cmd,
3384 (cwd == AT) ? NULL:ATDIR,
3385 e->u->name, e->u->uid) == -1) {
3386 process_anc_files(CRON_ANC_DELETE);
3387 crabort("cannot create ancillary files for atjobs",
3388 REMOVE_FIFO|CONSOLE_MSG);
3389 }
3390 }
3391
3392 static void
delete_anc_atjob(struct event * e)3393 delete_anc_atjob(struct event *e)
3394 {
3395 if (!e->of.at.exists)
3396 return;
3397
3398 (void) audit_cron_delete_anc_file(e->cmd,
3399 (cwd == AT) ? NULL:ATDIR);
3400 }
3401
3402
3403 static void
process_anc_files(int del)3404 process_anc_files(int del)
3405 {
3406 struct usr *u = uhead;
3407 struct event *e;
3408
3409 if (!audit_cron_mode())
3410 return;
3411
3412 for (;;) {
3413 if (u->ctexists && u->ctevents != NULL) {
3414 e = u->ctevents;
3415 for (;;) {
3416 if (del)
3417 delete_anc_ctab(e);
3418 else
3419 create_anc_ctab(e);
3420 if ((e = e->link) == NULL)
3421 break;
3422 }
3423 }
3424
3425 if (u->atevents != NULL) {
3426 e = u->atevents;
3427 for (;;) {
3428 if (del)
3429 delete_anc_atjob(e);
3430 else
3431 create_anc_atjob(e);
3432 if ((e = e->link) == NULL)
3433 break;
3434 }
3435 }
3436
3437 if ((u = u->nextusr) == NULL)
3438 break;
3439 }
3440 }
3441
3442 static int
cron_conv(int num_msg,const struct pam_message ** msgs,struct pam_response ** response __unused,void * appdata_ptr __unused)3443 cron_conv(int num_msg, const struct pam_message **msgs,
3444 struct pam_response **response __unused, void *appdata_ptr __unused)
3445 {
3446 const struct pam_message **m = msgs;
3447 int i;
3448
3449 for (i = 0; i < num_msg; i++) {
3450 switch (m[i]->msg_style) {
3451 case PAM_ERROR_MSG:
3452 case PAM_TEXT_INFO:
3453 if (m[i]->msg != NULL) {
3454 (void) msg("%s\n", m[i]->msg);
3455 }
3456 break;
3457
3458 default:
3459 break;
3460 }
3461 }
3462 return (0);
3463 }
3464
3465 /*
3466 * Cron creates process for other than job. Mail process is the
3467 * one which rinfo does not cover. Therefore, miscpid will keep
3468 * track of the pids executed from cron. Otherwise, we will see
3469 * "unexpected pid returned.." messages appear in the log file.
3470 */
3471 static void
miscpid_insert(pid_t pid)3472 miscpid_insert(pid_t pid)
3473 {
3474 struct miscpid *mp;
3475
3476 mp = xmalloc(sizeof (*mp));
3477 mp->pid = pid;
3478 mp->next = miscpid_head;
3479 miscpid_head = mp;
3480 }
3481
3482 static int
miscpid_delete(pid_t pid)3483 miscpid_delete(pid_t pid)
3484 {
3485 struct miscpid *mp, *omp;
3486 int found = 0;
3487
3488 omp = NULL;
3489 for (mp = miscpid_head; mp != NULL; mp = mp->next) {
3490 if (mp->pid == pid) {
3491 found = 1;
3492 break;
3493 }
3494 omp = mp;
3495 }
3496 if (found) {
3497 if (omp != NULL)
3498 omp->next = mp->next;
3499 else
3500 miscpid_head = NULL;
3501 free(mp);
3502 }
3503 return (found);
3504 }
3505
3506 /*
3507 * Establish contract terms such that all children are in abandoned
3508 * process contracts.
3509 */
3510 static void
contract_set_template(void)3511 contract_set_template(void)
3512 {
3513 int fd;
3514
3515 if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3516 crabort("cannot open process contract template",
3517 REMOVE_FIFO | CONSOLE_MSG);
3518
3519 if (ct_pr_tmpl_set_param(fd, 0) ||
3520 ct_tmpl_set_informative(fd, 0) ||
3521 ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR))
3522 crabort("cannot establish contract template terms",
3523 REMOVE_FIFO | CONSOLE_MSG);
3524
3525 if (ct_tmpl_activate(fd))
3526 crabort("cannot activate contract template",
3527 REMOVE_FIFO | CONSOLE_MSG);
3528
3529 (void) close(fd);
3530 }
3531
3532 /*
3533 * Clear active process contract template.
3534 */
3535 static void
contract_clear_template(void)3536 contract_clear_template(void)
3537 {
3538 int fd;
3539
3540 if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0)
3541 crabort("cannot open process contract template",
3542 REMOVE_FIFO | CONSOLE_MSG);
3543
3544 if (ct_tmpl_clear(fd))
3545 crabort("cannot clear contract template",
3546 REMOVE_FIFO | CONSOLE_MSG);
3547
3548 (void) close(fd);
3549 }
3550
3551 /*
3552 * Abandon latest process contract unconditionally. If we have leaked [some
3553 * critical amount], exit such that the kernel reaps our contracts.
3554 */
3555 static void
contract_abandon_latest(pid_t pid)3556 contract_abandon_latest(pid_t pid)
3557 {
3558 int r;
3559 ctid_t id;
3560 static uint_t cts_lost;
3561
3562 if (cts_lost > MAX_LOST_CONTRACTS)
3563 crabort("repeated failure to abandon contracts",
3564 REMOVE_FIFO | CONSOLE_MSG);
3565
3566 if ((r = contract_latest(&id)) != 0) {
3567 msg("could not obtain latest contract for "
3568 "PID %ld: %s", pid, strerror(r));
3569 cts_lost++;
3570 return;
3571 }
3572
3573 if ((r = contract_abandon_id(id)) != 0) {
3574 msg("could not abandon latest contract %ld: %s", id,
3575 strerror(r));
3576 cts_lost++;
3577 return;
3578 }
3579 }
3580
3581 static struct shared *
create_shared(void * obj,void * (* obj_alloc)(void * obj),void (* obj_free)(void *))3582 create_shared(void *obj, void * (*obj_alloc)(void *obj),
3583 void (*obj_free)(void *))
3584 {
3585 struct shared *out;
3586
3587 if ((out = xmalloc(sizeof (struct shared))) == NULL) {
3588 return (NULL);
3589 }
3590 if ((out->obj = obj_alloc(obj)) == NULL) {
3591 free(out);
3592 return (NULL);
3593 }
3594 out->count = 1;
3595 out->free = obj_free;
3596
3597 return (out);
3598 }
3599
3600 static struct shared *
create_shared_str(char * str)3601 create_shared_str(char *str)
3602 {
3603 return (create_shared(str, (void *(*)(void *))strdup, free));
3604 }
3605
3606 static struct shared *
dup_shared(struct shared * obj)3607 dup_shared(struct shared *obj)
3608 {
3609 if (obj != NULL) {
3610 obj->count++;
3611 }
3612 return (obj);
3613 }
3614
3615 static void
rel_shared(struct shared * obj)3616 rel_shared(struct shared *obj)
3617 {
3618 if (obj && (--obj->count) == 0) {
3619 obj->free(obj->obj);
3620 free(obj);
3621 }
3622 }
3623
3624 static void *
get_obj(struct shared * obj)3625 get_obj(struct shared *obj)
3626 {
3627 return (obj->obj);
3628 }
3629